新增/修改内容: - config: 添加配置管理(config.example.yaml, config.go) - cache: 添加Redis缓存层(redis.go) - domain: 添加invariants不变量验证及测试 - middleware: 添加auth认证和idempotency幂等性中间件及测试 - repository: 添加完整数据访问层(account, package, settlement, idempotency, db) - sql: 添加幂等性表DDL脚本 代码覆盖: - auth middleware实现凭证边界验证 - idempotency middleware实现请求幂等性 - invariants实现业务不变量检查 - repository层实现完整的数据访问逻辑 关联issue: Round-1 R1-ISSUE-006 凭证边界硬门禁
102 lines
3.5 KiB
Go
102 lines
3.5 KiB
Go
package domain
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateAccountStateTransition(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
from AccountStatus
|
|
to AccountStatus
|
|
expected bool
|
|
}{
|
|
{"pending to active", AccountStatusPending, AccountStatusActive, true},
|
|
{"pending to disabled", AccountStatusPending, AccountStatusDisabled, true},
|
|
{"active to suspended", AccountStatusActive, AccountStatusSuspended, true},
|
|
{"active to disabled", AccountStatusActive, AccountStatusDisabled, true},
|
|
{"suspended to active", AccountStatusSuspended, AccountStatusActive, true},
|
|
{"suspended to disabled", AccountStatusSuspended, AccountStatusDisabled, true},
|
|
{"disabled to active", AccountStatusDisabled, AccountStatusActive, true},
|
|
{"active to pending", AccountStatusActive, AccountStatusPending, false},
|
|
{"suspended to pending", AccountStatusSuspended, AccountStatusPending, false},
|
|
{"disabled to suspended", AccountStatusDisabled, AccountStatusSuspended, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ValidateStateTransition(tt.from, tt.to)
|
|
if result != tt.expected {
|
|
t.Errorf("ValidateStateTransition(%s, %s) = %v, want %v", tt.from, tt.to, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidatePackageStateTransition(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
from PackageStatus
|
|
to PackageStatus
|
|
expected bool
|
|
}{
|
|
{"draft to active", PackageStatusDraft, PackageStatusActive, true},
|
|
{"active to paused", PackageStatusActive, PackageStatusPaused, true},
|
|
{"active to sold_out", PackageStatusActive, PackageStatusSoldOut, true},
|
|
{"active to expired", PackageStatusActive, PackageStatusExpired, true},
|
|
{"paused to active", PackageStatusPaused, PackageStatusActive, true},
|
|
{"paused to expired", PackageStatusPaused, PackageStatusExpired, true},
|
|
{"draft to paused", PackageStatusDraft, PackageStatusPaused, false},
|
|
{"sold_out to active", PackageStatusSoldOut, PackageStatusActive, false},
|
|
{"expired to active", PackageStatusExpired, PackageStatusActive, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ValidatePackageStateTransition(tt.from, tt.to)
|
|
if result != tt.expected {
|
|
t.Errorf("ValidatePackageStateTransition(%s, %s) = %v, want %v", tt.from, tt.to, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInvariantErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
contains string
|
|
}{
|
|
{"account cannot delete active", ErrAccountCannotDeleteActive, "cannot delete active"},
|
|
{"account disabled requires admin", ErrAccountDisabledRequiresAdmin, "disabled account requires admin"},
|
|
{"package sold out system only", ErrPackageSoldOutSystemOnly, "sold_out status"},
|
|
{"package expired cannot restore", ErrPackageExpiredCannotRestore, "expired package cannot"},
|
|
{"settlement cannot cancel", ErrSettlementCannotCancel, "cannot cancel"},
|
|
{"withdraw exceeds balance", ErrWithdrawExceedsBalance, "exceeds available balance"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.err == nil {
|
|
t.Errorf("expected error but got nil")
|
|
}
|
|
if tt.contains != "" && !containsString(tt.err.Error(), tt.contains) {
|
|
t.Errorf("error = %v, want contains %v", tt.err, tt.contains)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func containsString(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstring(s, substr))
|
|
}
|
|
|
|
func containsSubstring(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|