Files
paliad/internal/services/proceeding_codes_shape_test.go
mAi 216abbfc98 feat(t-paliad-206): switch Go layer to lowercase dot-form proceeding codes
Sweeps internal/services + internal/handlers + internal/models to use
the new proceeding codes landed by mig 096. Stable Code* constants
live in internal/services/proceeding_mapping.go so a future rename
needs to touch one file.

Substantive changes:
- proceeding_mapping.go gains ResolveCounterclaimRouting() — the
  cascade resolver that routes upc.ccr.cfi (illustrative peer) back
  to upc.inf.cfi with with_ccr=true as default flag (design doc S1).
- deadline_search_service.go forum-bucket map updated; upc.ccr.cfi
  added to upc_cfi since it is a CFI peer.
- project_service.go CreateCounterclaim default lookup parameterised
  so the SQL string carries the constant, not a literal.
- proceeding_codes_shape_test.go: new file. Validates the shape
  regex standalone (always runs) and walks live DB rows asserting
  every active fristenrechner row matches the new shape + every
  stable Code* constant resolves to exactly one active row.

Comments and test fixtures throughout the Go tree updated to the
new shape. Tests pass under `go test ./internal/... -short`.
2026-05-18 12:13:24 +02:00

122 lines
3.9 KiB
Go

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)
}
}
}