Skip to content
Closed
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
9 changes: 2 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ GOFLAGS :=
COVERFILE := coverage.out
TESTCOVERAGE_THRESHOLD := 71
GOLANGCI_LINT := golangci-lint
SPECVERSION := v0.23.0
SPECVERSION := v1.0.0-rc.0

.PHONY: all tidy fmtcheck fmt vet lint test testcov race coverage-check build install generate ci-local clean help

Expand Down Expand Up @@ -135,16 +135,11 @@ install:
@echo " > Installing module/binaries"
@go install ./...

update-schema:
@echo " > Pulling down Gemara CUE package"
@cue def github.com/gemaraproj/gemara@$(SPECVERSION) --outfile schema.cue --force

# Generate files from CUE schemas
# Generates Go types from the Gemara CUE package with stable and experimental variants
generate:
@echo " > Generating types from Gemara CUE package"
@cue exp gengotypes schema.cue
@mv cue_types_gen.go generated_types.go
@cue exp gengotypes --outfile generated_types.go github.com/gemaraproj/gemara@$(SPECVERSION)
@go run ./cmd/typestagger generated_types.go

genlocal:
Expand Down
4 changes: 2 additions & 2 deletions cmd/oscalexport/export/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestGuidance(t *testing.T) {
tempDir := t.TempDir()
mockYAML := `
mockYAML := `
metadata:
id: Test
title: Test
Expand Down Expand Up @@ -74,7 +74,7 @@ guidelines:
func TestCatalog(t *testing.T) {
tempDir := t.TempDir()

mockYAML := `
mockYAML := `
metadata:
id: Test
title: Test
Expand Down
5 changes: 2 additions & 3 deletions control_catalog_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ func (c *ControlCatalog) UnmarshalYAML(data []byte) error {
Groups []Group `yaml:"groups,omitempty"`
Families []Group `yaml:"families,omitempty"`

Title string `yaml:"title"`
Title string `yaml:"title"`
Metadata Metadata `yaml:"metadata"`

Extends []ArtifactMapping `yaml:"extends,omitempty"`
Extends []ArtifactMapping `yaml:"extends,omitempty"`
Imports []MultiEntryMapping `yaml:"imports,omitempty"`

Controls []Control `yaml:"controls,omitempty"`
Expand All @@ -38,4 +38,3 @@ func (c *ControlCatalog) UnmarshalYAML(data []byte) error {

return nil
}

70 changes: 63 additions & 7 deletions enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ type RiskAppetite int
// ModType defines the type of modification to the assessment requirement.
type ModType int

// ResultType defines the nature of an audit result
type ResultType int

// EvidenceType categorizes the kind of evidence referenced in an audit
type EvidenceType string

const (
NotRun Result = iota
Passed
Expand Down Expand Up @@ -127,7 +133,8 @@ const (
)

const (
DispositionEnforced Disposition = iota
DispositionUndetermined Disposition = iota
DispositionEnforced
DispositionTolerated
DispositionClear
)
Expand Down Expand Up @@ -161,6 +168,13 @@ const (
ModOverride
)

const (
ResultObservation ResultType = iota
ResultStrength
ResultFinding
ResultGap
)

var (
toString = map[Result]string{
NotRun: "Not Run",
Expand Down Expand Up @@ -307,15 +321,17 @@ var (
}

dispositionToString = map[Disposition]string{
DispositionEnforced: "Enforced",
DispositionTolerated: "Tolerated",
DispositionClear: "Clear",
DispositionUndetermined: "Undetermined",
DispositionEnforced: "Enforced",
DispositionTolerated: "Tolerated",
DispositionClear: "Clear",
}

stringToDisposition = map[string]Disposition{
"Enforced": DispositionEnforced,
"Tolerated": DispositionTolerated,
"Clear": DispositionClear,
"Undetermined": DispositionUndetermined,
"Enforced": DispositionEnforced,
"Tolerated": DispositionTolerated,
"Clear": DispositionClear,
}

severityToString = map[Severity]string{
Expand Down Expand Up @@ -375,6 +391,20 @@ var (
"Replace": ModReplace,
"Override": ModOverride,
}

resultTypeToString = map[ResultType]string{
ResultObservation: "Observation",
ResultStrength: "Strength",
ResultFinding: "Finding",
ResultGap: "Gap",
}

stringToResultType = map[string]ResultType{
"Observation": ResultObservation,
"Strength": ResultStrength,
"Finding": ResultFinding,
"Gap": ResultGap,
}
)

// enumStringer is used by marshal helpers. Implemented by all string-backed enums.
Expand Down Expand Up @@ -802,6 +832,32 @@ func (m *ModType) UnmarshalJSON(data []byte) error {
return unmarshalJSONEnum(data, stringToModType, "ModType", m)
}

func (r ResultType) String() string {
if r, ok := resultTypeToString[r]; ok {
return r
}
return fmt.Sprintf("ResultType(%d)", r)
}
func (r ResultType) MarshalYAML() (interface{}, error) { return marshalYAMLString(r) }

func (r ResultType) MarshalJSON() ([]byte, error) { return marshalJSONString(r) }

func (r *ResultType) UnmarshalYAML(data []byte) error {
return unmarshalYAMLEnum(data, stringToResultType, "ResultType", r)
}

func (r *ResultType) UnmarshalJSON(data []byte) error {
return unmarshalJSONEnum(data, stringToResultType, "ResultType", r)
}

// ToArtifactType converts an EvidenceType to the corresponding ArtifactType.
func (e EvidenceType) ToArtifactType() (ArtifactType, error) {
if at, ok := stringToArtifactType[string(e)]; ok {
return at, nil
}
return 0, unknownEnumStringError("ArtifactType", string(e), stringToArtifactType)
}

// UpdateAggregateResult compares the current result with the new result and returns the most severe of the two.
func UpdateAggregateResult(previous Result, new Result) Result {
if new == NotRun {
Expand Down
135 changes: 127 additions & 8 deletions enums_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gemara

import (
"encoding/json"
"strings"
"testing"
)
Expand Down Expand Up @@ -364,6 +365,48 @@ func TestEntityTypeString(t *testing.T) {
}
}

func TestResultStringUnknownValue(t *testing.T) {
// Out-of-range or unknown int should not return empty string
const unknown Result = 99
got := unknown.String()
if got == "" {
t.Error("String() for unknown Result should not return empty string")
}
if !strings.Contains(got, "99") {
t.Errorf("String() for unknown Result should include numeric value, got %q", got)
}
}

func TestResultTypeStringUnknownValue(t *testing.T) {
const unknown ResultType = 99
got := unknown.String()
if got == "" {
t.Error("String() for unknown ResultType should not return empty string")
}
if !strings.Contains(got, "99") {
t.Errorf("String() for unknown ResultType should include numeric value, got %q", got)
}
}

func TestResultTypeString(t *testing.T) {
tests := []struct {
v ResultType
expected string
}{
{ResultObservation, "Observation"},
{ResultStrength, "Strength"},
{ResultFinding, "Finding"},
{ResultGap, "Gap"},
}
for _, tt := range tests {
t.Run(tt.expected, func(t *testing.T) {
if got := tt.v.String(); got != tt.expected {
t.Errorf("String() = %q, want %q", got, tt.expected)
}
})
}
}

func TestLifecycleMarshalUnmarshalJSON(t *testing.T) {
var l Lifecycle
if err := l.UnmarshalJSON([]byte(`"Draft"`)); err != nil {
Expand Down Expand Up @@ -415,14 +458,90 @@ func TestRelationshipTypeUnmarshalJSONInvalid(t *testing.T) {
}
}

func TestResultStringUnknownValue(t *testing.T) {
// Out-of-range or unknown int should not return empty string
const unknown Result = 99
got := unknown.String()
if got == "" {
t.Error("String() for unknown Result should not return empty string")
func TestResultTypeMarshalUnmarshalJSON(t *testing.T) {
tests := []struct {
value ResultType
jsonRepr string
}{
{ResultObservation, `"Observation"`},
{ResultStrength, `"Strength"`},
{ResultFinding, `"Finding"`},
{ResultGap, `"Gap"`},
}
if !strings.Contains(got, "99") {
t.Errorf("String() for unknown Result should include numeric value, got %q", got)
for _, tt := range tests {
t.Run(tt.jsonRepr, func(t *testing.T) {
out, err := tt.value.MarshalJSON()
if err != nil {
t.Fatalf("MarshalJSON: %v", err)
}
if string(out) != tt.jsonRepr {
t.Errorf("MarshalJSON = %s, want %s", out, tt.jsonRepr)
}

var got ResultType
if err := got.UnmarshalJSON(out); err != nil {
t.Fatalf("UnmarshalJSON: %v", err)
}
if got != tt.value {
t.Errorf("UnmarshalJSON round-trip: got %v, want %v", got, tt.value)
}
})
}
}

func TestEvidenceTypeToArtifactType(t *testing.T) {
tests := []struct {
name string
ev EvidenceType
expected ArtifactType
wantErr bool
}{
{"ControlCatalog", EvidenceType("ControlCatalog"), ControlCatalogArtifact, false},
{"EvaluationLog", EvidenceType("EvaluationLog"), EvaluationLogArtifact, false},
{"GuidanceCatalog", EvidenceType("GuidanceCatalog"), GuidanceCatalogArtifact, false},
{"MappingDocument", EvidenceType("MappingDocument"), MappingDocumentArtifact, false},
{"Policy", EvidenceType("Policy"), PolicyArtifact, false},
{"ThreatCatalog", EvidenceType("ThreatCatalog"), ThreatCatalogArtifact, false},
{"VectorCatalog", EvidenceType("VectorCatalog"), VectorCatalogArtifact, false},
{"RiskCatalog", EvidenceType("RiskCatalog"), RiskCatalogArtifact, false},
{"invalid value", EvidenceType("not-an-artifact"), 0, true},
{"empty string", EvidenceType(""), 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.ev.ToArtifactType()
if (err != nil) != tt.wantErr {
t.Errorf("ToArtifactType() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.expected {
t.Errorf("ToArtifactType() = %v, want %v", got, tt.expected)
}
})
}
}

func TestEvidenceTypeJSONRoundTrip(t *testing.T) {
type wrapper struct {
Type EvidenceType `json:"type"`
}

tests := []string{"document", "interview", "automated-scan", "custom-value"}
for _, val := range tests {
t.Run(val, func(t *testing.T) {
input := wrapper{Type: EvidenceType(val)}
data, err := json.Marshal(input)
if err != nil {
t.Fatalf("Marshal: %v", err)
}

var got wrapper
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got.Type != input.Type {
t.Errorf("round-trip: got %q, want %q", got.Type, input.Type)
}
})
}
}
Loading