feat: complete remaining Go backend modules

This commit is contained in:
zn-admin committed 2026-08-14 00:02:43 +08:00
1 parent cea2751dc5
commit aef5a97165
145 files changed
+18376 -199

No files matched your search

@@ -2,13 +2,99 @@ package postgres
import (
"context"
"database/sql"
"errors"
"reflect"
"strings"
"testing"
"time"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/administration"
)
func TestApplyAccountUpdateUsesNarrowAtomicSQLAndDatabaseVersionIncrement(t *testing.T) {
now := time.Date(2026, 8, 13, 8, 0, 0, 0, time.UTC)
rows := &administrationRows{rows: [][]any{{
"user-1", "13800138000", "New Name", "user", "org-1", "active",
"new-password-hash", "new-password-salt", 3, now.Add(time.Hour), 9, now.Add(-time.Hour),
nil, now.Add(-24 * time.Hour), now,
}}}
querier := &administrationQuerier{rows: rows}
db := NewDatabase(Config{Backend: BackendPostgres}, querier)
name := "New Name"
got, err := db.ApplyAccountUpdate(context.Background(), "user-1", administration.AccountUpdate{
Actor: administration.Actor{ID: "super", Role: administration.RoleSuperAdmin},
DisplayName: &name,
UpdatedAt: now,
})
if err != nil {
t.Fatal(err)
}
if got.SessionVersion != 9 || got.PasswordHash != "new-password-hash" || got.FailedLoginCount != 3 {
t.Fatalf("updated account=%#v, want database-owned security fields preserved", got)
}
if querier.sql != ApplyAdministrationAccountUpdateSQL {
t.Fatalf("SQL=%q", querier.sql)
}
for _, forbidden := range []string{"phone=$", "created_at=$", "last_login_at=$", "legacy_subject=$"} {
if strings.Contains(querier.sql, forbidden) {
t.Fatalf("atomic PATCH must not overwrite unrelated column: SQL contains %q", forbidden)
}
}
for _, required := range []string{
"password_hash = CASE WHEN $10::boolean THEN $11::text ELSE password_hash END",
"failed_login_count = CASE WHEN $13::boolean THEN 0 ELSE failed_login_count END",
"session_version = session_version + CASE WHEN $14::boolean THEN 1 ELSE 0 END",
} {
if !strings.Contains(querier.sql, required) {
t.Fatalf("atomic PATCH SQL missing %q", required)
}
}
wantArgs := []any{
"user-1",
true, "New Name",
false, administration.Role(""),
false, nil,
false, administration.Status(""),
false, "", "",
false, false,
now,
administration.RoleSuperAdmin, "",
}
if !reflect.DeepEqual(querier.args, wantArgs) {
t.Fatalf("args=%#v want %#v", querier.args, wantArgs)
}
}
func TestApplyAccountUpdateAtomicallyResetsPasswordClearsLockAndIncrementsCurrentVersion(t *testing.T) {
now := time.Date(2026, 8, 13, 8, 0, 0, 0, time.UTC)
rows := &administrationRows{rows: [][]any{{
"user-1", "13800138000", "Name", "user", "org-1", "active",
"replacement-hash", "replacement-salt", 0, nil, 12, nil,
nil, now.Add(-24 * time.Hour), now,
}}}
querier := &administrationQuerier{rows: rows}
db := NewDatabase(Config{Backend: BackendPostgres}, querier)
got, err := db.ApplyAccountUpdate(context.Background(), "user-1", administration.AccountUpdate{
Actor: administration.Actor{Role: administration.RoleOrganizationAdmin, OrganizationID: "org-1"},
PasswordHash: &administration.PasswordHash{Hash: "replacement-hash", Salt: "replacement-salt"},
ClearLoginLock: true,
IncrementSessionVersion: true,
UpdatedAt: now,
})
if err != nil {
t.Fatal(err)
}
if got.SessionVersion != 12 || got.FailedLoginCount != 0 || got.LockedUntil != nil {
t.Fatalf("updated account=%#v", got)
}
if !reflect.DeepEqual(querier.args[9:14], []any{true, "replacement-hash", "replacement-salt", true, true}) {
t.Fatalf("security args=%#v", querier.args[9:14])
}
}
func TestAdministrationListAccountsUsesExplicitColumnsAndParameterizedFilters(t *testing.T) {
rows := &identityRows{}
querier := &identityQuerier{rows: rows}
@@ -31,7 +117,7 @@ func TestDeleteAccountArchivesAllOwnedDataAndIdentityInOneTransaction(t *testing
if err := db.DeleteAccount(context.Background(), "user-1", "archive:org-1"); err != nil {
t.Fatal(err)
}
wantSQL := []string{ArchiveAssetsSQL, ArchiveGenerationJobsSQL, ArchiveProjectsSQL, ArchiveImageTemplatesSQL, DeleteAdministrationAccountSQL}
wantSQL := []string{ArchiveAssetsSQL, ArchiveGenerationJobsSQL, ArchiveProjectsSQL, ArchiveImageTemplatesSQL, ArchiveUsageEventsSQL, DeleteAdministrationAccountSQL}
if !reflect.DeepEqual(tx.sql, wantSQL) {
t.Fatalf("SQL sequence=%#v want %#v", tx.sql, wantSQL)
}
@@ -94,3 +180,56 @@ func (t *administrationTransaction) Exec(_ context.Context, query string, args .
}
func (t *administrationTransaction) Commit(context.Context) error { t.commits++; return nil }
func (t *administrationTransaction) Rollback(context.Context) error { t.rollbacks++; return nil }
type administrationQuerier struct {
rows *administrationRows
sql string
args []any
}
func (q *administrationQuerier) Query(_ context.Context, query string, args ...any) (Rows, error) {
q.sql, q.args = query, args
return q.rows, nil
}
type administrationRows struct {
rows [][]any
idx int
}
func (r *administrationRows) Close() {}
func (r *administrationRows) Err() error { return nil }
func (r *administrationRows) Next() bool { return r.idx < len(r.rows) }
func (r *administrationRows) Scan(dest ...any) error {
if r.idx >= len(r.rows) || len(dest) != len(r.rows[r.idx]) {
return errors.New("invalid administration row scan")
}
row := r.rows[r.idx]
r.idx++
for i, target := range dest {
value := row[i]
switch target := target.(type) {
case *string:
*target = value.(string)
case *int:
*target = value.(int)
case *administration.Role:
*target = administration.Role(value.(string))
case *administration.Status:
*target = administration.Status(value.(string))
case *time.Time:
*target = value.(time.Time)
case *sql.NullString:
if value != nil {
*target = sql.NullString{String: value.(string), Valid: true}
}
case *sql.NullTime:
if value != nil {
*target = sql.NullTime{Time: value.(time.Time), Valid: true}
}
default:
return errors.New("unsupported administration scan target")
}
}
return nil
}