From cb46d7d02fd58b01a4277c6687076358d245244d Mon Sep 17 00:00:00 2001 From: David Ahmann Date: Wed, 18 Feb 2026 11:43:58 -0500 Subject: [PATCH] Restore core/framework coverage gate with validation tests --- core/framework/framework.go | 4 + core/framework/framework_test.go | 124 +++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/core/framework/framework.go b/core/framework/framework.go index d4c2a9b..e664237 100644 --- a/core/framework/framework.go +++ b/core/framework/framework.go @@ -74,6 +74,10 @@ func Load(idOrFile string) (*Framework, error) { if err != nil { return nil, fmt.Errorf("load framework %s: %w", idOrFile, err) } + return parseFramework(idOrFile, raw) +} + +func parseFramework(idOrFile string, raw []byte) (*Framework, error) { var f Framework if err := yaml.Unmarshal(raw, &f); err != nil { return nil, err diff --git a/core/framework/framework_test.go b/core/framework/framework_test.go index 4c7ba80..ad43bb6 100644 --- a/core/framework/framework_test.go +++ b/core/framework/framework_test.go @@ -72,3 +72,127 @@ func TestFrameworkCopiesStayInSync(t *testing.T) { require.Equalf(t, string(repoRaw), string(coreRaw), "framework copy mismatch for %s", entry.Name()) } } + +func TestParseFrameworkBranches(t *testing.T) { + _, err := parseFramework("bad-yaml", []byte("framework: [")) + require.Error(t, err) + + _, err = parseFramework("missing-id", []byte(` +framework: + version: "1" + title: Missing ID +controls: + - id: c1 + title: Control + required_record_types: [decision] + required_fields: [record_id] + minimum_frequency: continuous +`)) + require.ErrorContains(t, err, "missing id") + + _, err = parseFramework("missing-controls", []byte(` +framework: + id: test + version: "1" + title: Missing Controls +controls: [] +`)) + require.ErrorContains(t, err, "has no controls") +} + +func TestValidateControlsErrors(t *testing.T) { + cases := []struct { + name string + in []Control + needle string + }{ + { + name: "missing id", + in: []Control{{ + Title: "Control", + RequiredRecordTypes: []string{"decision"}, + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + }}, + needle: "missing id", + }, + { + name: "missing title", + in: []Control{{ + ID: "c1", + RequiredRecordTypes: []string{"decision"}, + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + }}, + needle: "missing title", + }, + { + name: "missing required_record_types", + in: []Control{{ + ID: "c1", + Title: "Control", + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + }}, + needle: "missing required_record_types", + }, + { + name: "missing minimum_frequency", + in: []Control{{ + ID: "c1", + Title: "Control", + RequiredRecordTypes: []string{"decision"}, + RequiredFields: []string{"record_id"}, + }}, + needle: "missing minimum_frequency", + }, + { + name: "blank required_record_types entry", + in: []Control{{ + ID: "c1", + Title: "Control", + RequiredRecordTypes: []string{"decision", " "}, + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + }}, + needle: "blank required_record_types entry", + }, + { + name: "blank required_fields entry", + in: []Control{{ + ID: "c1", + Title: "Control", + RequiredRecordTypes: []string{"decision"}, + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id", ""}, + }}, + needle: "blank required_fields entry", + }, + { + name: "invalid child control", + in: []Control{{ + ID: "c1", + Title: "Control", + RequiredRecordTypes: []string{"decision"}, + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + Children: []Control{{ + ID: "child", + Title: "Child", + MinimumFrequency: "continuous", + RequiredFields: []string{"record_id"}, + }}, + }}, + needle: "missing required_record_types", + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := validateControls(tc.in, "controls") + require.Error(t, err) + require.ErrorContains(t, err, tc.needle) + }) + } +}