package services import ( "context" "os" "regexp" "testing" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "mgit.msbls.de/m/paliad/internal/db" ) // shapeRegex is the lowercase dot-separated form ratified by t-paliad-204 // and enforced at the DB layer by mig 096's paliad_proceeding_code_shape // CHECK constraint. Every active fristenrechner-category row must match. var shapeRegex = regexp.MustCompile(`^[a-z]+\.[a-z]+\.[a-z]+$`) // TestProceedingCodeShape walks every active fristenrechner-category row // in paliad.proceeding_types and asserts the `code` matches the // taxonomy regex. Catches future inserts that slip past the CHECK // constraint (e.g. via a manual psql edit on a staging snapshot) and // catches drift between this Go layer's stable code constants and the // DB. // // Mirrors the assertions in mig 096 §8 — same regex, same shape — so a // failure here pinpoints which row went off-shape without making a DB // trip first. // // Skipped when TEST_DATABASE_URL is unset, mirroring the pattern in // project_service_test.go. func TestProceedingCodeShape(t *testing.T) { url := os.Getenv("TEST_DATABASE_URL") if url == "" { t.Skip("TEST_DATABASE_URL not set — skipping live DB test") } if err := db.ApplyMigrations(url); err != nil { t.Fatalf("apply migrations: %v", err) } pool, err := sqlx.Connect("postgres", url) if err != nil { t.Fatalf("connect: %v", err) } defer pool.Close() ctx := context.Background() var rows []struct { ID int `db:"id"` Code string `db:"code"` } if err := pool.SelectContext(ctx, &rows, `SELECT id, code FROM paliad.proceeding_types WHERE category = 'fristenrechner' AND is_active = true ORDER BY id`); err != nil { t.Fatalf("load active fristenrechner rows: %v", err) } if len(rows) == 0 { t.Fatal("no active fristenrechner rows — mig 096 likely not applied") } for _, r := range rows { if !shapeRegex.MatchString(r.Code) { t.Errorf("proceeding_types[id=%d] code=%q does not match taxonomy shape %s", r.ID, r.Code, shapeRegex.String()) } } // Spot-check the stable code constants in proceeding_mapping.go all // resolve to live rows. Catches a constant being renamed without a // matching mig update. stable := []string{ CodeUPCInfringement, CodeUPCRevocation, CodeUPCCounterclaim, CodeUPCPreliminary, CodeUPCDamages, CodeUPCDiscovery, CodeUPCAppealMerits, CodeUPCAppealOrder, CodeUPCAppealCost, CodeDEInfringementLG, CodeDEInfringementOLG, CodeDEInfringementBGH, CodeDENullityBPatG, CodeDENullityBGH, CodeEPAGrant, CodeEPAOpposition, CodeEPAOppositionAppeal, CodeDPMAOpposition, CodeDPMAAppealBPatG, CodeDPMAAppealBGH, } for _, c := range stable { var hit int if err := pool.GetContext(ctx, &hit, `SELECT count(*) FROM paliad.proceeding_types WHERE code = $1 AND is_active = true`, c); err != nil { t.Fatalf("count rows for %s: %v", c, err) } if hit != 1 { t.Errorf("stable code constant %q matches %d active rows, want 1", c, hit) } } } // TestProceedingCodeShapeRegexStandalone exercises the regex without // hitting the DB so the shape rule is verified on every `go test ./...` // run (no skip when TEST_DATABASE_URL is unset). func TestProceedingCodeShapeRegexStandalone(t *testing.T) { good := []string{ "upc.inf.cfi", "upc.rev.cfi", "upc.ccr.cfi", "upc.apl.merits", "upc.apl.order", "upc.apl.cost", "de.inf.lg", "de.null.bgh", "epa.opp.opd", "epa.grant.exa", "dpma.opp.dpma", } for _, code := range good { if !shapeRegex.MatchString(code) { t.Errorf("good code %q rejected by shape regex", code) } } bad := []string{ "UPC_INF", // old uppercase "upc.inf", // missing third position "upc.inf.cfi.extra", // four positions "upc..cfi", // empty middle "upc-inf-cfi", // dashes "_archived_litigation", } for _, code := range bad { if shapeRegex.MatchString(code) { t.Errorf("bad code %q accepted by shape regex", code) } } }