package services import ( "bytes" "context" "os" "strings" "testing" "github.com/google/uuid" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" ) // TestResolveOrgSheets_LiveSchemaSnapshot probes the live paliad schema // the way the backup runner does at the start of every run, then asserts // that every spec the registry declares either keeps all its ORDER BY // columns or — if any are missing — composes a fallback SELECT that the // DB can still execute. Catches the m/paliad#140 class of bug // (hardcoded ORDER BY against a renamed column) before deploy. // // Skipped when TEST_DATABASE_URL is unset. Read-only: opens a // REPEATABLE READ tx, never writes. func TestResolveOrgSheets_LiveSchemaSnapshot(t *testing.T) { url := os.Getenv("TEST_DATABASE_URL") if url == "" { t.Skip("TEST_DATABASE_URL not set — skipping live DB test") } pool, err := sqlx.Connect("postgres", url) if err != nil { t.Fatalf("connect: %v", err) } defer pool.Close() ctx := context.Background() specs := orgSheetSpecs() sheets, err := resolveOrgSheets(ctx, pool, specs) if err != nil { t.Fatalf("resolveOrgSheets: %v", err) } if len(sheets) != len(specs) { t.Fatalf("resolved %d sheets, want %d", len(sheets), len(specs)) } // Each resolved SELECT must run cleanly against the live schema. // We LIMIT 1 inside a sub-SELECT so we don't materialise the full // table (some are large) but still exercise the ORDER BY clause. for _, sq := range sheets { wrapped := `SELECT * FROM (` + sq.SQL + `) _wrap LIMIT 1` if _, err := pool.QueryxContext(ctx, wrapped, sq.Args...); err != nil { t.Errorf("sheet %q SQL failed: %v\nSQL: %s", sq.SheetName, err, sq.SQL) } } } // TestWriteOrg_LiveSmoke runs the full ExportService.WriteOrg pipeline // against a real DB: schema probe, REPEATABLE READ tx, every sheet // query, xlsx + json + per-sheet CSV assembly, outer zip framing. // Discards the bytes — this is a "does it crash" smoke, the bug class // it catches is exactly the one from m/paliad#140 (hardcoded ORDER BY // against a missing column). // // Skipped when TEST_DATABASE_URL is unset. func TestWriteOrg_LiveSmoke(t *testing.T) { url := os.Getenv("TEST_DATABASE_URL") if url == "" { t.Skip("TEST_DATABASE_URL not set — skipping live DB test") } pool, err := sqlx.Connect("postgres", url) if err != nil { t.Fatalf("connect: %v", err) } defer pool.Close() svc := NewExportService(pool, "test-firm") var buf bytes.Buffer meta, err := svc.WriteOrg(context.Background(), &buf, ExportSpec{ ActorID: uuid.New(), ActorEmail: "backup-smoke@test.local", ActorLabel: "Backup Smoke", }) if err != nil { t.Fatalf("WriteOrg: %v", err) } if buf.Len() == 0 { t.Fatalf("WriteOrg wrote no bytes") } // Spot-check meta fills. if meta.Scope != ExportScopeOrg { t.Errorf("meta.Scope = %q, want %q", meta.Scope, ExportScopeOrg) } if len(meta.RowCounts) != len(orgSheetSpecs()) { t.Errorf("meta.RowCounts has %d entries, want %d (one per sheet)", len(meta.RowCounts), len(orgSheetSpecs())) } // The bytes are a zip; the first 4 bytes are PK\x03\x04 for a non-empty zip. if buf.Len() >= 4 && !strings.HasPrefix(buf.String()[:4], "PK\x03\x04") { t.Errorf("bundle bytes don't look like a zip (first bytes: %x)", buf.Bytes()[:4]) } }