package main import ( "context" "database/sql" "errors" "strings" "testing" "time" "calllinesystem/server/internal/model" "gorm.io/driver/postgres" "gorm.io/gorm" ) type inertConnPool struct{} func (inertConnPool) PrepareContext(context.Context, string) (*sql.Stmt, error) { return nil, errors.New("unexpected prepare") } func (inertConnPool) ExecContext(context.Context, string, ...any) (sql.Result, error) { return nil, errors.New("unexpected exec") } func (inertConnPool) QueryContext(context.Context, string, ...any) (*sql.Rows, error) { return nil, errors.New("unexpected query") } func (inertConnPool) QueryRowContext(context.Context, string, ...any) *sql.Row { return &sql.Row{} } func TestResetProjectRejectsArchivedProjectBeforeAnyMutation(t *testing.T) { db, err := gorm.Open(postgres.New(postgres.Config{Conn: inertConnPool{}}), &gorm.Config{ DisableAutomaticPing: true, }) if err != nil { t.Fatal(err) } archivedAt := time.Date(2026, 8, 13, 8, 0, 0, 0, time.UTC) existing := model.Project{ ID: "11111111-1111-4111-8111-111111111111", Code: "DEMO", Name: "archived project", Status: model.ProjectEnded, ArchivedAt: &archivedAt, } if err := db.Callback().Query().Replace("gorm:query", func(tx *gorm.DB) { project, ok := tx.Statement.Dest.(*model.Project) if !ok { t.Fatalf("query destination = %T, want *model.Project", tx.Statement.Dest) } *project = existing tx.RowsAffected = 1 }); err != nil { t.Fatal(err) } mutationCount := 0 recordMutation := func(tx *gorm.DB) { mutationCount++ tx.AddError(errors.New("unexpected mutation")) } if err := db.Callback().Raw().Replace("gorm:raw", recordMutation); err != nil { t.Fatal(err) } if err := db.Callback().Update().Replace("gorm:update", recordMutation); err != nil { t.Fatal(err) } if err := db.Callback().Create().Replace("gorm:create", recordMutation); err != nil { t.Fatal(err) } if err := db.Callback().Delete().Replace("gorm:delete", recordMutation); err != nil { t.Fatal(err) } project, err := resetProject(db, projectSeed{ Code: "demo", Name: "replacement", Status: model.ProjectRunning, }, time.Date(2026, 8, 13, 9, 0, 0, 0, time.UTC)) if err == nil || !strings.Contains(strings.ToLower(err.Error()), "archived") { t.Fatalf("resetProject error = %v, want explicit archived-project error", err) } if mutationCount != 0 { t.Fatalf("resetProject performed %d mutations after loading an archived project, want 0", mutationCount) } if project.ID != existing.ID || project.Name != existing.Name || project.Status != existing.Status || project.ArchivedAt != existing.ArchivedAt { t.Fatalf("returned project was modified: %#v", project) } }