Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
124 changes: 124 additions & 0 deletions core/framework/framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
Loading