package services // Tests for the Slice 2 (project-subtree) sheet registry. Pure-function // shape tests — live-DB integration coverage of the SQL itself stays in // the existing query patterns the personal-scope tests already cover. import ( "strings" "testing" "time" "github.com/google/uuid" ) // TestProjectSheetQueries_RegistryShape pins the sheet inventory + the // design's §2 contract: every entity sheet binds rootID as $1, and the // approval_policies sheet ships with all three sources (project + // ancestor + partner_unit_default). func TestProjectSheetQueries_RegistryShape(t *testing.T) { rootID := uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb") qs := projectSheetQueries(rootID, false) wantSheets := []string{ "projects", "project_teams", "project_partner_units", "deadlines", "appointments", "parties", "notes", "documents", "project_events", "approval_requests", "approval_policies", "checklist_instances", "partner_units", "partner_unit_members", "users_referenced", "system_audit_log_subset", "ref__proceeding_types", "ref__event_types", "ref__event_categories", "ref__deadline_rules", "ref__deadline_concepts", "ref__courts", "ref__countries", "ref__holidays", } gotSheets := []string{} for _, q := range qs { gotSheets = append(gotSheets, q.SheetName) } if len(gotSheets) != len(wantSheets) { t.Fatalf("sheet count = %d, want %d (got %v)", len(gotSheets), len(wantSheets), gotSheets) } for i, want := range wantSheets { if gotSheets[i] != want { t.Errorf("sheet[%d] = %q, want %q", i, gotSheets[i], want) } } // Every NON-reference sheet binds rootID as $1. for _, q := range qs { if strings.HasPrefix(q.SheetName, "ref__") { if len(q.Args) != 0 { t.Errorf("ref sheet %q has %d args, want 0", q.SheetName, len(q.Args)) } continue } if len(q.Args) != 1 { t.Errorf("entity sheet %q has %d args, want 1", q.SheetName, len(q.Args)) continue } if got, ok := q.Args[0].(uuid.UUID); !ok || got != rootID { t.Errorf("entity sheet %q first arg = %v, want rootID %v", q.SheetName, q.Args[0], rootID) } } } // TestProjectSheetQueries_ApprovalPoliciesTripleSource verifies that the // approval_policies sheet's SQL carries all three source tags so an // importer can reconstruct the effective gate (Q4 lock-in). func TestProjectSheetQueries_ApprovalPoliciesTripleSource(t *testing.T) { qs := projectSheetQueries(uuid.New(), false) var found *sheetQuery for i := range qs { if qs[i].SheetName == "approval_policies" { found = &qs[i] break } } if found == nil { t.Fatal("approval_policies sheet missing from registry") } for _, src := range []string{ `'project'::text AS source`, `'ancestor'::text AS source`, `'partner_unit_default'::text AS source`, } { if !strings.Contains(found.SQL, src) { t.Errorf("approval_policies SQL missing %q tag — Q4 triple-source attribution broken.\nSQL:\n%s", src, found.SQL) } } } // TestProjectSheetQueries_DirectOnlyNarrowsSubtree pins that direct_only=true // produces a subtree subquery resolving to exactly the root (no LIKE-walk). func TestProjectSheetQueries_DirectOnlyNarrowsSubtree(t *testing.T) { subtreeAll := projectSubtreeProjectIDsSQL(false) subtreeRoot := projectSubtreeProjectIDsSQL(true) if !strings.Contains(subtreeAll, `LIKE r.path`) { t.Errorf("default subtree SQL missing path-LIKE descendant walk:\n%s", subtreeAll) } if strings.Contains(subtreeRoot, `LIKE`) { t.Errorf("direct_only subtree SQL still has LIKE walk — should be root-only:\n%s", subtreeRoot) } if !strings.Contains(subtreeRoot, `$1::uuid`) { t.Errorf("direct_only subtree SQL missing $1::uuid root reference:\n%s", subtreeRoot) } } // TestProjectSheetQueries_NoPersonalSidecars guards against an accidental // inclusion of personal sidecars (caldav config, views, pins, paliadin // turns) in the project-scope export. These are per-user, not per-project, // and don't belong in a matter handover. func TestProjectSheetQueries_NoPersonalSidecars(t *testing.T) { qs := projectSheetQueries(uuid.New(), false) for _, q := range qs { switch q.SheetName { case "my_caldav_config", "my_views", "my_pinned_projects", "my_card_layouts", "my_paliadin_turns", "me": t.Errorf("project-scope export must not include personal sidecar sheet %q", q.SheetName) } // Also defence-in-depth on the SQL: no SELECT from // user_caldav_config or paliadin_turns from project scope. if strings.Contains(q.SQL, "user_caldav_config") { t.Errorf("sheet %q SQL touches user_caldav_config — never in project scope", q.SheetName) } if strings.Contains(q.SQL, "paliadin_turns") { t.Errorf("sheet %q SQL touches paliadin_turns — never in project scope", q.SheetName) } } } // TestProjectSheetQueries_AttachedPartnerUnitsOnly pins that the // partner_units sheet is filtered to attached units only (not the full // org chart). func TestProjectSheetQueries_AttachedPartnerUnitsOnly(t *testing.T) { qs := projectSheetQueries(uuid.New(), false) for _, q := range qs { if q.SheetName != "partner_units" { continue } if !strings.Contains(q.SQL, "project_partner_units") { t.Errorf("partner_units sheet SQL must filter via project_partner_units (got attached-only requirement):\n%s", q.SQL) } return } t.Fatal("partner_units sheet missing from registry") } // TestShortUUIDSuffix_ReturnsLast8Hex pins the §3 filename disambiguator // shape — Q5 lock-in. func TestShortUUIDSuffix_ReturnsLast8Hex(t *testing.T) { cases := []struct { in uuid.UUID want string }{ {uuid.Nil, ""}, {uuid.MustParse("11111111-1111-1111-1111-aaaaaaaaaaaa"), "aaaaaaaaaaaa"}, {uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb"), "a89469e2cacb"}, } for _, c := range cases { got := shortUUIDSuffix(c.in) if got != c.want { t.Errorf("shortUUIDSuffix(%v) = %q, want %q", c.in, got, c.want) } } } // TestMetaToKeyValueRows_ProjectScopeRows verifies that project-scope // meta picks up scope_root_label + scope_root_path + direct_only rows // (so the __meta sheet carries Q6 lock-in details). func TestMetaToKeyValueRows_ProjectScopeRows(t *testing.T) { rootID := uuid.MustParse("61e3fb9e-29fb-44aa-867e-a89469e2cacb") m := ExportMeta{ SchemaVersion: 1, FirmName: "HLC", Scope: ExportScopeProject, ScopeRootID: &rootID, ScopeRootLabel: "Siemens AG", ScopeRootPath: "61e3fb9e_29fb_44aa_867e_a89469e2cacb", DirectOnly: false, GeneratedAt: time.Date(2026, 5, 20, 14, 23, 0, 0, time.UTC), RowCounts: map[string]int{}, } rows := metaToKeyValueRows(m) want := map[string]string{ "scope_root_label": "Siemens AG", "scope_root_path": "61e3fb9e_29fb_44aa_867e_a89469e2cacb", "direct_only": "FALSE", } seen := map[string]string{} for _, r := range rows { seen[r[0]] = r[1] } for k, v := range want { if seen[k] != v { t.Errorf("meta key %q = %q, want %q (full rows: %v)", k, seen[k], v, rows) } } }