Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create a package for fixture helpers #39

Merged
merged 18 commits into from
Aug 29, 2016
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
65 changes: 65 additions & 0 deletions control/narrative_section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package control

import (
"errors"
"regexp"

"github.com/jbowtie/gokogiri/xml"
docxHelper "github.com/opencontrol/fedramp-templater/docx/helper"
"github.com/opencontrol/fedramp-templater/opencontrols"
xmlHelper "github.com/opencontrol/fedramp-templater/xml/helper"
)

type narrativeSection struct {
row xml.Node
}

func (n narrativeSection) parsePart() (key string, err error) {
re := regexp.MustCompile(`Part ([a-z])`)
content := []byte(n.row.Content())
subMatches := re.FindSubmatch(content)
if len(subMatches) != 2 {
err = errors.New("No Parts found.")
return
}
key = string(subMatches[1])
return
}

// GetKey returns the narrative section "part"/key. `key` will be an empty string if there is no "Part".
func (n narrativeSection) GetKey() (key string, err error) {
cells, err := xmlHelper.SearchSubtree(n.row, `./w:tc`)
numCells := len(cells)
if numCells == 1 {
// there is only a single narrative section
key = ""
} else if numCells == 2 {
key, err = n.parsePart()
if err != nil {
return
}
} else {
err = errors.New("Don't know how to parse row.")
}

return
}

// Fill populates the section/part with the narrative for this control part from the provided data.
func (n narrativeSection) Fill(data opencontrols.Data, control string) (err error) {
// the row should have one or two cells; either way, the last one is what should be filled
cellNode, err := xmlHelper.SearchOne(n.row, `./w:tc[last()]`)
if err != nil {
return
}

key, err := n.GetKey()
if err != nil {
return
}

narrative := data.GetNarrative(control, key)
docxHelper.FillCell(cellNode, narrative)

return
}
50 changes: 50 additions & 0 deletions control/narrative_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package control

import (
"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/opencontrols"
)

func fillRows(rows []xml.Node, data opencontrols.Data, control string) error {
for _, row := range rows {
section := narrativeSection{row}
err := section.Fill(data, control)
if err != nil {
return err
}
}
return nil
}

// NarrativeTable represents the node in the Word docx XML tree that corresponds to the justification fields for a security control.
type NarrativeTable struct {
table
}

// NewNarrativeTable creates a NarrativeTable instance.
func NewNarrativeTable(root xml.Node) NarrativeTable {
tbl := table{Root: root}
return NarrativeTable{tbl}
}

// SectionRows returns the list of rows which correspond to each "part" of the narrative. Will return a single row when the narrative isn't split into parts.
func (t *NarrativeTable) SectionRows() ([]xml.Node, error) {
// skip the header row
return t.table.searchSubtree(`.//w:tr[position() > 1]`)
}

// Fill inserts the OpenControl data into the table.
func (t *NarrativeTable) Fill(openControlData opencontrols.Data) (err error) {
control, err := t.table.controlName()
if err != nil {
return
}

rows, err := t.SectionRows()
if err != nil {
return
}

fillRows(rows, openControlData, control)
return
}
37 changes: 37 additions & 0 deletions control/narrative_table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package control_test

