55471e15de
为兑换码增加可选生效和截止时间,并按左闭右开区间校验 同步 SpacetimeDB 迁移、生成绑定、共享契约和后台管理界面 让后台侧边栏与主内容区域独立滚动 修正兑换业务拒绝的 HTTP 状态映射并补充回归测试和文档
90 lines
3.1 KiB
Rust
90 lines
3.1 KiB
Rust
use module_runtime::{
|
|
RuntimeProfileFieldError, RuntimeProfileRedeemCodeMode, RuntimeProfileRedeemCodeSnapshot,
|
|
build_runtime_profile_redeem_code_admin_upsert_input, build_runtime_profile_redeem_code_record,
|
|
validate_runtime_profile_redeem_code_usage,
|
|
validate_runtime_profile_redeem_code_validity_window,
|
|
};
|
|
|
|
#[test]
|
|
fn redeem_code_validity_window_allows_open_ended_and_requires_strict_order() {
|
|
assert!(validate_runtime_profile_redeem_code_validity_window(None, None).is_ok());
|
|
assert!(validate_runtime_profile_redeem_code_validity_window(Some(10), None).is_ok());
|
|
assert!(validate_runtime_profile_redeem_code_validity_window(None, Some(10)).is_ok());
|
|
assert_eq!(
|
|
validate_runtime_profile_redeem_code_validity_window(Some(10), Some(10)),
|
|
Err(RuntimeProfileFieldError::InvalidRedeemCodeValidityWindow)
|
|
);
|
|
assert_eq!(
|
|
validate_runtime_profile_redeem_code_validity_window(Some(20), Some(10)),
|
|
Err(RuntimeProfileFieldError::InvalidRedeemCodeValidityWindow)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn redeem_code_admin_upsert_carries_optional_validity_window() {
|
|
let input = build_runtime_profile_redeem_code_admin_upsert_input(
|
|
"admin-1".to_string(),
|
|
"GIFT".to_string(),
|
|
RuntimeProfileRedeemCodeMode::Public,
|
|
30,
|
|
1,
|
|
true,
|
|
Vec::new(),
|
|
Vec::new(),
|
|
Some(10),
|
|
Some(20),
|
|
5,
|
|
)
|
|
.expect("valid window should pass");
|
|
|
|
assert_eq!(input.starts_at_micros, Some(10));
|
|
assert_eq!(input.expires_at_micros, Some(20));
|
|
}
|
|
|
|
#[test]
|
|
fn redeem_code_usage_uses_inclusive_start_and_exclusive_expire_boundary() {
|
|
let code = redeem_code_snapshot(Some(20), Some(30));
|
|
|
|
assert_eq!(
|
|
validate_runtime_profile_redeem_code_usage(&code, "user-1", 0, 19),
|
|
Err(RuntimeProfileFieldError::RedeemCodeNotStarted)
|
|
);
|
|
validate_runtime_profile_redeem_code_usage(&code, "user-1", 0, 20)
|
|
.expect("start boundary should be valid");
|
|
validate_runtime_profile_redeem_code_usage(&code, "user-1", 0, 29)
|
|
.expect("time before expire boundary should be valid");
|
|
assert_eq!(
|
|
validate_runtime_profile_redeem_code_usage(&code, "user-1", 0, 30),
|
|
Err(RuntimeProfileFieldError::RedeemCodeExpired)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn redeem_code_record_formats_optional_validity_window() {
|
|
let record =
|
|
build_runtime_profile_redeem_code_record(redeem_code_snapshot(Some(0), Some(1_000_000)));
|
|
|
|
assert_eq!(record.starts_at.as_deref(), Some("1970-01-01T00:00:00Z"));
|
|
assert_eq!(record.expires_at.as_deref(), Some("1970-01-01T00:00:01Z"));
|
|
}
|
|
|
|
fn redeem_code_snapshot(
|
|
starts_at_micros: Option<i64>,
|
|
expires_at_micros: Option<i64>,
|
|
) -> RuntimeProfileRedeemCodeSnapshot {
|
|
RuntimeProfileRedeemCodeSnapshot {
|
|
code: "GIFT".to_string(),
|
|
mode: RuntimeProfileRedeemCodeMode::Public,
|
|
reward_points: 30,
|
|
max_uses: 1,
|
|
global_used_count: 0,
|
|
enabled: true,
|
|
allowed_user_ids: Vec::new(),
|
|
created_by: "admin".to_string(),
|
|
created_at_micros: 1,
|
|
updated_at_micros: 1,
|
|
starts_at_micros,
|
|
expires_at_micros,
|
|
}
|
|
}
|