Files
Agent-System/internal/auth/admin_reset_password_test.go
T
agent-bot ee1e53b510
CI / go-vet (pull_request) Failing after 10s
CI / backup-restore-drill (pull_request) Failing after 25s
CI / web (pull_request) Successful in 33s
fix(tests): per-package test databases + legitimate approval fixtures (issue #2 close)
Root causes of the remaining 5 CI PG failures (and the hidden flaky set):
1. All PG-integration packages shared ONE database under parallel package
   execution - migrations/seeds/truncates raced (nondeterministic failure
   sets, locally and in CI).
   Fix: RequireTestDatabaseURL/SkipIfNoTestDatabaseURL now derive a
   per-package database (<base>_<pkgHash>, advisory-lock-guarded
   CREATE); 9 raw-env straggler tests funnelled through the helpers;
   db-package tests call it unqualified (no import cycle).
2. Approval-chain fixtures were not production-shaped: freezeReviewSnapshot
   only honors head_snapshot_id, fake content hashes never froze a
   snapshot, so ApproverApprove rejected with 'approved requires frozen
   review_snapshot_id'. Errors were swallowed (_, _ =) hiding this.
   Fix (ownership-respecting, arch-enforced):
   - docrevision.SeedContentSnapshotForTest seeds the ledger tables it owns
   - docgov GovernanceEngine.FreezeHeadSnapshotForTest freezes head/review
     pointers on document_records (docgov-owned) and clears engine cache
   - all four integration fixtures use the pair; swallowed errors checked
3. Fixed-name fixtures collide once a package shares one DB (projects
   uq_projects_name_global): uniquified in projectinstance + server tests.
4. CAS/ctxmgr tests never bootstrapped migrations (relied on other packages
   having migrated the shared DB): explicit RunMigrationsOnceForTest.

Verified: full parallel short suite = 75 packages, 0 failures against an
isolated throwaway PG. CI skip list removed - short tests fully required.
2026-09-29 09:54:43 +00:00

104 lines
3.3 KiB
Go

package auth_test
import (
"strings"
"testing"
"chat-agent-system/internal/auth"
"chat-agent-system/internal/config"
)
func TestGenerateTempPassword(t *testing.T) {
pw, err := auth.GenerateTempPassword(16)
if err != nil {
t.Fatal(err)
}
if len(pw) != 16 {
t.Fatalf("len=%d", len(pw))
}
for _, r := range pw {
if !strings.ContainsRune("ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789", r) {
t.Fatalf("unexpected rune %q", r)
}
}
}
func TestCreateUserRejectsEmptyPasswordHash(t *testing.T) {
store := auth.NewMemoryStore(&config.Config{Env: "dev", AllowInMemory: true})
_, err := store.CreateUser(t.Context(), &auth.UserAccount{
Username: "nohash",
Name: "无哈希",
Status: auth.StatusActive,
RoleID: "role-guest",
})
if err == nil || !strings.Contains(err.Error(), "password hash required") {
t.Fatalf("expected password hash required, got %v", err)
}
}
func TestAdminResetPassword_Service(t *testing.T) {
cfg := &config.Config{Env: "dev", AllowInMemory: true, AdminUsername: "admin", AdminPassword: "admin123"}
store := auth.NewMemoryStore(cfg)
svc := auth.NewService(store, nil, cfg)
created, err := svc.CreateUserByAdmin(t.Context(), auth.CreateUserRequest{
Username: "svc_reset",
Name: "服务重置",
InitialPassword: "InitPass123",
RoleID: "role-engineer",
Position: "方案工程师",
})
if err != nil {
t.Fatal(err)
}
result, err := svc.AdminResetPassword(t.Context(), created.ID, 1, "req_test")
if err != nil {
t.Fatal(err)
}
if result.TempPassword == "" || !result.MustChangePassword {
t.Fatalf("bad result %+v", result)
}
_, err = svc.Login(t.Context(), "svc_reset", "InitPass123")
if err == nil {
t.Fatal("old password should fail")
}
session, err := svc.Login(t.Context(), "svc_reset", result.TempPassword)
if err != nil {
t.Fatal(err)
}
if !session.MustChangePassword {
t.Fatal("expected must_change_password")
}
}
func TestLoginMissingPasswordHash(t *testing.T) {
cfg := &config.Config{Env: "dev", AllowInMemory: true, AdminUsername: "admin", AdminPassword: "admin123"}
store := auth.NewMemoryStore(cfg)
// Bypass CreateUser guard by mutating seeded user hash after create via UpdateUserPassword empty is not allowed;
// plant via direct store map is not exported — use CreateUser with hash then clear via UpdateUserPassword with invalid.
// Instead: create with hash then overwrite stored hash through UpdateUserPassword with a sentinel that CheckPassword rejects,
// and separately verify empty-hash path by creating through internal seed is hard.
// Use Login against unknown user for 401 uniformity, and empty-hash via GetUserByID mutation on MemoryStore UpdateUserPassword
// with hash "" is allowed by UpdateUserPassword — plant empty hash that way.
created, err := store.CreateUser(t.Context(), &auth.UserAccount{
Username: "emptyhash",
Name: "空哈希",
PasswordHash: "placeholder",
Status: auth.StatusActive,
RoleID: "role-guest",
})
if err != nil {
t.Fatal(err)
}
if _, err := store.UpdateUserPassword(t.Context(), created.ID, "", false); err != nil {
t.Fatal(err)
}
svc := auth.NewService(store, nil, cfg)
_, err = svc.Login(t.Context(), "emptyhash", "anything")
if err == nil || err.Error() != "用户名或密码错误" {
t.Fatalf("expected unified credential error, got %v", err)
}
}