package dnfbridge import ( "context" "testing" "time" dnfdungeonadmission "longheng.io/server/internal/modules/dnf/dungeonadmission" dnfrepo "longheng.io/server/internal/modules/dnf/repository" dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory" ) // abisTestOwner 建一个内存仓储组 + 角色 + 账号,并返回准入 Owner。 func abisTestOwner(t *testing.T) (*dnfdungeonadmission.Owner, dnfrepo.Group, string, string) { t.Helper() repositories := dnfrepomemory.NewMemoryGroup() ctx := context.Background() accountID := "dnf:abis-test" characterID := "7" if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{ CharacterID: characterID, AccountID: accountID, Slot: 0, Level: 90, }); err != nil { t.Fatalf("save character: %v", err) } if err := repositories.Account.Save(ctx, dnfrepo.AccountRecord{ AccountID: accountID, State: "active", Metadata: map[string]string{}, }); err != nil { t.Fatalf("save account: %v", err) } owner, err := dnfdungeonadmission.NewOwner(repositories) if err != nil { t.Fatalf("new owner: %v", err) } return owner, repositories, accountID, characterID } // 验证账号 Metadata 侧的准入计数在提交后能被再次读到(内存层往返)。 // 这条覆盖 Owner.Commit 的账号策略写入路径。 func TestAbisAccountCounterRoundTrip(t *testing.T) { owner, repositories, accountID, characterID := abisTestOwner(t) ctx := context.Background() now := time.Date(2026, 10, 12, 12, 0, 0, 0, time.UTC) policies, err := abisEntryPolicies(abisDungeonSpecs[0], 0) // 4906, account 5 / character 2 if err != nil { t.Fatal(err) } // 第一次提交:账号与角色各 +1。 snapshots, err := owner.Commit(ctx, characterID, policies, "abis-test-receipt-1", now) if err != nil { t.Fatalf("first commit: %v", err) } for _, s := range snapshots { t.Logf("policy=%s scope=%d used=%d limit=%d remaining=%d", s.Policy.Key, s.Policy.Scope, s.Used, s.EffectiveLimit, s.Remaining) } // 读回账号记录,确认账号侧 used=1 真的写进了 Metadata。 account, found, err := repositories.Account.Load(ctx, accountID) if err != nil || !found { t.Fatalf("load account: found=%v err=%v", found, err) } foundKey := "" for key, value := range account.Metadata { if len(key) > 0 && value != "" { foundKey += "|" + key + "=" + value } } t.Logf("account metadata entries: %d %s", len(account.Metadata), foundKey) // 同一回执重复提交必须幂等:不重复扣次。 replay, err := owner.Commit(ctx, characterID, policies, "abis-test-receipt-1", now) if err != nil { t.Fatalf("replay commit: %v", err) } for _, s := range replay { if !s.Replayed { t.Errorf("policy %s: replay not detected (used=%d)", s.Policy.Key, s.Used) } } // 第三次用新回执:used 必须变成 2。 third, err := owner.Commit(ctx, characterID, policies, "abis-test-receipt-2", now) if err != nil { t.Fatalf("third commit: %v", err) } for _, s := range third { if s.Policy.Scope == dnfdungeonadmission.ScopeAccount && s.Used != 2 { t.Errorf("account policy used=%d want=2", s.Used) } if s.Policy.Scope == dnfdungeonadmission.ScopeCharacter && s.Used != 2 { t.Errorf("character policy used=%d want=2", s.Used) } } // 第四次:角色上限 2 已满,必须被拒绝。 if _, err := owner.Commit(ctx, characterID, policies, "abis-test-receipt-3", now); err == nil { t.Errorf("expected character limit rejection at used=2 limit=2") } else { t.Logf("limit rejection as expected: %v", err) } }