From 84f64dc1365f50c9cd717da7846c174db27da86a Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Thu, 25 Jun 2026 22:35:14 +0200 Subject: [PATCH] topdata: fix global validation regressions --- internal/topdata/json_contract_test.go | 44 ++++++++++++++++++++++++++ internal/topdata/topdata.go | 36 +++++++++++++++++---- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/internal/topdata/json_contract_test.go b/internal/topdata/json_contract_test.go index c7f62e3..825dbe6 100644 --- a/internal/topdata/json_contract_test.go +++ b/internal/topdata/json_contract_test.go @@ -461,6 +461,50 @@ func TestValidateProjectRejectsInvalidAnyPresent(t *testing.T) { } } +func TestValidateProjectReportsUnsupportedGlobalInjectionKeysAndInvalidAnyPresent(t *testing.T) { + root := testProjectRoot(t) + writeCanonicalContractScaffold(t, root) + writeCanonicalSpellsDataset(t, root) + writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{ + "injections": [ + { + "row": {}, + "when_present": [], + "any_present": [] + } + ] +}`+"\n") + + report := ValidateProject(testProject(root)) + if !report.HasErrors() { + t.Fatalf("expected invalid global injection to fail validation, got:\n%s", diagnosticsText(report.Diagnostics)) + } + assertDiagnosticTextContainsAll(t, report.Diagnostics, + `global injection 0 contains unsupported key "when_present"`, + "any_present must contain at least one condition", + ) +} + +func TestValidateProjectValidatesGlobalColumnsOnce(t *testing.T) { + root := testProjectRoot(t) + writeCanonicalContractScaffold(t, root) + writeCanonicalSpellsDataset(t, root) + writeFile(t, filepath.Join(root, "topdata", "data", "spells", "global.json"), `{ + "columns": "ImpactScript", + "entries": {}, + "overrides": [] +}`+"\n") + + report := ValidateProject(testProject(root)) + if !report.HasErrors() { + t.Fatalf("expected invalid global columns file to fail validation, got:\n%s", diagnosticsText(report.Diagnostics)) + } + text := diagnosticsText(report.Diagnostics) + if got := strings.Count(text, "columns must be a JSON array when present"); got != 1 { + t.Fatalf("expected one global columns diagnostic, got %d:\n%s", got, text) + } +} + func TestValidateProjectValidatesEveryCanonicalModuleContainer(t *testing.T) { root := testProjectRoot(t) writeCanonicalContractScaffold(t, root) diff --git a/internal/topdata/topdata.go b/internal/topdata/topdata.go index 59e4032..8e96b39 100644 --- a/internal/topdata/topdata.go +++ b/internal/topdata/topdata.go @@ -990,16 +990,42 @@ func validateOverridesFileWithColumns(path string, obj map[string]any, report *V } } +func parseGlobalConditionGroupsWithoutKeyCheck(injection map[string]any) (globalConditionGroups, error) { + var groups globalConditionGroups + if raw, ok := injection["require_present"]; ok { + parsed, err := parseGlobalConditionList("require_present", raw, false) + if err != nil { + return globalConditionGroups{}, err + } + groups.RequirePresent = parsed + } + if raw, ok := injection["any_present"]; ok { + parsed, err := parseGlobalConditionList("any_present", raw, true) + if err != nil { + return globalConditionGroups{}, err + } + groups.AnyPresent = parsed + } + if raw, ok := injection["unless_present"]; ok { + parsed, err := parseGlobalConditionList("unless_present", raw, false) + if err != nil { + return globalConditionGroups{}, err + } + groups.UnlessPresent = parsed + } + return groups, nil +} + func validateGlobalJSONFile(path string, obj map[string]any, report *ValidationReport) { reportTopdataContractError(path, unsupportedObjectKeysError("global.json root", obj, globalRootKeys...), report) if _, ok := obj["columns"]; ok { validateColumnsFile(path, obj, report) } if _, ok := obj["entries"]; ok { - validateEntriesFile(path, obj, report) + validateEntriesFileWithColumns(path, obj, report, false) } if _, ok := obj["overrides"]; ok { - validateOverridesFile(path, obj, report) + validateOverridesFileWithColumns(path, obj, report, false) } if _, ok := obj["defaults"]; ok { validateGlobalDefaults(path, obj, report) @@ -1060,10 +1086,8 @@ func validateGlobalJSONFile(path string, obj map[string]any, report *ValidationR } else { rows = append(rows, row) } - if err == nil { - _, conditionErr := parseGlobalConditionGroups(injection) - reportTopdataContractError(path, conditionErr, report) - } + _, conditionErr := parseGlobalConditionGroupsWithoutKeyCheck(injection) + reportTopdataContractError(path, conditionErr, report) } validateRowCollection(path, obj, rows, report) }