package uiaselector import ( "strings" "testing" "time" ) func fixedReportTime() time.Time { return time.Date(2026, 7, 9, 0, 0, 0, 0, time.UTC) } func loadN14Fixture(t *testing.T) *Node { t.Helper() root, err := LoadDumpFile("testdata/n12r-2026-07-09-uia-redacted.json") if err != nil { t.Fatalf("LoadDumpFile: %v", err) } return root } func TestRunCatalogReportMatchesAllN13Selectors(t *testing.T) { report := RunCatalogReport( "testdata/n12r-2026-07-09-uia-redacted.json", loadN14Fixture(t), DefaultCatalog(), fixedReportTime(), ) if !report.OK { t.Fatalf("report OK=false: %#v", report.Error) } if report.SourceDump != "testdata/n12r-2026-07-09-uia-redacted.json" { t.Fatalf("source_dump = %q", report.SourceDump) } if report.GeneratedAtUTC != "2026-07-09T00:00:00Z" { t.Fatalf("generated_at_utc = %q", report.GeneratedAtUTC) } if report.CatalogSize != 10 { t.Fatalf("catalog_size = %d, want 10", report.CatalogSize) } if report.MatchedCount != 10 { t.Fatalf("matched_count = %d, want 10", report.MatchedCount) } if report.UnmatchedCount != 0 { t.Fatalf("unmatched_count = %d, want 0", report.UnmatchedCount) } if report.AmbiguousCount != 0 { t.Fatalf("ambiguous_count = %d, want 0", report.AmbiguousCount) } if got := StrictExitCode(report); got != StrictExitOK { t.Fatalf("StrictExitCode = %d, want %d", got, StrictExitOK) } } func TestStrictModeClassifiesFailures(t *testing.T) { catalog := []Selector{ { ID: "missing_panel", Description: "missing structural test selector", AllowedUse: AllowedUseReadOnlyLocate, Required: Criteria{AutomationID: "does_not_exist", ControlType: "Pane", FrameworkID: "WinForm"}, StabilityScore: 80, Evidence: Evidence{CaptureID: "unit-test", UIAPath: "root/missing"}, }, } report := RunCatalogReport("fixture.json", loadN14Fixture(t), catalog, fixedReportTime()) if !report.OK { t.Fatalf("report OK=false: %#v", report.Error) } if report.UnmatchedCount != 1 { t.Fatalf("unmatched_count = %d, want 1", report.UnmatchedCount) } if got := StrictExitCode(report); got != StrictExitUnmatched { t.Fatalf("StrictExitCode = %d, want %d", got, StrictExitUnmatched) } } func TestReportRejectsNonReadOnlyCatalogEntries(t *testing.T) { catalog := []Selector{ { ID: "bad_write_selector", Description: "invalid write selector", AllowedUse: "write", Required: Criteria{AutomationID: "frmMain", ControlType: "Window", FrameworkID: "WinForm"}, StabilityScore: 1, Evidence: Evidence{CaptureID: "unit-test", UIAPath: "root"}, }, } report := RunCatalogReport("fixture.json", loadN14Fixture(t), catalog, fixedReportTime()) if report.OK { t.Fatalf("report OK=true for invalid catalog") } if report.Error == nil || report.Error.Code != "INVALID_CATALOG" { t.Fatalf("report error = %#v, want INVALID_CATALOG", report.Error) } if got := StrictExitCode(report); got != StrictExitInvalidCatalog { t.Fatalf("StrictExitCode = %d, want %d", got, StrictExitInvalidCatalog) } } func TestReportDoesNotExposeVisibleText(t *testing.T) { report := RunCatalogReport( "testdata/n12r-2026-07-09-uia-redacted.json", loadN14Fixture(t), DefaultCatalog(), fixedReportTime(), ) body, err := RenderReportJSON(report) if err != nil { t.Fatalf("RenderReportJSON: %v", err) } jsonText := string(body) for _, forbidden := range []string{`"name"`, `"value"`, "国网甘肃省电力公司"} { if strings.Contains(jsonText, forbidden) { t.Fatalf("JSON report exposed forbidden text %q in %s", forbidden, jsonText) } } for _, required := range []string{`"has_name"`, `"name_length"`, `"selector_id"`, `"automation_id"`} { if !strings.Contains(jsonText, required) { t.Fatalf("JSON report missing required structural field %q in %s", required, jsonText) } } } func TestMarkdownReportDoesNotExposeVisibleText(t *testing.T) { report := RunCatalogReport( "testdata/n12r-2026-07-09-uia-redacted.json", loadN14Fixture(t), DefaultCatalog(), fixedReportTime(), ) markdown := RenderReportMarkdown(report) for _, forbidden := range []string{"国网甘肃省电力公司", "| name |", "| value |"} { if strings.Contains(markdown, forbidden) { t.Fatalf("Markdown report exposed forbidden text %q in %s", forbidden, markdown) } } for _, required := range []string{"# N14 UIA Selector Report", "## Summary", "automation_id", "control_type", "class_name", "has_name", "name_length"} { if !strings.Contains(markdown, required) { t.Fatalf("Markdown report missing %q in %s", required, markdown) } } } func TestStrictModeClassifiesAmbiguousMatches(t *testing.T) { root := &Node{ ControlType: "Window", AutomationID: "frmMain", FrameworkID: "WinForm", Children: []Node{ {ControlType: "Pane", AutomationID: "duplicate", FrameworkID: "WinForm"}, {ControlType: "Pane", AutomationID: "duplicate", FrameworkID: "WinForm"}, }, } catalog := []Selector{ { ID: "duplicate_panel", Description: "duplicate structural selector", AllowedUse: AllowedUseReadOnlyLocate, Required: Criteria{AutomationID: "duplicate", ControlType: "Pane", FrameworkID: "WinForm"}, StabilityScore: 80, Evidence: Evidence{CaptureID: "unit-test", UIAPath: "root/duplicate"}, }, } report := RunCatalogReport("synthetic-ambiguous.json", root, catalog, fixedReportTime()) if !report.OK { t.Fatalf("report OK=false: %#v", report.Error) } if report.CatalogSize != 1 { t.Fatalf("catalog_size = %d, want 1", report.CatalogSize) } if report.MatchedCount != 1 { t.Fatalf("matched_count = %d, want 1", report.MatchedCount) } if report.UnmatchedCount != 0 { t.Fatalf("unmatched_count = %d, want 0", report.UnmatchedCount) } if report.AmbiguousCount != 1 { t.Fatalf("ambiguous_count = %d, want 1", report.AmbiguousCount) } if len(report.Selectors) != 1 { t.Fatalf("len(selectors) = %d, want 1", len(report.Selectors)) } if got := report.Selectors[0].MatchCount; got != 2 { t.Fatalf("selector match_count = %d, want 2", got) } if got := StrictExitCode(report); got != StrictExitAmbiguous { t.Fatalf("StrictExitCode = %d, want %d", got, StrictExitAmbiguous) } }