From 02101a5633c009ff46083651745b6aa40ac62448 Mon Sep 17 00:00:00 2001 From: Megan Wolf <97549300+meganwolf0@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:10:29 -0400 Subject: [PATCH] fix(console): refactor, retries, sleep to address flaky tests (#698) --- src/internal/testhelpers/testhelpers.go | 42 +++++ .../assessment-results_test.go | 40 +++++ .../TestAssessmentResultsBasicView.golden | 56 +++++++ src/internal/tui/component/component_test.go | 103 +++++++++++- .../TestComponentControlSelect.golden | 56 +++++++ .../TestComponentDefinitionBasicView.golden} | 115 +++++++------- ...tComponentDefinitionComponentSwitch.golden | 56 +++++++ ...estEditViewComponentDefinitionModel.golden | 56 +++++++ src/internal/tui/model_test.go | 146 ++---------------- .../TestComponentControlSelect.golden | 59 ------- ...estEditViewComponentDefinitionModel.golden | 59 ------- .../TestMultiComponentDefinitionModel.golden | 59 ------- ...nModel.golden => TestNewOSCALModel.golden} | 0 13 files changed, 475 insertions(+), 372 deletions(-) create mode 100644 src/internal/tui/assessment_results/assessment-results_test.go create mode 100644 src/internal/tui/assessment_results/testdata/TestAssessmentResultsBasicView.golden create mode 100644 src/internal/tui/component/testdata/TestComponentControlSelect.golden rename src/internal/tui/{testdata/TestNewAssessmentResultsModel.golden => component/testdata/TestComponentDefinitionBasicView.golden} (50%) create mode 100644 src/internal/tui/component/testdata/TestComponentDefinitionComponentSwitch.golden create mode 100644 src/internal/tui/component/testdata/TestEditViewComponentDefinitionModel.golden delete mode 100644 src/internal/tui/testdata/TestComponentControlSelect.golden delete mode 100644 src/internal/tui/testdata/TestEditViewComponentDefinitionModel.golden delete mode 100644 src/internal/tui/testdata/TestMultiComponentDefinitionModel.golden rename src/internal/tui/testdata/{TestNewComponentDefinitionModel.golden => TestNewOSCALModel.golden} (100%) diff --git a/src/internal/testhelpers/testhelpers.go b/src/internal/testhelpers/testhelpers.go index 936ab224..e119ff75 100644 --- a/src/internal/testhelpers/testhelpers.go +++ b/src/internal/testhelpers/testhelpers.go @@ -4,7 +4,10 @@ import ( "fmt" "os" "testing" + "time" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/exp/teatest" oscalTypes_1_1_2 "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-2" "github.com/defenseunicorns/lula/src/pkg/common/oscal" ) @@ -32,3 +35,42 @@ func CreateTempFile(t *testing.T, ext string) *os.File { return tempFile } + +// RunTestModelView runs a test model view with a given model and messages, impelements a retry loop if final model is nil +func RunTestModelView(t *testing.T, m tea.Model, msgs []tea.Msg, timeout time.Duration, maxRetries, height, width int) error { + + testModelView := func(t *testing.T) (bool, error) { + tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(width, height)) + + for _, msg := range msgs { + tm.Send(msg) + time.Sleep(time.Millisecond * 50) + } + + if err := tm.Quit(); err != nil { + return false, err + } + + fm := tm.FinalModel(t, teatest.WithFinalTimeout(timeout)) + + if fm == nil { + return true, nil + } + + teatest.RequireEqualOutput(t, []byte(fm.View())) + + return false, nil + } + + for i := 0; i < maxRetries; i++ { + retry, err := testModelView(t) + if retry { + continue + } + if err != nil { + return err + } + break + } + return nil +} diff --git a/src/internal/tui/assessment_results/assessment-results_test.go b/src/internal/tui/assessment_results/assessment-results_test.go new file mode 100644 index 00000000..e57240a0 --- /dev/null +++ b/src/internal/tui/assessment_results/assessment-results_test.go @@ -0,0 +1,40 @@ +package assessmentresults_test + +import ( + "testing" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/defenseunicorns/lula/src/internal/testhelpers" + assessmentresults "github.com/defenseunicorns/lula/src/internal/tui/assessment_results" + "github.com/defenseunicorns/lula/src/internal/tui/common" + "github.com/muesli/termenv" +) + +const ( + timeout = time.Second * 20 + maxRetries = 3 + height = common.DefaultHeight + width = common.DefaultWidth + + validAssessmentResults = "../../../test/unit/common/oscal/valid-assessment-results.yaml" +) + +func init() { + lipgloss.SetColorProfile(termenv.Ascii) +} + +// TestAssessmentResultsBasicView tests that the model is created correctly from an assessment results model +func TestAssessmentResultsBasicView(t *testing.T) { + oscalModel := testhelpers.OscalFromPath(t, validAssessmentResults) + model := assessmentresults.NewAssessmentResultsModel(oscalModel.AssessmentResults) + model.Open(height, width) + + msgs := []tea.Msg{} + + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { + t.Fatal(err) + } +} diff --git a/src/internal/tui/assessment_results/testdata/TestAssessmentResultsBasicView.golden b/src/internal/tui/assessment_results/testdata/TestAssessmentResultsBasicView.golden new file mode 100644 index 00000000..02d559d7 --- /dev/null +++ b/src/internal/tui/assessment_results/testdata/TestAssessmentResultsBasicView.golden @@ -0,0 +1,56 @@ + ? toggle help + ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ + Selected Result │Lula Validation Result - 41787700-2a4c-…│ Compare Result │No Result Selected │ + ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ +╭───────────────╮ ╭─────────╮ +│ Findings List ├─────────────────────────────╮ │ Summary ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +╰───────────────╯ ╰─────────╯ + │ │ │ ⚠️ Summary Under Construction ⚠️ │ + │ 1 item │ │ │ + │ │ │ │ + │ ID-1 │ │ │ + │ not-satisfied │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭──────────────╮ + │ │ │ Observations ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰──────────────╯ + │ │ │ ⚠️ Observations Under Construction ⚠️ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ ↑/k up • ↓/j down • ↳ confirm • ? toggle │ │ │ + ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/component/component_test.go b/src/internal/tui/component/component_test.go index cb6fa8a4..ed263a30 100644 --- a/src/internal/tui/component/component_test.go +++ b/src/internal/tui/component/component_test.go @@ -3,19 +3,120 @@ package component_test import ( "os" "testing" + "time" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" oscalTypes_1_1_2 "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-2" "github.com/defenseunicorns/lula/src/internal/testhelpers" + "github.com/defenseunicorns/lula/src/internal/tui/common" "github.com/defenseunicorns/lula/src/internal/tui/component" "github.com/defenseunicorns/lula/src/pkg/common/oscal" + "github.com/muesli/termenv" ) +const ( + timeout = time.Second * 20 + maxRetries = 3 + height = common.DefaultHeight + width = common.DefaultWidth + + validCompDef = "../../../test/unit/common/oscal/valid-generated-component.yaml" + validCompDefValidations = "../../../test/unit/common/oscal/valid-component.yaml" + validCompDefMulti = "../../../test/unit/common/oscal/valid-multi-component.yaml" + validCompDefMultiValidations = "../../../test/unit/common/oscal/valid-multi-component-validations.yaml" +) + +func init() { + lipgloss.SetColorProfile(termenv.Ascii) +} + +// TestComponentDefinitionBasicView tests that the model is created correctly from a component definition with validations +func TestComponentDefinitionBasicView(t *testing.T) { + oscalModel := testhelpers.OscalFromPath(t, validCompDef) + model := component.NewComponentDefinitionModel(oscalModel.ComponentDefinition) + model.Open(height, width) + + msgs := []tea.Msg{} + + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { + t.Fatal(err) + } +} + +// TestComponentDefinitionComponentSwitch tests that the component picker executes correctly +func TestComponentDefinitionComponentSwitch(t *testing.T) { + oscalModel := testhelpers.OscalFromPath(t, validCompDefMulti) + model := component.NewComponentDefinitionModel(oscalModel.ComponentDefinition) + model.Open(height, width) + + msgs := []tea.Msg{ + tea.KeyMsg{Type: tea.KeyRight}, // Select component + tea.KeyMsg{Type: tea.KeyEnter}, // enter component selection overlay + tea.KeyMsg{Type: tea.KeyDown}, // navigate down + tea.KeyMsg{Type: tea.KeyEnter}, // select new component, exit overlay + tea.KeyMsg{Type: tea.KeyRight}, // Select framework + tea.KeyMsg{Type: tea.KeyRight}, // Select control + tea.KeyMsg{Type: tea.KeyEnter}, // Open control + } + + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { + t.Fatal(err) + } +} + +// TestComponentControlSelect tests that the user can navigate to a control, select it, and see expected +// remarks, description, and validations +func TestComponentControlSelect(t *testing.T) { + oscalModel := testhelpers.OscalFromPath(t, validCompDefMulti) + model := component.NewComponentDefinitionModel(oscalModel.ComponentDefinition) + model.Open(height, width) + + msgs := []tea.Msg{ + tea.KeyMsg{Type: tea.KeyRight}, // Select component + tea.KeyMsg{Type: tea.KeyRight}, // Select framework + tea.KeyMsg{Type: tea.KeyRight}, // Select control + tea.KeyMsg{Type: tea.KeyEnter}, // Open control + } + + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { + t.Fatal(err) + } +} + +// TestEditViewComponentDefinitionModel tests that the editing views of the component definition model are correct +func TestEditViewComponentDefinitionModel(t *testing.T) { + oscalModel := testhelpers.OscalFromPath(t, validCompDefValidations) + model := component.NewComponentDefinitionModel(oscalModel.ComponentDefinition) + model.Open(height, width) + + msgs := []tea.Msg{ + tea.KeyMsg{Type: tea.KeyRight}, // Select component + tea.KeyMsg{Type: tea.KeyRight}, // Select framework + tea.KeyMsg{Type: tea.KeyRight}, // Select control + tea.KeyMsg{Type: tea.KeyEnter}, // Open control + tea.KeyMsg{Type: tea.KeyRight}, // Navigate to remarks + tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}, // Edit remarks + tea.KeyMsg{Type: tea.KeyCtrlE}, // Newline + tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t', 'e', 's', 't'}}, // Add "test" to remarks + tea.KeyMsg{Type: tea.KeyEnter}, // Confirm edit + } + + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { + t.Fatal(err) + } +} + // TestEditComponentDefinitionModel tests that a component definition model can be modified, written, and re-read func TestEditComponentDefinitionModel(t *testing.T) { tempOscalFile := testhelpers.CreateTempFile(t, "yaml") defer os.Remove(tempOscalFile.Name()) - oscalModel := testhelpers.OscalFromPath(t, "../../../test/unit/common/oscal/valid-generated-component.yaml") + oscalModel := testhelpers.OscalFromPath(t, validCompDef) model := component.NewComponentDefinitionModel(oscalModel.ComponentDefinition) testControlId := "ac-1" diff --git a/src/internal/tui/component/testdata/TestComponentControlSelect.golden b/src/internal/tui/component/testdata/TestComponentControlSelect.golden new file mode 100644 index 00000000..16e1886e --- /dev/null +++ b/src/internal/tui/component/testdata/TestComponentControlSelect.golden @@ -0,0 +1,56 @@ + ↳ select • ↑/k move up • ↓/j move down • / filter • ? toggle help + ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ + Selected Component │Component A - 7c02500a-6e33-44e0-82ee-f…│ Selected Framework │https://raw.githubusercontent.com/usnis…│ + ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ +╭───────────────╮ ╭─────────╮ +│ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +╰───────────────╯ ╰─────────╯ + │ │ │ STATEMENT: │ + │ 3 items │ │ The organization:a. Develops, documents, and disseminates to [Assignment: organization-defined organization-defined personnel or roles]: │ + │ │ │ 1. An access control policy that addresses purpose, scope, roles, responsibilities, management commitment, coordination among │ + │ │ ac-1 │ │ organizational entities, and compliance; and │ + │ │ 67dd59c4-0340-4aed-a49d-002815b50157 │ │ 2. Procedures to facilitate the implementation of the access control policy and associated access controls; and │ + │ │ │ b. Reviews and updates the current: │ + │ ac-2 │ │ 1. Access control policy [Assignment: organization-defined organization-defined frequency]; and │ + │ 663e7c26-3bfe-4c71-b423-10d8338d5445 │ │ 2. Access control procedures [Assignment: organization-defined organization-defined frequency]. │ + │ │ │ │ + │ ac-3 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ 07e1e996-5ae7-4b0b-b4c0-01f35729e442 │ ╭─────────────╮ + │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭─────────────╮ + │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ No items │ + │ │ │ │ + │ │ │ No items. │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/testdata/TestNewAssessmentResultsModel.golden b/src/internal/tui/component/testdata/TestComponentDefinitionBasicView.golden similarity index 50% rename from src/internal/tui/testdata/TestNewAssessmentResultsModel.golden rename to src/internal/tui/component/testdata/TestComponentDefinitionBasicView.golden index e838673b..5394da3f 100644 --- a/src/internal/tui/testdata/TestNewAssessmentResultsModel.golden +++ b/src/internal/tui/component/testdata/TestComponentDefinitionBasicView.golden @@ -1,59 +1,56 @@ -╭─────────────────────╮╭───────────────────╮╭────────────────────╮╭────────────────╮╭───────────────────────────╮╭─────────╮╭─────────╮ -│ ComponentDefinition ││ AssessmentResults ││ SystemSecurityPlan ││ AssessmentPlan ││ PlanOfActionAndMilestones ││ Catalog ││ Profile │ -┴─────────────────────┴┘ └┴────────────────────┴┴────────────────┴┴───────────────────────────┴┴─────────┴┴─────────┴───────────────────────────────────────────────────────────────── - ? toggle help - ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ - Selected Result │Lula Validation Result - 41787700-2a4c-…│ Compare Result │No Result Selected │ - ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ - ╭───────────────╮ ╭─────────╮ - │ Findings List ├─────────────────────────────╮ │ Summary ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - ╰───────────────╯ ╰─────────╯ - │ │ │ ⚠️ Summary Under Construction ⚠️ │ - │ 1 item │ │ │ - │ │ │ │ - │ ID-1 │ │ │ - │ not-satisfied │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭──────────────╮ - │ │ │ Observations ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰──────────────╯ - │ │ │ ⚠️ Observations Under Construction ⚠️ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ ↑/k up • ↓/j down • ↳ confirm • ? toggle │ │ │ - ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file + ←/h, →/l navigation • tab/shift+tab switch models • ? toggle help + ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ + Selected Component │Component Title - e8011225-75bc-43e5-98…│ Selected Framework │https://raw.githubusercontent.com/usnis…│ + ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ +╭───────────────╮ ╭─────────╮ +│ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +╰───────────────╯ ╰─────────╯ + │ │ │ │ + │ 4 items │ │ │ + │ │ │ │ + │ ac-1 │ │ │ + │ 84517036-ea65-4bfa-992d-f89a1b0d9822 │ │ │ + │ │ │ │ + │ ac-3 │ │ │ + │ 0d4fe96a-df1c-4199-87eb-cf0c1385e9ab │ │ │ + │ │ │ │ + │ ac-3.2 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ 2131d7da-19a3-462f-a0bd-2345e5098ea5 │ ╭─────────────╮ + │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ac-4 │ ╰─────────────╯ + │ 3ee0aedf-d047-4902-9d77-4d7f0072f213 │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭─────────────╮ + │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ No items │ + │ │ │ │ + │ │ │ No items. │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/component/testdata/TestComponentDefinitionComponentSwitch.golden b/src/internal/tui/component/testdata/TestComponentDefinitionComponentSwitch.golden new file mode 100644 index 00000000..597cceab --- /dev/null +++ b/src/internal/tui/component/testdata/TestComponentDefinitionComponentSwitch.golden @@ -0,0 +1,56 @@ + ↳ select • ↑/k move up • ↓/j move down • / filter • ? toggle help + ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ + Selected Component │Component B - 4cb1810c-d0d8-404e-b346-5…│ Selected Framework │https://raw.githubusercontent.com/usnis…│ + ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ +╭───────────────╮ ╭─────────╮ +│ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +╰───────────────╯ ╰─────────╯ + │ │ │ STATEMENT: │ + │ 3 items │ │ The information system enforces approved authorizations for controlling the flow of information within the system and between interconnected │ + │ │ │ systems based on [Assignment: organization-defined organization-defined information flow control policies]. │ + │ │ ac-4 │ │ │ + │ │ ea9f3b4d-64c2-4631-ace5-55428552f9aa │ │ │ + │ │ │ │ + │ ac-5 │ │ │ + │ 1976b301-115f-48a4-b847-3374aa3b98d5 │ │ │ + │ │ │ │ + │ ac-6 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ be129429-290b-4516-9390-f4d38067fbd0 │ ╭─────────────╮ + │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭─────────────╮ + │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ No items │ + │ │ │ │ + │ │ │ No items. │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/component/testdata/TestEditViewComponentDefinitionModel.golden b/src/internal/tui/component/testdata/TestEditViewComponentDefinitionModel.golden new file mode 100644 index 00000000..86722a19 --- /dev/null +++ b/src/internal/tui/component/testdata/TestEditViewComponentDefinitionModel.golden @@ -0,0 +1,56 @@ + e edit • ctrl+s save • ←/h, →/l navigation • tab/shift+tab switch models • ? toggle help + ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ + Selected Component │lula - A9D5204C-7E5B-4C43-BD49-34DF759B…│ Selected Framework │https://github.com/defenseunicorns/lula │ + ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ +╭───────────────╮ ╭─────────╮ +│ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +╰───────────────╯ ╰─────────╯ + │ │ │ Here are some remarks about this control. │ + │ 1 item │ │ test │ + │ │ │ │ + │ │ ID-1 │ │ │ + │ │ 42C2FFDC-5F05-44DF-A67F-EEC8660AEFFD │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭─────────────╮ + │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim │ + │ │ │ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in │ + │ │ │ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia │ + │ │ │ deserunt mollit anim id est laborum. │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ │ ╭─────────────╮ + │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ ╰─────────────╯ + │ │ │ │ + │ │ │ 1 item │ + │ │ │ │ + │ │ │ Validate pods with label foo=bar │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/model_test.go b/src/internal/tui/model_test.go index 89fa7e77..e3a3cfd6 100644 --- a/src/internal/tui/model_test.go +++ b/src/internal/tui/model_test.go @@ -7,159 +7,35 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/x/exp/teatest" "github.com/defenseunicorns/lula/src/internal/testhelpers" "github.com/defenseunicorns/lula/src/internal/tui" "github.com/defenseunicorns/lula/src/internal/tui/common" "github.com/muesli/termenv" ) -const timeout = time.Second * 20 +const ( + timeout = time.Second * 20 + maxRetries = 3 + height = common.DefaultHeight + width = common.DefaultWidth +) func init() { lipgloss.SetColorProfile(termenv.Ascii) - tea.Sequence() } -// TestNewComponentDefinitionModel tests that the NewOSCALModel creates the expected model from component definition file -func TestNewComponentDefinitionModel(t *testing.T) { +// TestNewOSCALModel tests that the NewOSCALModel creates the expected model from component definition file +func TestNewOSCALModel(t *testing.T) { tempLog := testhelpers.CreateTempFile(t, "log") defer os.Remove(tempLog.Name()) oscalModel := testhelpers.OscalFromPath(t, "../../test/unit/common/oscal/valid-component.yaml") model := tui.NewOSCALModel(oscalModel, "", tempLog) - testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(common.DefaultWidth, common.DefaultHeight)) - - if err := testModel.Quit(); err != nil { - t.Fatal(err) - } - - if testModel == nil { - t.Fatal("testModel is nil") - } - - fm := testModel.FinalModel(t, teatest.WithFinalTimeout(timeout)) - - teatest.RequireEqualOutput(t, []byte(fm.View())) -} - -// TestMultiComponentDefinitionModel tests that the NewOSCALModel creates the expected model from component definition file -// and checks the component selection overlay -> new component section -func TestMultiComponentDefinitionModel(t *testing.T) { - tempLog := testhelpers.CreateTempFile(t, "log") - defer os.Remove(tempLog.Name()) - - oscalModel := testhelpers.OscalFromPath(t, "../../test/unit/common/oscal/valid-multi-component.yaml") - model := tui.NewOSCALModel(oscalModel, "", tempLog) - testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(common.DefaultWidth, common.DefaultHeight)) - - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select component - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // enter component selection overlay - testModel.Send(tea.KeyMsg{Type: tea.KeyDown}) // navigate down - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // select new component, exit overlay - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select framework - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select control - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Open control - - if err := testModel.Quit(); err != nil { - t.Fatal(err) - } - - if testModel == nil { - t.Fatal("testModel is nil") - } - - fm := testModel.FinalModel(t, teatest.WithFinalTimeout(timeout)) - - teatest.RequireEqualOutput(t, []byte(fm.View())) -} - -// TestNewAssessmentResultsModel tests that the NewOSCALModel creates the expected model from assessment results file -func TestNewAssessmentResultsModel(t *testing.T) { - tempLog := testhelpers.CreateTempFile(t, "log") - defer os.Remove(tempLog.Name()) - - oscalModel := testhelpers.OscalFromPath(t, "../../test/unit/common/oscal/valid-assessment-results.yaml") - model := tui.NewOSCALModel(oscalModel, "", tempLog) - testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(common.DefaultWidth, common.DefaultHeight)) - - testModel.Send(tea.KeyMsg{Type: tea.KeyTab}) + msgs := []tea.Msg{} - if err := testModel.Quit(); err != nil { + err := testhelpers.RunTestModelView(t, model, msgs, timeout, maxRetries, height, width) + if err != nil { t.Fatal(err) } - - if testModel == nil { - t.Fatal("testModel is nil") - } - - fm := testModel.FinalModel(t, teatest.WithFinalTimeout(timeout)) - - teatest.RequireEqualOutput(t, []byte(fm.View())) -} - -// TestComponentControlSelect tests that the user can navigate to a control, select it, and see expected -// remarks, description, and validations -func TestComponentControlSelect(t *testing.T) { - tempLog := testhelpers.CreateTempFile(t, "log") - defer os.Remove(tempLog.Name()) - - oscalModel := testhelpers.OscalFromPath(t, "../../test/unit/common/oscal/valid-component.yaml") - model := tui.NewOSCALModel(oscalModel, "", tempLog) - testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(common.DefaultWidth, common.DefaultHeight)) - - // Navigate to the control - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select component - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select framework - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select control - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Open control - - if err := testModel.Quit(); err != nil { - t.Fatal(err) - } - - if testModel == nil { - t.Fatal("testModel is nil") - } - - fm := testModel.FinalModel(t, teatest.WithFinalTimeout(timeout)) - - teatest.RequireEqualOutput(t, []byte(fm.View())) -} - -// TestEditViewComponentDefinitionModel tests that the editing views of the component definition model are correct -func TestEditViewComponentDefinitionModel(t *testing.T) { - tempLog := testhelpers.CreateTempFile(t, "log") - defer os.Remove(tempLog.Name()) - tempOscalFile := testhelpers.CreateTempFile(t, "yaml") - defer os.Remove(tempOscalFile.Name()) - - oscalModel := testhelpers.OscalFromPath(t, "../../test/unit/common/oscal/valid-component.yaml") - model := tui.NewOSCALModel(oscalModel, tempOscalFile.Name(), tempLog) - - testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(common.DefaultWidth, common.DefaultHeight)) - - // Edit the remarks - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select component - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select framework - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Select control - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Open control - testModel.Send(tea.KeyMsg{Type: tea.KeyRight}) // Navigate to remarks - testModel.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) // Edit remarks - testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlE}) // Newline - testModel.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t', 'e', 's', 't'}}) // Add "test" to remarks - testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Open control - - if err := testModel.Quit(); err != nil { - t.Fatal(err) - } - - if testModel == nil { - t.Fatal("testModel is nil") - } - - fm := testModel.FinalModel(t, teatest.WithFinalTimeout(timeout)) - - teatest.RequireEqualOutput(t, []byte(fm.View())) } diff --git a/src/internal/tui/testdata/TestComponentControlSelect.golden b/src/internal/tui/testdata/TestComponentControlSelect.golden deleted file mode 100644 index 921e81ae..00000000 --- a/src/internal/tui/testdata/TestComponentControlSelect.golden +++ /dev/null @@ -1,59 +0,0 @@ -╭─────────────────────╮╭───────────────────╮╭────────────────────╮╭────────────────╮╭───────────────────────────╮╭─────────╮╭─────────╮ -│ ComponentDefinition ││ AssessmentResults ││ SystemSecurityPlan ││ AssessmentPlan ││ PlanOfActionAndMilestones ││ Catalog ││ Profile │ -┘ └┴───────────────────┴┴────────────────────┴┴────────────────┴┴───────────────────────────┴┴─────────┴┴─────────┴───────────────────────────────────────────────────────────────── - ↳ select • ↑/k move up • ↓/j move down • / filter • ? toggle help - ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ - Selected Component │lula - A9D5204C-7E5B-4C43-BD49-34DF759B…│ Selected Framework │https://github.com/defenseunicorns/lula │ - ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ - ╭───────────────╮ ╭─────────╮ - │ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - ╰───────────────╯ ╰─────────╯ - │ │ │ Here are some remarks about this control. │ - │ 1 item │ │ │ - │ │ │ │ - │ │ ID-1 │ │ │ - │ │ 42C2FFDC-5F05-44DF-A67F-EEC8660AEFFD │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭─────────────╮ - │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim │ - │ │ │ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in │ - │ │ │ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia │ - │ │ │ deserunt mollit anim id est laborum. │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭─────────────╮ - │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ │ - │ │ │ 1 item │ - │ │ │ │ - │ │ │ Validate pods with label foo=bar │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/testdata/TestEditViewComponentDefinitionModel.golden b/src/internal/tui/testdata/TestEditViewComponentDefinitionModel.golden deleted file mode 100644 index 3480acee..00000000 --- a/src/internal/tui/testdata/TestEditViewComponentDefinitionModel.golden +++ /dev/null @@ -1,59 +0,0 @@ -╭─────────────────────╮╭───────────────────╮╭────────────────────╮╭────────────────╮╭───────────────────────────╮╭─────────╮╭─────────╮ -│ ComponentDefinition ││ AssessmentResults ││ SystemSecurityPlan ││ AssessmentPlan ││ PlanOfActionAndMilestones ││ Catalog ││ Profile │ -┘ └┴───────────────────┴┴────────────────────┴┴────────────────┴┴───────────────────────────┴┴─────────┴┴─────────┴───────────────────────────────────────────────────────────────── - e edit • ctrl+s save • ←/h, →/l navigation • tab/shift+tab switch models • ? toggle help - ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ - Selected Component │lula - A9D5204C-7E5B-4C43-BD49-34DF759B…│ Selected Framework │https://github.com/defenseunicorns/lula │ - ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ - ╭───────────────╮ ╭─────────╮ - │ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - ╰───────────────╯ ╰─────────╯ - │ │ │ Here are some remarks about this control. │ - │ 1 item │ │ test │ - │ │ │ │ - │ │ ID-1 │ │ │ - │ │ 42C2FFDC-5F05-44DF-A67F-EEC8660AEFFD │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭─────────────╮ - │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim │ - │ │ │ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in │ - │ │ │ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia │ - │ │ │ deserunt mollit anim id est laborum. │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭─────────────╮ - │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ │ - │ │ │ 1 item │ - │ │ │ │ - │ │ │ Validate pods with label foo=bar │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/testdata/TestMultiComponentDefinitionModel.golden b/src/internal/tui/testdata/TestMultiComponentDefinitionModel.golden deleted file mode 100644 index 2eecc66d..00000000 --- a/src/internal/tui/testdata/TestMultiComponentDefinitionModel.golden +++ /dev/null @@ -1,59 +0,0 @@ -╭─────────────────────╮╭───────────────────╮╭────────────────────╮╭────────────────╮╭───────────────────────────╮╭─────────╮╭─────────╮ -│ ComponentDefinition ││ AssessmentResults ││ SystemSecurityPlan ││ AssessmentPlan ││ PlanOfActionAndMilestones ││ Catalog ││ Profile │ -┘ └┴───────────────────┴┴────────────────────┴┴────────────────┴┴───────────────────────────┴┴─────────┴┴─────────┴───────────────────────────────────────────────────────────────── - ↳ select • ↑/k move up • ↓/j move down • / filter • ? toggle help - ╭────────────────────────────────────────╮ ╭────────────────────────────────────────╮ - Selected Component │Component A - 7c02500a-6e33-44e0-82ee-f…│ Selected Framework │https://raw.githubusercontent.com/usnis…│ - ╰────────────────────────────────────────╯ ╰────────────────────────────────────────╯ - ╭───────────────╮ ╭─────────╮ - │ Controls List ├─────────────────────────────╮ │ Remarks ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - ╰───────────────╯ ╰─────────╯ - │ │ │ STATEMENT: │ - │ 3 items │ │ The organization:a. Develops, documents, and disseminates to [Assignment: organization-defined organization-defined personnel or roles]: │ - │ │ │ 1. An access control policy that addresses purpose, scope, roles, responsibilities, management commitment, coordination among │ - │ │ ac-1 │ │ organizational entities, and compliance; and │ - │ │ 67dd59c4-0340-4aed-a49d-002815b50157 │ │ 2. Procedures to facilitate the implementation of the access control policy and associated access controls; and │ - │ │ │ b. Reviews and updates the current: │ - │ ac-2 │ │ 1. Access control policy [Assignment: organization-defined organization-defined frequency]; and │ - │ 663e7c26-3bfe-4c71-b423-10d8338d5445 │ │ 2. Access control procedures [Assignment: organization-defined organization-defined frequency]. │ - │ │ │ │ - │ ac-3 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ 07e1e996-5ae7-4b0b-b4c0-01f35729e442 │ ╭─────────────╮ - │ │ │ Description ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ - │ │ ╭─────────────╮ - │ │ │ Validations ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ - │ │ ╰─────────────╯ - │ │ │ │ - │ │ │ No items │ - │ │ │ │ - │ │ │ No items. │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - ╰────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/src/internal/tui/testdata/TestNewComponentDefinitionModel.golden b/src/internal/tui/testdata/TestNewOSCALModel.golden similarity index 100% rename from src/internal/tui/testdata/TestNewComponentDefinitionModel.golden rename to src/internal/tui/testdata/TestNewOSCALModel.golden