import (
. "github.com/opencontrol/fedramp-templater/control"
"github.com/opencontrol/fedramp-templater/fixtures"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("NarrativeTable", func() {
Describe("SectionRows", func() {
It("returns the correct number for a singular narrative", func() {
doc := fixtures.LoadSSP("FedRAMP_ac-2-1_v2.1.docx")
defer doc.Close()
root, err := doc.NarrativeTable("AC-2 (1)")
Expect(err).NotTo(HaveOccurred())

table := NewNarrativeTable(root)
sections, err := table.SectionRows()
Expect(err).NotTo(HaveOccurred())
Expect(len(sections)).To(Equal(1))
})

It("returns the correct number for multiple narrative sections", func() {
doc := fixtures.LoadSSP("FedRAMP_ac-2_v2.1.docx")
defer doc.Close()
root, err := doc.NarrativeTable("AC-2")
Expect(err).NotTo(HaveOccurred())

table := NewNarrativeTable(root)
sections, err := table.SectionRows()
Expect(err).NotTo(HaveOccurred())
Expect(len(sections)).To(Equal(11))
})
})
})
7 changes: 4 additions & 3 deletions control/responsible_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ import (
"strings"

"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/xml/helper"
)

// findResponsibleRole looks for the Responsible Role cell in the control table.
func findResponsibleRole(st *SummaryTable) (*responsibleRole, error) {
nodes, err := st.searchSubtree(".//w:tc[starts-with(normalize-space(.), 'Responsible Role')]")
func findResponsibleRole(ct *SummaryTable) (*responsibleRole, error) {
nodes, err := ct.table.searchSubtree(".//w:tc[starts-with(normalize-space(.), 'Responsible Role')]")
if err != nil {
return nil, err
}
if len(nodes) != 1 {
return nil, errors.New("could not find Responsible Role cell")
}
parentNode := nodes[0]
childNodes, err := parentNode.Search(".//w:t")
childNodes, err := helper.SearchSubtree(parentNode, `.//w:t`)
if err != nil || len(childNodes) < 1 {
return nil, errors.New("Should not happen, cannot find text nodes.")
}
Expand Down
49 changes: 7 additions & 42 deletions control/summary_table.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package control

import (
"errors"
"regexp"
"strings"

// using fork because of https://github.com/moovweb/gokogiri/pull/93#issuecomment-215582446
"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/opencontrols"
"github.com/opencontrol/fedramp-templater/reporter"
Expand All @@ -15,49 +10,19 @@ const (
responsibleRoleField = "Responsible Role"
)

// SummaryTable represents the node in the Word docx XML tree that corresponds to a security control.
// SummaryTable represents the node in the Word docx XML tree that corresponds to the summary information for a security control.
type SummaryTable struct {
Root xml.Node
table
}

func (st *SummaryTable) searchSubtree(xpath string) (nodes []xml.Node, err error) {
// http://stackoverflow.com/a/25387687/358804
if !strings.HasPrefix(xpath, ".") {
err = errors.New("XPath must have leading period (`.`) to only search the subtree")
return
}

return st.Root.Search(xpath)
}

func (st *SummaryTable) tableHeader() (content string, err error) {
nodes, err := st.searchSubtree(".//w:tr")
if err != nil {
return
}
if len(nodes) == 0 {
err = errors.New("could not find control name")
return
}
// we only care about the first match
content = nodes[0].Content()

return
// NewSummaryTable creates a SummaryTable instance.
func NewSummaryTable(root xml.Node) SummaryTable {
tbl := table{Root: root}
return SummaryTable{tbl}
}

func (st *SummaryTable) controlName() (name string, err error) {
content, err := st.tableHeader()
if err != nil {
return
}

// matches controls and control enhancements, e.g. `AC-2`, `AC-2 (1)`, etc.
regex := regexp.MustCompile(`[A-Z]{2}-\d+( +\(\d+\))?`)
name = regex.FindString(content)
if name == "" {
err = errors.New("control name not found")
}
return
return st.table.controlName()
}

// Fill inserts the OpenControl justifications into the table. Note this modifies the `table`.
Expand Down
42 changes: 9 additions & 33 deletions control/summary_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package control_test

import (
"bytes"
"os"
"path/filepath"
"text/template"
// using fork because of https://github.com/moovweb/gokogiri/pull/93#issuecomment-215582446
"github.com/jbowtie/gokogiri/xml"

"github.com/jbowtie/gokogiri/xml"
. "github.com/opencontrol/fedramp-templater/control"
"github.com/opencontrol/fedramp-templater/docx/helper"
"github.com/opencontrol/fedramp-templater/opencontrols"
"github.com/opencontrol/fedramp-templater/ssp"
"github.com/opencontrol/fedramp-templater/fixtures"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -23,7 +20,7 @@ type tableData struct {
}

func docFixture(control string) *xml.XmlDocument {
path := filepath.Join("..", "fixtures", "simplified_table.xml")
path := fixtures.FixturePath("simplified_table.xml")
tpl, err := template.ParseFiles(path)
Expect(err).ToNot(HaveOccurred())

Expand All @@ -45,32 +42,12 @@ func getTable(control string) xml.Node {
return tables[0]
}

func openControlFixturePath() string {
path := filepath.Join("..", "fixtures", "opencontrols")
path, err := filepath.Abs(path)
Expect(err).NotTo(HaveOccurred())
_, err = os.Stat(path)
Expect(err).NotTo(HaveOccurred())

return path
}

func openControlFixture() opencontrols.Data {
path := openControlFixturePath()
data, errors := opencontrols.LoadFrom(path)
for _, err := range errors {
Expect(err).NotTo(HaveOccurred())
}

return data
}

var _ = Describe("SummaryTable", func() {
Describe("Fill", func() {
It("fills in the Responsible Role for controls", func() {
table := getTable("AC-2")
st := SummaryTable{Root: table}
openControlData := openControlFixture()
st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()

st.Fill(openControlData)

Expand All @@ -79,8 +56,8 @@ var _ = Describe("SummaryTable", func() {

It("fills in the Responsible Role for control enhancements", func() {
table := getTable("AC-2 (1)")
st := SummaryTable{Root: table}
openControlData := openControlFixture()
st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()

st.Fill(openControlData)

Expand All @@ -91,9 +68,8 @@ var _ = Describe("SummaryTable", func() {
Describe("Diff", func() {
It("detects no diff when the value of responsible role is empty", func() {
table := getTable("AC-2")
st := SummaryTable{Root: table}
openControlData := openControlFixture()

st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()
diff, err := st.Diff(openControlData)

Expect(diff).To(Equal([]reporter.Reporter{}))
Expand Down
47 changes: 47 additions & 0 deletions control/table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package control

import (
"errors"
"regexp"

"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/xml/helper"
)

type table struct {
Root xml.Node
}

func (t *table) searchSubtree(xpath string) ([]xml.Node, error) {
return helper.SearchSubtree(t.Root, xpath)
}

func (t *table) tableHeader() (content string, err error) {
nodes, err := t.searchSubtree(".//w:tr")
if err != nil {
return
}
if len(nodes) == 0 {
err = errors.New("could not find control name")
return
}
// we only care about the first match
content = nodes[0].Content()

return
}

func (t *table) controlName() (name string, err error) {
content, err := t.tableHeader()
if err != nil {
return
}

// matches controls and control enhancements, e.g. `AC-2`, `AC-2 (1)`, etc.
regex := regexp.MustCompile(`[A-Z]{2}-\d+( +\(\d+\))?`)
name = regex.FindString(content)
if name == "" {
err = errors.New("control name not found")
}
return
}
Loading