package services import ( "context" "os" "testing" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "mgit.msbls.de/m/paliad/internal/db" lp "mgit.msbls.de/m/paliad/pkg/litigationplanner" ) // TestLookupEvents covers the multi-axis catalog query API from Slice // B2 (m/paliad#124 §18.2). Skipped when TEST_DATABASE_URL is unset, // mirroring TestCalculateRule. // // Cases: // - jurisdiction=UPC, depth=all-following → every active+published // UPC rule, anchor depth=1 for all (no parent_id outside the // filtered set lights up depth>1 because the entire UPC subset is // a single anchor cohort). // - proceeding_type_id (upc.inf.cfi) + party=defendant + depth=next // → defendant rules in upc.inf.cfi at depth=1 + direct children // of those at depth=2. // - unknown jurisdiction value → silently ignored, no filter applied. // - empty axes → all rules (no filter on any axis). func TestLookupEvents(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() rules := NewDeadlineRuleService(pool) catalog := &paliadCatalog{rules: rules} t.Run("jurisdiction=UPC, all-following returns the UPC corpus", func(t *testing.T) { matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "UPC", }, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents: %v", err) } if len(matches) == 0 { t.Fatal("expected non-empty UPC corpus") } // Every match must be a UPC rule. for _, m := range matches { if m.ProceedingType.Jurisdiction == nil || *m.ProceedingType.Jurisdiction != "UPC" { t.Errorf("non-UPC row leaked into UPC-axis query: code=%s jurisdiction=%v", m.ProceedingType.Code, m.ProceedingType.Jurisdiction) } if m.DepthFromAnchor < 1 { t.Errorf("depth=%d for rule %s, want >= 1", m.DepthFromAnchor, m.Rule.ID) } } }) t.Run("party=defendant scopes to defendant rules", func(t *testing.T) { matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "UPC", Party: "defendant", }, lp.EventLookupDepthNext) if err != nil { t.Fatalf("LookupEvents: %v", err) } if len(matches) == 0 { t.Fatal("expected at least one defendant rule across the UPC corpus") } // Anchor matches (depth=1) must be primary_party=defendant. // Depth=2 children appear under EventLookupDepthNext only as // expansion from anchors — they may carry any party. for _, m := range matches { if m.DepthFromAnchor != 1 { continue } if m.Rule.PrimaryParty == nil || *m.Rule.PrimaryParty != "defendant" { t.Errorf("anchor row %s (depth=1) is not defendant: %v", m.Rule.Name, m.Rule.PrimaryParty) } } }) t.Run("unknown jurisdiction value silently falls through", func(t *testing.T) { matchesAll, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{}, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents (all): %v", err) } matchesUnknown, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "XX-not-a-real-jurisdiction", }, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents (unknown): %v", err) } if len(matchesAll) != len(matchesUnknown) { t.Errorf("unknown jurisdiction should fall through to no-filter; got %d vs all-axes %d", len(matchesUnknown), len(matchesAll)) } }) t.Run("appeal_target=endentscheidung returns upc.apl merits rules", func(t *testing.T) { matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "UPC", AppealTarget: lp.AppealTargetEndentscheidung, }, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents: %v", err) } // Should hit the 7 rules under the unified upc.apl that // carry applies_to_target={endentscheidung} (Slice B1 mig 134). if len(matches) == 0 { t.Fatal("expected upc.apl endentscheidung rules after B1 mig") } for _, m := range matches { if m.DepthFromAnchor != 1 { continue // children of anchors may be from other targets } found := false for _, t := range m.Rule.AppliesToTarget { if t == lp.AppealTargetEndentscheidung { found = true break } } if !found { t.Errorf("anchor row %s missing endentscheidung target: %v", m.Rule.Name, m.Rule.AppliesToTarget) } if m.ProceedingType.Code != "upc.apl.unified" { t.Errorf("anchor row %s came from %s, want upc.apl.unified", m.Rule.Name, m.ProceedingType.Code) } } }) t.Run("appeal_target=schadensbemessung returns upc.apl merits rules (mig 138 backfill)", func(t *testing.T) { matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "UPC", AppealTarget: lp.AppealTargetSchadensbemessung, }, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents: %v", err) } // mig 138 (t-paliad-303, m/paliad#134) extends the 7 merits-track // rules under upc.apl.unified with applies_to_target ⊇ {schadensbemessung} // because R.224 is uniform across substantive R.118 decisions. if len(matches) == 0 { t.Fatal("expected upc.apl schadensbemessung rules after mig 138 backfill") } for _, m := range matches { if m.DepthFromAnchor != 1 { continue } found := false for _, t := range m.Rule.AppliesToTarget { if t == lp.AppealTargetSchadensbemessung { found = true break } } if !found { t.Errorf("anchor row %s missing schadensbemessung target: %v", m.Rule.Name, m.Rule.AppliesToTarget) } if m.ProceedingType.Code != "upc.apl.unified" { t.Errorf("anchor row %s came from %s, want upc.apl.unified", m.Rule.Name, m.ProceedingType.Code) } } }) t.Run("appeal_target=bucheinsicht returns upc.apl order rules (mig 138 backfill)", func(t *testing.T) { matches, err := catalog.LookupEvents(ctx, lp.EventLookupAxes{ Jurisdiction: "UPC", AppealTarget: lp.AppealTargetBucheinsicht, }, lp.EventLookupDepthAllFollowing) if err != nil { t.Fatalf("LookupEvents: %v", err) } // mig 138 (t-paliad-303, m/paliad#134) extends the 7 order-track // rules under upc.apl.unified with applies_to_target ⊇ {bucheinsicht} // because R.220.2 / R.224.2.b / R.235.2 / R.237 / R.238.2 are // uniform across the orders they appeal. if len(matches) == 0 { t.Fatal("expected upc.apl bucheinsicht rules after mig 138 backfill") } for _, m := range matches { if m.DepthFromAnchor != 1 { continue } found := false for _, t := range m.Rule.AppliesToTarget { if t == lp.AppealTargetBucheinsicht { found = true break } } if !found { t.Errorf("anchor row %s missing bucheinsicht target: %v", m.Rule.Name, m.Rule.AppliesToTarget) } if m.ProceedingType.Code != "upc.apl.unified" { t.Errorf("anchor row %s came from %s, want upc.apl.unified", m.Rule.Name, m.ProceedingType.Code) } } }) }