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) } }