fix(backend): harden postgres id default repair and CI coverage

Run Postgres id-sequence repair on every startup migration and add contract coverage plus a GitHub Actions Postgres job to catch schema-drift regressions before release.
This commit is contained in:
sagit
2026-02-14 11:02:46 +00:00
parent 6abe3e7713
commit 3f80278dd4
4 changed files with 237 additions and 5 deletions
@@ -1307,6 +1307,8 @@ func bootstrapSchema(db *store.DB, schemaSQL, seedSQL string) error {
const currentSchemaVersion = 2
var ensurePostgresIDDefaultsFn = ensurePostgresIDDefaults
func getSchemaVersion(db *store.DB) int {
_, _ = db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 0)`)
var v int
@@ -1327,6 +1329,11 @@ func migrateSchema(db *store.DB) error {
}
ver := getSchemaVersion(db)
if db.Dialect() == store.DialectPostgres {
if err := ensurePostgresIDDefaultsFn(db); err != nil {
return err
}
}
if ver >= currentSchemaVersion {
return nil
}
@@ -1396,11 +1403,6 @@ func migrateSchema(db *store.DB) error {
return err
}
if db.Dialect() == store.DialectPostgres {
if err := ensurePostgresIDDefaults(db); err != nil {
return err
}
}
setSchemaVersion(db, currentSchemaVersion)
return nil
}
@@ -0,0 +1,78 @@
package sqlite
import (
"database/sql"
"errors"
"testing"
"go-backend/internal/store"
_ "modernc.org/sqlite"
)
func TestMigrateSchemaRunsPostgresIDRepairEvenAtCurrentVersion(t *testing.T) {
raw, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
t.Cleanup(func() {
_ = raw.Close()
})
db := store.Wrap(raw, store.DialectPostgres)
if _, err := db.Exec(`CREATE TABLE schema_version (version INTEGER NOT NULL DEFAULT 0)`); err != nil {
t.Fatalf("create schema_version: %v", err)
}
if _, err := db.Exec(`INSERT INTO schema_version(version) VALUES(?)`, currentSchemaVersion); err != nil {
t.Fatalf("seed schema_version: %v", err)
}
called := 0
original := ensurePostgresIDDefaultsFn
ensurePostgresIDDefaultsFn = func(db *store.DB) error {
called++
return nil
}
t.Cleanup(func() {
ensurePostgresIDDefaultsFn = original
})
if err := migrateSchema(db); err != nil {
t.Fatalf("migrateSchema: %v", err)
}
if called != 1 {
t.Fatalf("expected postgres id repair to run once, got %d", called)
}
}
func TestMigrateSchemaReturnsPostgresIDRepairError(t *testing.T) {
raw, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
t.Cleanup(func() {
_ = raw.Close()
})
db := store.Wrap(raw, store.DialectPostgres)
if _, err := db.Exec(`CREATE TABLE schema_version (version INTEGER NOT NULL DEFAULT 0)`); err != nil {
t.Fatalf("create schema_version: %v", err)
}
if _, err := db.Exec(`INSERT INTO schema_version(version) VALUES(?)`, currentSchemaVersion); err != nil {
t.Fatalf("seed schema_version: %v", err)
}
wantErr := errors.New("repair failed")
original := ensurePostgresIDDefaultsFn
ensurePostgresIDDefaultsFn = func(db *store.DB) error {
return wantErr
}
t.Cleanup(func() {
ensurePostgresIDDefaultsFn = original
})
err = migrateSchema(db)
if !errors.Is(err, wantErr) {
t.Fatalf("expected error %v, got %v", wantErr, err)
}
}