Files
lijiaoqiao/supply-api/internal/domain/invariants_test.go
Your Name 0196ee5d47 feat(supply-api): 完成核心模块实现
新增/修改内容:
- 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 凭证边界硬门禁
2026-04-01 08:53:28 +08:00

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
}