From 5c4529677c151cc48a90a12f42763631f5988fc6 Mon Sep 17 00:00:00 2001 From: "James C. Scott" Date: Fri, 19 Aug 2016 10:27:41 -0400 Subject: [PATCH 1/3] Move standards and control to common interface --- commands/diff/inventory.go | 10 +- commands/docs/gitbook/gitbook.go | 2 +- commands/docs/gitbook/gitbookCertification.go | 6 +- .../docs/gitbook/gitbookCertification_test.go | 4 +- commands/docs/gitbook/gitbookSummaries.go | 6 +- lib/certifications.go | 13 +- lib/certifications_test.go | 105 ---------------- lib/common/control.go | 12 ++ lib/common/errors.go | 9 +- lib/common/standard.go | 18 +++ lib/standards.go | 83 +++++------- lib/standards/standard.go | 22 ++++ lib/standards/standard_test.go | 62 +++++++++ lib/standards/versions/1_0_0/standard.go | 60 +++++++++ lib/standards/versions/1_0_0/standard_test.go | 78 ++++++++++++ lib/standards_test.go | 118 ------------------ lib/workspace.go | 13 +- lib/workspace_test.go | 4 +- 18 files changed, 314 insertions(+), 311 deletions(-) delete mode 100644 lib/certifications_test.go create mode 100644 lib/common/control.go create mode 100644 lib/common/standard.go create mode 100644 lib/standards/standard.go create mode 100644 lib/standards/standard_test.go create mode 100644 lib/standards/versions/1_0_0/standard.go create mode 100644 lib/standards/versions/1_0_0/standard_test.go delete mode 100644 lib/standards_test.go diff --git a/commands/diff/inventory.go b/commands/diff/inventory.go index 03490930..809bc779 100644 --- a/commands/diff/inventory.go +++ b/commands/diff/inventory.go @@ -10,15 +10,15 @@ import ( // Inventory maintains the inventory of all the controls within a given workspace. type Inventory struct { *lib.LocalWorkspace - masterControlList map[string]lib.Control + masterControlList map[string]common.Control actualSatisfiedControls map[string]common.Satisfies - MissingControlList map[string]lib.Control + MissingControlList map[string]common.Control } // retrieveMasterControlsList will gather the list of controls needed for a given certification. func (i *Inventory) retrieveMasterControlsList() { for standardKey, standard := range i.Certification.Standards { - for controlKey, control := range standard.Controls { + for controlKey, control := range standard.GetControls() { key := standardAndControlString(standardKey, controlKey) if _, exists := i.masterControlList[key]; !exists { i.masterControlList[key] = control @@ -73,9 +73,9 @@ func ComputeGapAnalysis(config Config) (Inventory, []error) { workspace, _ := lib.LoadData(config.OpencontrolDir, certificationPath) i := Inventory{ LocalWorkspace: workspace, - masterControlList: make(map[string]lib.Control), + masterControlList: make(map[string]common.Control), actualSatisfiedControls: make(map[string]common.Satisfies), - MissingControlList: make(map[string]lib.Control), + MissingControlList: make(map[string]common.Control), } if i.Certification == nil || i.Components == nil { return Inventory{}, []error{fmt.Errorf("Unable to load data in %s for certification %s", config.OpencontrolDir, config.Certification)} diff --git a/commands/docs/gitbook/gitbook.go b/commands/docs/gitbook/gitbook.go index 83b16e4c..41df51f4 100644 --- a/commands/docs/gitbook/gitbook.go +++ b/commands/docs/gitbook/gitbook.go @@ -37,7 +37,7 @@ type ComponentGitbook struct { // ControlGitbook struct is an extension of models.Control that adds // an exportPath type ControlGitbook struct { - *lib.Control + common.Control exportPath string standardKey string controlKey string diff --git a/commands/docs/gitbook/gitbookCertification.go b/commands/docs/gitbook/gitbookCertification.go index 9b85b453..0e2087bb 100644 --- a/commands/docs/gitbook/gitbookCertification.go +++ b/commands/docs/gitbook/gitbookCertification.go @@ -88,7 +88,7 @@ func (openControl *OpenControlGitBook) getControlOrigin(text string, controlOrig func (openControl *OpenControlGitBook) exportControl(control *ControlGitbook) (string, string) { key := replaceParentheses(fmt.Sprintf("%s-%s", control.standardKey, control.controlKey)) - text := fmt.Sprintf("#%s\n##%s\n", key, control.Name) + text := fmt.Sprintf("#%s\n##%s\n", key, control.GetName()) openControl.Justifications.GetAndApply(control.standardKey, control.controlKey, func(selectJustifications lib.Verifications) { // In the case that no information was found period for the standard and control if len(selectJustifications) == 0 { @@ -117,8 +117,8 @@ func (openControl *OpenControlGitBook) exportStandards() { standardsExportPath := filepath.Join(openControl.exportPath, "standards") openControl.FSUtil.Mkdirs(standardsExportPath) openControl.Certification.GetSortedData(func(standardKey string, controlKey string) { - control := openControl.Standards.Get(standardKey).Controls[controlKey] - controlPath, controlText := openControl.exportControl(&ControlGitbook{&control, standardsExportPath, standardKey, controlKey}) + control := openControl.Standards.Get(standardKey).GetControl(controlKey) + controlPath, controlText := openControl.exportControl(&ControlGitbook{control, standardsExportPath, standardKey, controlKey}) ioutil.WriteFile(controlPath, []byte(controlText), 0700) }) } diff --git a/commands/docs/gitbook/gitbookCertification_test.go b/commands/docs/gitbook/gitbookCertification_test.go index ef76249a..475b4015 100644 --- a/commands/docs/gitbook/gitbookCertification_test.go +++ b/commands/docs/gitbook/gitbookCertification_test.go @@ -66,8 +66,8 @@ func TestExportControl(t *testing.T) { dir, fs.OSUtil{}, } - control := openControl.Standards.Get(example.standardKey).Controls[example.controlKey] - actualPath, actualText := openControl.exportControl(&ControlGitbook{&control, dir, example.standardKey, example.controlKey}) + control := openControl.Standards.Get(example.standardKey).GetControl(example.controlKey) + actualPath, actualText := openControl.exportControl(&ControlGitbook{control, dir, example.standardKey, example.controlKey}) expectedPath := filepath.Join(dir, example.expectedPath) // Verify the expected export path is the same as the actual export path if expectedPath != actualPath { diff --git a/commands/docs/gitbook/gitbookSummaries.go b/commands/docs/gitbook/gitbookSummaries.go index 92dc0c5c..4e73588a 100644 --- a/commands/docs/gitbook/gitbookSummaries.go +++ b/commands/docs/gitbook/gitbookSummaries.go @@ -23,9 +23,9 @@ func (openControl *OpenControlGitBook) buildStandardsSummaries() (string, *map[s openControl.Certification.GetSortedData(func(standardKey string, controlKey string) { componentLink := replaceParentheses(standardKey + "-" + controlKey + ".md") - control := openControl.Standards.Get(standardKey).Controls[controlKey] - controlFamily := control.Family - controlName := control.Name + control := openControl.Standards.Get(standardKey).GetControl(controlKey) + controlFamily := control.GetFamily() + controlName := control.GetName() newFamily = standardKey + "-" + controlFamily // create control family headings if oldFamily != newFamily { diff --git a/lib/certifications.go b/lib/certifications.go index cf359294..ad92cba4 100644 --- a/lib/certifications.go +++ b/lib/certifications.go @@ -6,13 +6,15 @@ import ( "gopkg.in/yaml.v2" "vbom.ml/util/sortorder" + v1 "github.com/opencontrol/compliance-masonry/lib/standards/versions/1_0_0" + "github.com/opencontrol/compliance-masonry/lib/common" ) // Certification struct is a collection of specific standards and controls // Schema info: https://github.com/opencontrol/schemas#certifications type Certification struct { Key string `yaml:"name" json:"name"` - Standards map[string]Standard `yaml:"standards" json:"standards"` + Standards map[string]v1.Standard `yaml:"standards" json:"standards"` } // GetSortedData returns a list of sorted standards @@ -23,9 +25,10 @@ func (certification Certification) GetSortedData(callback func(string, string)) } sort.Sort(sortorder.Natural(standardNames)) for _, standardKey := range standardNames { - certification.Standards[standardKey].GetSortedData(func(controlKey string) { + controlKeys := certification.Standards[standardKey].GetSortedControls() + for _, controlKey := range controlKeys { callback(standardKey, controlKey) - }) + } } } @@ -35,11 +38,11 @@ func (ws *LocalWorkspace) LoadCertification(certificationFile string) error { var certification Certification certificationData, err := ioutil.ReadFile(certificationFile) if err != nil { - return ErrReadFile + return common.ErrReadFile } err = yaml.Unmarshal(certificationData, &certification) if err != nil { - return ErrCertificationSchema + return common.ErrCertificationSchema } ws.Certification = &certification return nil diff --git a/lib/certifications_test.go b/lib/certifications_test.go deleted file mode 100644 index f7974848..00000000 --- a/lib/certifications_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package lib - -import ( - "path/filepath" - "testing" -) - -type certificationTest struct { - certificationFile string - expected Certification - expectedStandards int - expectedControls int -} - -type certificationTestError struct { - certificationFile string - expectedError error -} - -type standardOrderTest struct { - certification Certification - expectedOrder string -} - -var certificationTests = []certificationTest{ - // Test loading a certification file that has the LATO key, 2 standards, and 6 controls. - {filepath.Join("..", "fixtures", "opencontrol_fixtures", "certifications", "LATO.yaml"), Certification{Key: "LATO"}, 2, 6}, -} - -func TestLoadCertification(t *testing.T) { - for _, example := range certificationTests { - ws := &LocalWorkspace{} - ws.LoadCertification(example.certificationFile) - actual := ws.Certification - // Check if loaded certification has the expected key - if actual.Key != example.expected.Key { - t.Errorf("Expected %s, Actual: %s", example.expected.Key, actual.Key) - } - // Check if loaded certification has the expected number of standards - if len(actual.Standards) != example.expectedStandards { - t.Errorf("Expected %d, Actual: %d", example.expectedStandards, len(actual.Standards)) - } - // Get the length of the control by using the GetSortedData method - totalControls := 0 - actual.GetSortedData(func(_ string, _ string) { - totalControls++ - }) - // Check if loaded certification has the expected number of controls - if totalControls != example.expectedControls { - t.Errorf("Expected %d, Actual: %d", example.expectedControls, totalControls) - } - } -} - -var certificationTestErrors = []certificationTestError{ - // Test a file that can't be read - {filepath.Join("..", "fixtures", "opencontrol_fixtures", "certifications"), ErrReadFile}, - // Test a file that has a broken schema - {filepath.Join("..", "fixtures", "opencontrol_fixtures", "components", "EC2", "artifact-ec2-1.png"), ErrCertificationSchema}, -} - -func TestLoadCertificationErrors(t *testing.T) { - for _, example := range certificationTestErrors { - ws := &LocalWorkspace{} - actualError := ws.LoadCertification(example.certificationFile) - // Check that the expected error is the actual error returned - if example.expectedError != actualError { - t.Errorf("Expected %s, Actual: %s", example.expectedError, actualError) - } - } -} - -var standardOrderTests = []standardOrderTest{ - { - // Verify Natural sort order - Certification{Standards: map[string]Standard{ - "A": Standard{Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}}, - "B": Standard{Controls: map[string]Control{"12": Control{}, "2": Control{}, "1": Control{}}}, - "C": Standard{Controls: map[string]Control{"2": Control{}, "11": Control{}, "101": Control{}, "1000": Control{}, "100": Control{}, "10": Control{}, "1": Control{}}}, - }}, - "A1A2A3B1B2B12C1C2C10C11C100C101C1000", - }, - { - // Check that data is returned in order given letters and numbers - Certification{Standards: map[string]Standard{ - "1": Standard{Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}}, - "B": Standard{Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}}, - "B2": Standard{Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}}, - }}, - "111213B1B2B3B21B22B23", - }, -} - -func TestStandardOrder(t *testing.T) { - for _, example := range standardOrderTests { - actualOrder := "" - example.certification.GetSortedData(func(standardKey string, controlKey string) { - actualOrder += standardKey + controlKey - }) - // Verify that the actual order is the expected order - if actualOrder != example.expectedOrder { - t.Errorf("Expected %s, Actual: %s", example.expectedOrder, actualOrder) - } - } -} diff --git a/lib/common/control.go b/lib/common/control.go new file mode 100644 index 00000000..88aad166 --- /dev/null +++ b/lib/common/control.go @@ -0,0 +1,12 @@ +package common + +// Control is the interface for getting all the attributes for a given control. +// Schema info: https://github.com/opencontrol/schemas#standards-documentation +// +// GetName returns the string representation of the control. +// +// GetFamily returns which family the control belongs to. +type Control interface { + GetName() string + GetFamily() string +} diff --git a/lib/common/errors.go b/lib/common/errors.go index 8af3f00d..2635568d 100644 --- a/lib/common/errors.go +++ b/lib/common/errors.go @@ -9,5 +9,10 @@ var ( ErrUnknownSchemaVersion = errors.New("Unknown schema version") // ErrCantParseSemver is thrown when the semantic versioning can not be parsed. ErrCantParseSemver = errors.New("Can't parse semantic versioning of schema_version") -) - + // ErrReadFile is raised when a file can not be read + ErrReadFile = errors.New("Unable to read the file") + // ErrCertificationSchema is raised a certification cannot be parsed + ErrCertificationSchema = errors.New("Unable to parse certification") + // ErrStandardSchema is raised a standard cannot be parsed + ErrStandardSchema = errors.New("Unable to parse standard") +) \ No newline at end of file diff --git a/lib/common/standard.go b/lib/common/standard.go new file mode 100644 index 00000000..17748509 --- /dev/null +++ b/lib/common/standard.go @@ -0,0 +1,18 @@ +package common + +// Standard is the container of all the information for a particular Standard. +// Schema info: https://github.com/opencontrol/schemas#standards-documentation +// +// GetName returns the name +// +// GetControls returns all controls associated with the standard +// +// GetControl returns a particular control +// +// GetSortedControls returns a list of sorted controls +type Standard interface { + GetName() string + GetControls() map[string]Control + GetControl(string) Control + GetSortedControls() []string +} diff --git a/lib/standards.go b/lib/standards.go index f696a2b5..14625562 100644 --- a/lib/standards.go +++ b/lib/standards.go @@ -1,82 +1,57 @@ package lib import ( - "io/ioutil" - "sort" "sync" - "gopkg.in/yaml.v2" - "vbom.ml/util/sortorder" + "github.com/opencontrol/compliance-masonry/lib/common" + "github.com/opencontrol/compliance-masonry/lib/standards" ) -// Control struct stores data on a specific security requirement -// Schema info: https://github.com/opencontrol/schemas#standards-documentation -type Control struct { - Family string `yaml:"family" json:"family"` - Name string `yaml:"name" json:"name"` -} - -// Standard struct is a collection of security requirements -// Schema info: https://github.com/opencontrol/schemas#standards-documentation -type Standard struct { - Name string `yaml:"name" json:"name"` - Controls map[string]Control `yaml:",inline"` -} - -// Standards struct is a thread save mapping of Standards -type Standards struct { - mapping map[string]*Standard +// standardsMap struct is a thread save mapping of Standards +type standardsMap struct { + mapping map[string]common.Standard sync.RWMutex } -// GetSortedData returns a list of sorted controls -func (standard Standard) GetSortedData(callback func(string)) { - var controlNames []string - for controlName := range standard.Controls { - controlNames = append(controlNames, controlName) - } - sort.Sort(sortorder.Natural(controlNames)) - for _, controlName := range controlNames { - callback(controlName) - } -} - -// NewStandards creates an instance of Components struct -func NewStandards() *Standards { - return &Standards{mapping: make(map[string]*Standard)} +// newStandards creates an instance of standardsMap struct +func newStandards() *standardsMap { + return &standardsMap{mapping: make(map[string]common.Standard)} } // Add adds a standard to the standards mapping -func (standards *Standards) Add(standard *Standard) { - standards.Lock() - standards.mapping[standard.Name] = standard - standards.Unlock() +func (s *standardsMap) Add(standard common.Standard) { + s.Lock() + s.mapping[standard.GetName()] = standard + s.Unlock() } // Get retrieves a standard -func (standards *Standards) Get(standardName string) *Standard { - standards.Lock() - defer standards.Unlock() - return standards.mapping[standardName] +func (s *standardsMap) Get(standardName string) common.Standard { + s.Lock() + defer s.Unlock() + return s.mapping[standardName] } // GetAll retrieves all the standards -func (standards *Standards) GetAll() map[string]*Standard { - return standards.mapping +func (s *standardsMap) getAll() []common.Standard { + s.RLock() + defer s.RUnlock() + standardSlice := make([]common.Standard, len(s.mapping)) + idx := 0 + for _, value := range s.mapping { + standardSlice[idx] = value + idx++ + } + return standardSlice } // LoadStandard imports a standard into the Standard struct and adds it to the // main object. func (ws *LocalWorkspace) LoadStandard(standardFile string) error { - var standard Standard - standardData, err := ioutil.ReadFile(standardFile) - if err != nil { - return ErrReadFile - } - err = yaml.Unmarshal(standardData, &standard) + standard, err := standards.Load(standardFile) if err != nil { - return ErrStandardSchema + return common.ErrStandardSchema } - ws.Standards.Add(&standard) + ws.Standards.Add(standard) return nil } diff --git a/lib/standards/standard.go b/lib/standards/standard.go new file mode 100644 index 00000000..899d82e9 --- /dev/null +++ b/lib/standards/standard.go @@ -0,0 +1,22 @@ +package standards + +import ( + "io/ioutil" + "github.com/opencontrol/compliance-masonry/lib/common" + v1_0_0 "github.com/opencontrol/compliance-masonry/lib/standards/versions/1_0_0" + "gopkg.in/yaml.v2" +) + +// Load will read the file at the given path and attempt to return a standard object. +func Load(path string) (common.Standard, error) { + var standard v1_0_0.Standard + standardData, err := ioutil.ReadFile(path) + if err != nil { + return nil, common.ErrReadFile + } + err = yaml.Unmarshal(standardData, &standard) + if err != nil { + return nil, common.ErrStandardSchema + } + return standard, nil +} \ No newline at end of file diff --git a/lib/standards/standard_test.go b/lib/standards/standard_test.go new file mode 100644 index 00000000..ec2d2ba0 --- /dev/null +++ b/lib/standards/standard_test.go @@ -0,0 +1,62 @@ +package standards + +import ( + "path/filepath" + v1_0_0 "github.com/opencontrol/compliance-masonry/lib/standards/versions/1_0_0" + "testing" + "github.com/opencontrol/compliance-masonry/lib/common" +) + +type v1standardsTest struct { + standardsFile string + expected common.Standard + expectedControls int +} + +var standardsTests = []v1standardsTest{ + // Check loading a standard that has 326 controls + {filepath.Join("..", "..", "fixtures", "opencontrol_fixtures", "standards", "NIST-800-53.yaml"), v1_0_0.Standard{Name: "NIST-800-53"}, 326}, + // Check loading a standard that has 258 controls + {filepath.Join("..", "..", "fixtures", "opencontrol_fixtures", "standards", "PCI-DSS-MAY-2015.yaml"), v1_0_0.Standard{Name: "PCI-DSS-MAY-2015"}, 258}, +} + +func TestLoadStandard(t *testing.T) { + for _, example := range standardsTests { + actual, err := Load(example.standardsFile) + if err != nil { + t.Errorf("Expected nil error, Actual %s", err.Error()) + } + // Check that the name of the standard was correctly loaded + if actual.GetName() != example.expected.GetName() { + t.Errorf("Expected %s, Actual: %s", example.expected.GetName(), actual.GetName()) + } + // Get the length of the control by using the GetSortedData method + totalControls := len(actual.GetSortedControls()) + if totalControls != example.expectedControls { + t.Errorf("Expected %d, Actual: %d", example.expectedControls, totalControls) + } + } +} + + +type standardTestError struct { + standardsFile string + expectedError error +} + +var standardTestErrors = []standardTestError{ + // Check the error loading a file that doesn't exist + {"", common.ErrReadFile}, + // Check the error loading a file that has a broken schema + {filepath.Join("..", "..", "fixtures", "standards_fixtures", "BrokenStandard", "NIST-800-53.yaml"), common.ErrStandardSchema}, +} + +func TestLoadStandardsErrors(t *testing.T) { + for _, example := range standardTestErrors { + _, actualError := Load(example.standardsFile) + // Check that the expected error and the actual error are the same + if example.expectedError != actualError { + t.Errorf("Expected %s, Actual: %s", example.expectedError, actualError) + } + } +} \ No newline at end of file diff --git a/lib/standards/versions/1_0_0/standard.go b/lib/standards/versions/1_0_0/standard.go new file mode 100644 index 00000000..9b67a4ec --- /dev/null +++ b/lib/standards/versions/1_0_0/standard.go @@ -0,0 +1,60 @@ +package standard + +import ( + "sort" + "vbom.ml/util/sortorder" + "github.com/opencontrol/compliance-masonry/lib/common" +) + +// Control struct stores data on a specific security requirement +// Schema info: https://github.com/opencontrol/schemas#standards-documentation +type Control struct { + Family string `yaml:"family" json:"family"` + Name string `yaml:"name" json:"name"` +} + +// Standard struct is a collection of security requirements +// Schema info: https://github.com/opencontrol/schemas#standards-documentation +type Standard struct { + Name string `yaml:"name" json:"name"` + Controls map[string]Control `yaml:",inline"` +} + +// GetSortedControls returns a list of sorted controls +func (standard Standard) GetSortedControls() []string { + var controlNames []string + for controlName := range standard.Controls { + controlNames = append(controlNames, controlName) + } + sort.Sort(sortorder.Natural(controlNames)) + return controlNames +} + +// GetName returns the name of the standard. +func (standard Standard) GetName() string { + return standard.Name +} + +// GetControls returns all controls associated with the standard +func (standard Standard) GetControls() map[string]common.Control { + m := make(map[string]common.Control) + for key, value := range standard.Controls { + m[key] = value + } + return m +} + +// GetControl returns a particular control +func (standard Standard) GetControl(controlKey string) common.Control { + return standard.Controls[controlKey] +} + +// GetFamily returns which family the control belongs to. +func (control Control) GetFamily() string { + return control.Family +} + +// GetName returns the string representation of the control. +func (control Control) GetName() string { + return control.Name +} diff --git a/lib/standards/versions/1_0_0/standard_test.go b/lib/standards/versions/1_0_0/standard_test.go new file mode 100644 index 00000000..d6313e94 --- /dev/null +++ b/lib/standards/versions/1_0_0/standard_test.go @@ -0,0 +1,78 @@ +package standard + +import "testing" + +type controlOrderTest struct { + standard Standard + expectedOrder string +} + +var controlOrderTests = []controlOrderTest{ + // Verify that numeric controls are ordered correctly + { + Standard{ + Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}, + }, + "123", + }, + // Verify that alphabetical controls are ordered correctly + { + Standard{ + Controls: map[string]Control{"c": Control{}, "b": Control{}, "a": Control{}}, + }, + "abc", + }, + // Verify that alphanumeric controls are ordered correctly + { + Standard{ + Controls: map[string]Control{"1": Control{}, "b": Control{}, "2": Control{}}, + }, + "12b", + }, + // Verify that complex alphanumeric controls are ordered correctly + { + Standard{ + Controls: map[string]Control{"AC-1": Control{}, "AB-2": Control{}, "1.1.1": Control{}, "2.1.1": Control{}}, + }, + "1.1.12.1.1AB-2AC-1", + }, + // Verify Natural sort order + { + Standard{ + Controls: map[string]Control{"AC-1": Control{}, "AC-12": Control{}, "AC-2 (1)": Control{}, "AC-2 (11)": Control{}, "AC-2 (3)": Control{}, "AC-3 (1)": Control{}}, + }, + "AC-1AC-2 (1)AC-2 (3)AC-2 (11)AC-3 (1)AC-12", + }, +} + +func TestControlOrder(t *testing.T) { + for _, example := range controlOrderTests { + actualOrder := "" + controlKeys := example.standard.GetSortedControls() + for _, controlKey := range controlKeys { + actualOrder += controlKey + } + // Check that the expected order is the actual order + if actualOrder != example.expectedOrder { + t.Errorf("Expected %s, Actual: %s", example.expectedOrder, actualOrder) + } + } +} + +func TestGetters(t *testing.T) { + st := Standard{Name: "test", Controls: map[string]Control{"3": Control{Name:"controlName", Family:"controlFamily"}, "2": Control{}, "1": Control{}}} + if st.GetName() != "test" { + t.Errorf("Expected standard name test. Actual %s", st.GetName()) + } + controls := st.GetControls() + if len(controls) != 3 { + t.Errorf("Expected 3 controls, found %d", len(controls)) + } + control := st.GetControl("3") + if control.GetName() != "controlName" { + t.Errorf("Expected control name 'controlName'. Actual %s", control.GetName()) + } + if control.GetFamily() != "controlFamily" { + t.Errorf("Expected control family 'controlFamily'. Actual %s", control.GetFamily()) + } +} \ No newline at end of file diff --git a/lib/standards_test.go b/lib/standards_test.go deleted file mode 100644 index 0b539ddb..00000000 --- a/lib/standards_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package lib - -import ( - "path/filepath" - "testing" -) - -type standardsTest struct { - standardsFile string - expected Standard - expectedControls int -} - -type standardTestError struct { - standardsFile string - expectedError error -} - -type controlOrderTest struct { - standard Standard - expectedOrder string -} - -var standardsTests = []standardsTest{ - // Check loading a standard that has 326 controls - {filepath.Join("..", "fixtures", "opencontrol_fixtures", "standards", "NIST-800-53.yaml"), Standard{Name: "NIST-800-53"}, 326}, - // Check loading a standard that has 258 controls - {filepath.Join("..", "fixtures", "opencontrol_fixtures", "standards", "PCI-DSS-MAY-2015.yaml"), Standard{Name: "PCI-DSS-MAY-2015"}, 258}, -} - -func TestLoadStandard(t *testing.T) { - for _, example := range standardsTests { - ws := &LocalWorkspace{Standards: NewStandards()} - ws.LoadStandard(example.standardsFile) - actual := ws.Standards.Get(example.expected.Name) - // Check that the name of the standard was correctly loaded - if actual.Name != example.expected.Name { - t.Errorf("Expected %s, Actual: %s", example.expected.Name, actual.Name) - } - // Get the length of the control by using the GetSortedData method - totalControls := 0 - actual.GetSortedData(func(_ string) { - totalControls++ - }) - if totalControls != example.expectedControls { - t.Errorf("Expected %d, Actual: %d", example.expectedControls, totalControls) - } - } -} - -var controlOrderTests = []controlOrderTest{ - // Verify that numeric controls are ordered correctly - { - Standard{ - Controls: map[string]Control{"3": Control{}, "2": Control{}, "1": Control{}}, - }, - "123", - }, - // Verify that alphabetical controls are ordered correctly - { - Standard{ - Controls: map[string]Control{"c": Control{}, "b": Control{}, "a": Control{}}, - }, - "abc", - }, - // Verify that alphanumeric controls are ordered correctly - { - Standard{ - Controls: map[string]Control{"1": Control{}, "b": Control{}, "2": Control{}}, - }, - "12b", - }, - // Verify that complex alphanumeric controls are ordered correctly - { - Standard{ - Controls: map[string]Control{"AC-1": Control{}, "AB-2": Control{}, "1.1.1": Control{}, "2.1.1": Control{}}, - }, - "1.1.12.1.1AB-2AC-1", - }, - // Verify Natural sort order - { - Standard{ - Controls: map[string]Control{"AC-1": Control{}, "AC-12": Control{}, "AC-2 (1)": Control{}, "AC-2 (11)": Control{}, "AC-2 (3)": Control{}, "AC-3 (1)": Control{}}, - }, - "AC-1AC-2 (1)AC-2 (3)AC-2 (11)AC-3 (1)AC-12", - }, -} - -func TestControlOrder(t *testing.T) { - for _, example := range controlOrderTests { - actualOrder := "" - example.standard.GetSortedData(func(controlKey string) { - actualOrder += controlKey - }) - // Check that the expected order is the actual order - if actualOrder != example.expectedOrder { - t.Errorf("Expected %s, Actual: %s", example.expectedOrder, actualOrder) - } - } -} - -var standardTestErrors = []standardTestError{ - // Check the error loading a file that doesn't exist - {"", ErrReadFile}, - // Check the error loading a file that has a broken schema - {filepath.Join("..", "fixtures", "standards_fixtures", "BrokenStandard", "NIST-800-53.yaml"), ErrStandardSchema}, -} - -func TestLoadStandardsErrors(t *testing.T) { - for _, example := range standardTestErrors { - ws := &LocalWorkspace{} - actualError := ws.LoadStandard(example.standardsFile) - // Check that the expected error and the actual error are the same - if example.expectedError != actualError { - t.Errorf("Expected %s, Actual: %s", example.expectedError, actualError) - } - } -} diff --git a/lib/workspace.go b/lib/workspace.go index be322164..fcfab6e2 100644 --- a/lib/workspace.go +++ b/lib/workspace.go @@ -9,20 +9,11 @@ import ( "github.com/codegangsta/cli" ) -var ( - // ErrReadFile is raised when a file can not be read - ErrReadFile = errors.New("Unable to read the file") - // ErrCertificationSchema is raised a certification cannot be parsed - ErrCertificationSchema = errors.New("Unable to parse certification") - // ErrStandardSchema is raised a standard cannot be parsed - ErrStandardSchema = errors.New("Unable to parse standard") -) - // LocalWorkspace struct combines components, standards, and a certification data // For more information on the opencontrol schema visit: https://github.com/opencontrol/schemas type LocalWorkspace struct { Components *componentsMap - Standards *Standards + Standards *standardsMap Justifications *Justifications Certification *Certification } @@ -38,7 +29,7 @@ func NewWorkspace() *LocalWorkspace { return &LocalWorkspace{ Justifications: NewJustifications(), Components: newComponents(), - Standards: NewStandards(), + Standards: newStandards(), } } diff --git a/lib/workspace_test.go b/lib/workspace_test.go index 0303c551..d3b79cb4 100644 --- a/lib/workspace_test.go +++ b/lib/workspace_test.go @@ -74,7 +74,7 @@ func TestLoadData(t *testing.T) { t.Errorf("Expected: `%d`, Actual: `%d`", example.expectedComponents, actualComponentNum) } // Check the number of standards - actualStandardsNum := len(actual.Standards.GetAll()) + actualStandardsNum := len(actual.Standards.getAll()) if actualStandardsNum != example.expectedStandardsNum { t.Errorf("Expected: `%d`, Actual: `%d`", example.expectedComponents, actualComponentNum) } @@ -134,7 +134,7 @@ func TestLoadStandards(t *testing.T) { for _, example := range loadStandardsTests { ws := NewWorkspace() ws.LoadStandards(example.dir) - actualStandards := len(ws.Standards.GetAll()) + actualStandards := len(ws.Standards.getAll()) // Check that the actual number of standards is the expected number of standards if actualStandards != example.expectedStandards { t.Errorf("Expected: `%d`, Actual: `%d`", example.expectedStandards, actualStandards) From 7f1c5cc7a4899cf629db793088f13f10231081df Mon Sep 17 00:00:00 2001 From: "James C. Scott" Date: Fri, 19 Aug 2016 10:52:12 -0400 Subject: [PATCH 2/3] Add standard mock --- lib/common/mocks/Standard.go | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/common/mocks/Standard.go diff --git a/lib/common/mocks/Standard.go b/lib/common/mocks/Standard.go new file mode 100644 index 00000000..2acbcb5f --- /dev/null +++ b/lib/common/mocks/Standard.go @@ -0,0 +1,71 @@ +package mocks + +import "github.com/opencontrol/compliance-masonry/lib/common" +import "github.com/stretchr/testify/mock" + +// Standard is an autogenerated mock type for the Standard type +type Standard struct { + mock.Mock +} + +// GetControl provides a mock function with given fields: _a0 +func (_m *Standard) GetControl(_a0 string) common.Control { + ret := _m.Called(_a0) + + var r0 common.Control + if rf, ok := ret.Get(0).(func(string) common.Control); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Control) + } + } + + return r0 +} + +// GetControls provides a mock function with given fields: +func (_m *Standard) GetControls() map[string]common.Control { + ret := _m.Called() + + var r0 map[string]common.Control + if rf, ok := ret.Get(0).(func() map[string]common.Control); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]common.Control) + } + } + + return r0 +} + +// GetName provides a mock function with given fields: +func (_m *Standard) GetName() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetSortedControls provides a mock function with given fields: +func (_m *Standard) GetSortedControls() []string { + ret := _m.Called() + + var r0 []string + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + return r0 +} From 0e1d033ca014ba7919e7b99f7ddfe3bce1977d70 Mon Sep 17 00:00:00 2001 From: "James C. Scott" Date: Fri, 19 Aug 2016 10:52:36 -0400 Subject: [PATCH 3/3] Increase test coverage for standards --- lib/standards_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lib/standards_test.go diff --git a/lib/standards_test.go b/lib/standards_test.go new file mode 100644 index 00000000..5ef0e295 --- /dev/null +++ b/lib/standards_test.go @@ -0,0 +1,33 @@ +package lib + +import ( + "testing" + "github.com/opencontrol/compliance-masonry/lib/common/mocks" + "github.com/stretchr/testify/assert" + "github.com/opencontrol/compliance-masonry/lib/common" +) + +func TestGetStandard(t *testing.T) { + // Setup map + m := newStandards() + // Get nil component. + standard := m.Get("test") + assert.Nil(t, standard) + // Create mock component + newStandard := new(mocks.Standard) + newStandard.On("GetName").Return("test") + // Test add method + m.Add(newStandard) + // Try to retrieve the component again. + standard = m.Get("test") + assert.Equal(t, standard.GetName(), "test") +} + +func TestBadLoadStandard(t *testing.T) { + // Setup map + m := newStandards() + ws := LocalWorkspace{Standards: m} + err := ws.LoadStandard("fake.file") + assert.NotNil(t, err) + assert.Equal(t, common.ErrStandardSchema, err) +} \ No newline at end of file