package data import ( "context" "testing" "time" "kra/internal/biz" ) func errorString(value string) *string { return &value } func newErrorRecordRepos(t *testing.T) (*auditRecorderRepo, *auditQueryRepo) { t.Helper() db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared") if err != nil { t.Fatal(err) } if err = db.AutoMigrate(&errorRecordPO{}); err != nil { t.Fatal(err) } sqlDB, err := db.DB() if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = sqlDB.Close() }) data := &Data{gormDB: newReloadableDB(db, nil)} data.databaseReady.Store(true) return &auditRecorderRepo{data: data}, &auditQueryRepo{data: data} } func TestCreateErrorBeforeDatabaseInitializationIsNoop(t *testing.T) { db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared") if err != nil { t.Fatal(err) } sqlDB, err := db.DB() if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = sqlDB.Close() }) recorder := &auditRecorderRepo{data: &Data{gormDB: newReloadableDB(db, nil)}} form := "后端" if err = recorder.CreateError(context.Background(), &biz.ErrorRecord{Form: &form}); err != nil { t.Fatalf("uninitialized error write = %v, want nil", err) } } func TestCreateErrorPreservesNullableFieldsAndDefaultStatus(t *testing.T) { recorder, query := newErrorRecordRepos(t) createdAt := time.Date(2025, time.March, 4, 5, 6, 7, 0, time.UTC) form := "前端" if err := recorder.CreateError(context.Background(), &biz.ErrorRecord{ ID: 41, CreatedAt: createdAt, Form: &form, Level: "error", }); err != nil { t.Fatal(err) } got, err := query.FindError(context.Background(), 41) if err != nil { t.Fatal(err) } if got.Form == nil || *got.Form != form { t.Fatalf("form = %#v, want %q", got.Form, form) } if got.Info != nil || got.Solution != nil { t.Fatalf("nullable fields = (%#v, %#v), want nil", got.Info, got.Solution) } if got.Status != "未处理" { t.Fatalf("status = %q, want 未处理", got.Status) } if !got.CreatedAt.Equal(createdAt) { t.Fatalf("created_at = %v, want %v", got.CreatedAt, createdAt) } } func TestUpdateErrorDistinguishesOmittedAndExplicitEmptyFields(t *testing.T) { recorder, query := newErrorRecordRepos(t) form, info, solution := "后端", "错误内容", "解决方案" if err := recorder.CreateError(context.Background(), &biz.ErrorRecord{ Form: &form, Info: &info, Solution: &solution, Level: "error", RequestID: "request-old", TraceID: "trace-old", Status: "处理中", }); err != nil { t.Fatal(err) } items, _, err := query.ListErrors(context.Background(), 1, 10, nil) if err != nil || len(items) != 1 { t.Fatalf("list before update = (%d, %v)", len(items), err) } id := items[0].ID newForm := "服务端" if err = query.UpdateError(context.Background(), &biz.ErrorRecord{ID: id, Form: &newForm}); err != nil { t.Fatal(err) } got, err := query.FindError(context.Background(), id) if err != nil { t.Fatal(err) } if got.Info == nil || *got.Info != info || got.Solution == nil || *got.Solution != solution { t.Fatalf("omitted fields changed: info=%#v solution=%#v", got.Info, got.Solution) } changedCreatedAt := time.Date(2024, time.January, 2, 3, 4, 5, 0, time.UTC) if err = query.UpdateError(context.Background(), &biz.ErrorRecord{ ID: id, CreatedAt: changedCreatedAt, Form: errorString(""), Info: errorString(""), Solution: errorString(""), RequestID: "request-new", TraceID: "trace-new", }); err != nil { t.Fatal(err) } got, err = query.FindError(context.Background(), id) if err != nil { t.Fatal(err) } if got.Form == nil || *got.Form != "" || got.Info == nil || *got.Info != "" || got.Solution == nil || *got.Solution != "" { t.Fatalf("explicit empty fields were not persisted: form=%#v info=%#v solution=%#v", got.Form, got.Info, got.Solution) } if got.Level != "error" || got.Status != "处理中" { t.Fatalf("zero-value fields changed: level=%q status=%q", got.Level, got.Status) } if got.RequestID != "request-new" || got.TraceID != "trace-new" { t.Fatalf("trace fields = (%q, %q), want updated values", got.RequestID, got.TraceID) } if !got.CreatedAt.Equal(changedCreatedAt) { t.Fatalf("created_at = %v, want %v", got.CreatedAt, changedCreatedAt) } }