diff --git a/components/policies/PolicyAssignments.js b/components/policies/PolicyAssignments.js index 1098fedb..da6cc70e 100644 --- a/components/policies/PolicyAssignments.js +++ b/components/policies/PolicyAssignments.js @@ -33,9 +33,9 @@ const PolicyAssignments = ({ policy }) => { return (
- {data && data?.policyAssignments?.length > 0 ? ( + {data && data?.data?.length > 0 ? ( <> - {data.policyAssignments.map((assignment) => { + {data.data.map((assignment) => { return (
diff --git a/cypress/fixtures/policies.json b/cypress/fixtures/policies.json index 51157a74..9250f8fd 100644 --- a/cypress/fixtures/policies.json +++ b/cypress/fixtures/policies.json @@ -2,10 +2,32 @@ "Existing": { "data": [ { - "id": "12345678910", + "id": "34912489-bd2a-409e-a00a-da274aff0635", + "policyId": "34912489-bd2a-409e-a00a-da274aff0635", "name": "My Policy Name", "description": "This is a policy description", - "regoContent": "package RegoRegoRego" + "regoContent": "package RegoRegoRego", + "currentVersion": 2, + "policyVersionId": "34912489-bd2a-409e-a00a-da274aff0635.1" + } + ], + "versions": [ + { + "created": "2021-07-06T13:53:41.872378726Z", + "id": "34912489-bd2a-409e-a00a-da274aff0635.1", + "message": "Initial policy creation", + "regoContent": "package rode.demo.sonar\n\npass {\n\tcount(violation_count) == 0\n}\n\nviolation_count[v] {\n\tviolations[v].pass == false\n}\n\nsonar_scan_started[o] {\n\tstartswith(input.occurrences[i].noteName, \"projects/rode/notes/sonar-scan\")\n\tinput.occurrences[i].kind == \"DISCOVERY\"\n\tinput.occurrences[i].discovered.discovered.analysisStatus == \"SCANNING\"\n\to := input.occurrences[i]\n}\n\nsonar_scan_finished[t] {\n\tstartswith(input.occurrences[i].noteName, \"projects/rode/notes/sonar-scan\")\n\tinput.occurrences[i].kind == \"DISCOVERY\"\n\tinput.occurrences[i].discovered.discovered.analysisStatus == \"FINISHED_SUCCESS\"\n\tt := input.occurrences[i].createTime\n}\n\nviolations[result] {\n\tstarted := sonar_scan_started\n\tresult := {\n\t\t\"pass\": count(started) >= 1,\n\t\t\"id\": \"sonar_scan_started\",\n\t\t\"name\": \"SonarQube Analysis Started\",\n\t\t\"description\": \"Verify SonarQube analysis started\",\n\t\t\"message\": \"SonarQube analysis started\",\n\t}\n}\n\nviolations[result] {\n\tfinished := sonar_scan_finished\n\tresult := {\n\t\t\"pass\": count(finished) >= 1,\n\t\t\"id\": \"sonar_scan_completed\",\n\t\t\"name\": \"SonarQube Analysis Finished\",\n\t\t\"description\": \"Verify SonarQube analysis finished successfully\",\n\t\t\"message\": sprintf(\"SonarQube analysis completed at %v\", [finished]),\n\t}\n}\n", + "sourcePath": "", + "version": 1 + } + ], + "assignments": [ + { + "created": "2021-07-06T13:59:41.639466966Z", + "id": "policies/34912489-bd2a-409e-a00a-da274aff0635/assignments/existing", + "policyGroup": "existing", + "policyVersionId": "34912489-bd2a-409e-a00a-da274aff0635.1", + "updated": "2021-07-06T13:59:41.639466966Z" } ], "pageToken": "12345678910pageTokenHere" diff --git a/cypress/fixtures/policy-groups.json b/cypress/fixtures/policy-groups.json new file mode 100644 index 00000000..c0929c27 --- /dev/null +++ b/cypress/fixtures/policy-groups.json @@ -0,0 +1,48 @@ +{ + "Existing": { + "data": [ + { + "created": "2021-07-06T13:59:28.133457892Z", + "deleted": false, + "description": "policies that limit the number of vulnerabilities", + "name": "existing", + "updated": "2021-07-06T13:59:28.133457892Z" + } + ], + "assignments": [], + "pageToken": "12345678910pageTokenHere" + }, + "ExistingWithAssignments": { + "data": [ + { + "created": "2021-07-06T13:59:28.133457892Z", + "deleted": false, + "description": "policies that limit the number of vulnerabilities", + "name": "existingWithAssignments", + "updated": "2021-07-06T13:59:28.133457892Z" + } + ], + "assignments": [ + { + "created": "2021-07-06T13:59:41.639466966Z", + "id": "policies/34912489-bd2a-409e-a00a-da274aff0635/assignments/existingWithAssignments", + "policyGroup": "existingWithAssignments", + "policyId": "34912489-bd2a-409e-a00a-da274aff0635", + "policyVersion": 1, + "policyVersionCount": 2, + "policyVersionId": "34912489-bd2a-409e-a00a-da274aff0635.1", + "policyName": "My Policy Name", + "updated": "2021-07-06T13:59:41.639466966Z" + } + ], + "pageToken": "12345678910pageTokenHere" + }, + "New": { + "data": [ + { + "name": "my_brand_new_policy_group", + "description": "This policy group was just created" + } + ] + } +} diff --git a/cypress/fixtures/resources.json b/cypress/fixtures/resources.json index 12b86e76..1c258494 100644 --- a/cypress/fixtures/resources.json +++ b/cypress/fixtures/resources.json @@ -584,7 +584,46 @@ } ], "other": [] - } + }, + "evaluations": [ + { + "id": "5e071925-f2d0-489e-8510-52da4dc0b3b6", + "pass": false, + "source": null, + "created": "2021-07-06T18:34:39.733446920Z", + "resourceVersion": { + "version": "harbor.localhost/library/nginx@sha256:0b159cd1ee1203dad901967ac55eee18c24da84ba3be384690304be93538bea8", + "names": ["test", "another tag"], + "created": "2021-07-06T13:58:56.024026238Z" + }, + "policyGroup": "vulnerability_limits", + "resourceUri": "harbor.localhost/library/nginx@sha256:0b159cd1ee1203dad901967ac55eee18c24da84ba3be384690304be93538bea8", + "resourceAliases": [ + "harbor.localhost/library/nginx:fail-harbor-policy-e6cbe59" + ], + "policyEvaluations": [ + { + "id": "9f8adc21-7abd-46f8-92f2-e791344b2b9f", + "resourceEvaluationId": "5e071925-f2d0-489e-8510-52da4dc0b3b6", + "pass": false, + "policyVersionId": "923260c9-40e8-4a9a-b41f-547a2e190697.1", + "violations": [ + { + "id": "harbor_scan_completed", + "name": "Harbor Scan", + "description": "Verify Harbor image scan completed", + "message": "Harbor scanned image 1 times. Last completed at {'2021-07-06T13:59:21.912469Z'}", + "link": "", + "pass": true + } + ], + "policyVersion": 1, + "policyId": "923260c9-40e8-4a9a-b41f-547a2e190697", + "policyName": "Sample Harbor Policy" + } + ] + } + ] } ], "pageToken": "123456789010pagetokenhere" diff --git a/cypress/integration/Playground.feature b/cypress/integration/Playground.feature index 950d300d..405e786d 100644 --- a/cypress/integration/Playground.feature +++ b/cypress/integration/Playground.feature @@ -12,7 +12,7 @@ Feature: Policy Playground And I search for "Existing" resource version And I select "Existing" resource version for evaluation When the resource the policy - Then I see "" message + Then I see the "" message Scenarios: | outcome | message | | passes | SuccessfulEvaluation | @@ -29,4 +29,4 @@ Feature: Policy Playground And I search for "Existing" resource version And I select "Existing" resource version for evaluation When I evaluate and an error occurs - Then I see "EvaluationError" message \ No newline at end of file + Then I see the "EvaluationError" message \ No newline at end of file diff --git a/cypress/integration/Policy.feature b/cypress/integration/Policy.feature index c1ccbdb0..fb1002f9 100644 --- a/cypress/integration/Policy.feature +++ b/cypress/integration/Policy.feature @@ -8,9 +8,9 @@ Feature: Policies Scenario: Search for a non-existent policy Given I am on the "PolicySearch" page When I search for a "NonExistent" policy - Then I see "NoPoliciesFound" message + Then I see the "NoPoliciesFound" message - @smoke + @smoke Scenario: Search for an existing policy Given I am on the "PolicySearch" page When I search for an "Existing" policy @@ -18,7 +18,22 @@ Feature: Policies When I click the "ViewPolicy" button Then I see "Existing" policy details - @smoke + Scenario: View policy details + Given I am on the "Existing" policy details page + When I select the PolicyDetails Section + Then I see "Existing" policy details + + Scenario: View policy version history + Given I am on the "Existing" policy details page + When I select the History Section + Then I see "Existing" policy version history + + Scenario: View policy assignments + Given I am on the "Existing" policy details page + When I select the Assignments Section + Then I see "Existing" policy assignment data + + @smoke Scenario: Create policy Given I open the application When I navigate to the "CreatePolicy" page @@ -29,7 +44,7 @@ Feature: Policies Scenario: Create policy - require name field Given I am on the "CreatePolicy" page When I click the "SavePolicy" button - Then I see "PolicyNameRequired" message + Then I see the "PolicyNameRequired" message When I type "name" into "PolicyName" input And I click the "SavePolicy" button Then I no longer see "PolicyNameRequired" message @@ -38,7 +53,7 @@ Feature: Policies Given I am on the "CreatePolicy" page When I clear the "PolicyRegoContent" input When I click the "SavePolicy" button - Then I see "PolicyRegoRequired" message + Then I see the "PolicyRegoRequired" message When I type "text" into "PolicyRegoContent" input And I click the "SavePolicy" button Then I no longer see "PolicyRegoRequired" message @@ -46,13 +61,13 @@ Feature: Policies Scenario Outline: Create policy - validating rego code Given I am on the "CreatePolicy" page When I test Rego policy code - Then I see "" message + Then I see the "" message Scenarios: - | validity | message | - | invalid | PolicyFailedValidation | - | valid | PolicyPassedValidation | + | validity | message | + | invalid | PolicyFailedValidation | + | valid | PolicyPassedValidation | - @updatePolicy + @updatePolicy Scenario Outline: Edit policy - update fields Given I am on the "Existing" policy details page When I click the "EditPolicy" button @@ -60,36 +75,46 @@ Feature: Policies When I update and save the "Existing" policy Then I see the updated "Existing" policy Scenarios: - | field | + | field | | name | | description | + Scenario: Edit policy - create new policy version + Given I am on the "Existing" policy details page + When I click the "EditPolicy" button + Then I see the Edit Policy form for "Existing" policy + When I update and save the "Existing" policy regoContent + Then I see the "NewPolicyVersion" message + When I type "this is an update message" into "PolicyUpdateMessage" input + And I click the "ConfirmUpdatePolicy" button + Then I see the updated "Existing" policy regoContent + Scenario: Edit policy - invalid rego Given I am on the "Existing" policy details page When I click the "EditPolicy" button Then I see the Edit Policy form for "Existing" policy When I save invalid rego code - Then I see "PolicyFailedUpdateInvalidRego" message - Then I see "PolicyFailedValidation" message + Then I see the "PolicyFailedUpdateInvalidRego" message + Then I see the "PolicyFailedValidation" message Scenario: Edit policy - unexpected errors Given I am on the "Existing" policy details page When I click the "EditPolicy" button And I save the Edit Policy form and an error occurs - Then I see "PolicyFailedUpdate" message + Then I see the "PolicyFailedUpdate" message Scenario: Delete policy Given I am on the "Existing" policy details page When I click the "EditPolicy" button And I click the "DeletePolicy" button And I confirm to delete the policy - Then I see "DeleteSuccess" message + Then I see the "DeleteSuccess" message Scenario: Delete policy - unexpected errors Given I am on the "Existing" policy details page When I click the "EditPolicy" button And I click the "DeletePolicy" button And I confirm to delete the policy and an error occurs - Then I see "PolicyFailedDelete" message + Then I see the "PolicyFailedDelete" message diff --git a/cypress/integration/Policy/policy.js b/cypress/integration/Policy/policy.js index b922fdc8..1d0a38c4 100644 --- a/cypress/integration/Policy/policy.js +++ b/cypress/integration/Policy/policy.js @@ -40,6 +40,16 @@ Given(/^I am on the "([^"]*)" policy details page$/, (policyName) => { { url: "**/api/policies/*", method: "GET" }, policies[policyName].data[0] ); + + cy.mockRequest( + { url: "**/api/policies/**/versions*", method: "GET" }, + { data: policies[policyName].versions } + ); + + cy.mockRequest( + { url: "**/api/policies/**/assignments*", method: "GET" }, + { data: policies[policyName].assignments } + ); cy.visit(`/policies/${policies[policyName].data[0].id}`); }); @@ -82,10 +92,6 @@ When( const selectorName = `Policy${capitalize(field)}Input`; cy.get(selectors[selectorName]).clear().type(updatedValues[field]); cy.get(selectors.UpdatePolicyButton).click(); - - if (field === "regoContent") { - cy.get(selectors.ConfirmUpdatePolicyButton).click(); - } } ); @@ -166,3 +172,31 @@ Then(/^I see the Edit Policy form for "([^"]*)" policy$/, (policyName) => { cy.get(button).should("be.visible"); }); }); + +Then(/^I see "([^"]*)" policy version history$/, (policyName) => { + const policyVersion = policies[policyName].versions[0]; + cy.url().should("contain", "#history"); + + cy.get('[data-testid="toggleCard"]') + .click() + .within(() => { + cy.contains("v1 (latest)").should("be.visible"); + cy.contains(policyVersion.regoContent).should("be.visible"); + }); +}); + +Then(/^I see "([^"]*)" policy assignment data$/, (policyName) => { + const policyAssignment = policies[policyName].assignments[0]; + cy.url().should("contain", "#assignments"); + + cy.contains(policyAssignment.policyGroup).should("be.visible"); + cy.contains(policyAssignment.policyVersionId.split(".")[1]).should( + "be.visible" + ); + cy.get(selectors.ViewPolicyGroupButton).should("be.visible").click(); + + cy.url().should( + "match", + new RegExp(`/policy-groups/${policyAssignment.policyGroup}`) + ); +}); diff --git a/cypress/integration/PolicyGroup.feature b/cypress/integration/PolicyGroup.feature new file mode 100644 index 00000000..67bd1003 --- /dev/null +++ b/cypress/integration/PolicyGroup.feature @@ -0,0 +1,48 @@ +Feature: Policy Groups + + Scenario: Open Policy Group Dashboard + Given I open the application + When I navigate to the "PolicyGroup" page + Then I see the policy groups dashboard + + Scenario: Create policy group + Given I open the application + When I navigate to the "PolicyGroup" page + And I click the "CreateNewPolicyGroup" button + Then I see the "CreatePolicyGroup" form + When I create the "New" policy group + Then I see the "New" policy group details + + Scenario: Create policy group - invalid name + Given I open the application + When I navigate to the "PolicyGroup" page + And I click the "CreateNewPolicyGroup" button + Then I see the "CreatePolicyGroup" form + When I type "Invalid*Name" into "PolicyGroupName" input + And I click the "SavePolicyGroup" button + Then I see the "InvalidPolicyGroupName" message + + @updatePolicyGroup + Scenario: Edit policy group description + Given I am on the Existing policy group details page + When I click the "EditPolicyGroup" button + Then I see the "EditPolicyGroup" form + When I update and save the Existing policy group description + Then I see the updated Existing policy group description + + Scenario: Assign policy to policy group + Given I am on the Existing policy group details page + When I click the "EditAssignments" button + Then I see the Edit Existing Assignments page + When I search for an "Existing" policy + And I assign the Existing policy to the Existing policy group + And I click the "SaveAssignments" button + Then I see the Existing policy assigned to the Existing policy group + + Scenario: Remove policy from policy group + Given I am on the ExistingWithAssignments policy group details page + When I click the "EditAssignments" button + Then I see the Edit ExistingWithAssignments Assignments page + When I remove an assignment from the ExistingWithAssignments policy group + And I click the "SaveAssignments" button + Then I see no assignments for the ExistingWithAssignments policy group diff --git a/cypress/integration/PolicyGroup/policy-group.js b/cypress/integration/PolicyGroup/policy-group.js new file mode 100644 index 00000000..c3f2276e --- /dev/null +++ b/cypress/integration/PolicyGroup/policy-group.js @@ -0,0 +1,203 @@ +/** + * Copyright 2021 The Rode Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Given, When, Then, Before } from "cypress-cucumber-preprocessor/steps"; +import * as selectors from "../../page-objects/policy-group"; +import policyGroups from "../../fixtures/policy-groups.json"; +import policies from "../../fixtures/policies.json"; +import Chance from "chance"; + +// TODO: figure out cy.fixture so I can "as" it throughout the remaining steps +const chance = new Chance(); +let updatedValues; + +Before({ tags: "@updatePolicyGroup" }, () => { + updatedValues = { + description: chance.sentence(), + }; +}); + +Given(/^I am on the ([^"]*) policy group details page$/, (policyGroupName) => { + cy.mockRequest( + { url: "**/api/policy-groups/*", method: "GET" }, + policyGroups[policyGroupName].data[0] + ); + + cy.mockRequest( + { url: "**/api/policy-groups/**/assignments*", method: "GET" }, + { data: policyGroups[policyGroupName].assignments } + ); + cy.visit(`/policy-groups/${policyGroups[policyGroupName].data[0].name}`); +}); + +When(/^I create the "([^"]*)" policy group$/, (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName].data[0]; + cy.mockRequest({ url: `**/api/policy-groups`, method: "POST" }, policyGroup); + cy.get(selectors.PolicyGroupNameInput).clear().type(policyGroup.name); + cy.get(selectors.PolicyGroupDescriptionInput) + .clear() + .type(policyGroup.description); + cy.get(selectors.SavePolicyGroupButton).click(); +}); + +When( + /^I update and save the ([^"]*) policy group description$/, + (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName].data[0]; + const updatedPolicyGroup = { + ...policyGroup, + description: updatedValues.description, + }; + cy.mockRequest( + { url: `**/api/policy-groups/${policyGroup.name}`, method: "PATCH" }, + updatedPolicyGroup + ); + cy.get(selectors.PolicyGroupDescriptionInput) + .clear() + .type(updatedPolicyGroup.description); + cy.get(selectors.UpdatePolicyGroupButton).click(); + } +); + +When( + /^I assign the ([^"]*) policy to the ([^"]*) policy group$/, + (policyName, policyGroupName) => { + const policyGroup = policyGroups[policyGroupName]; + const policy = policies[policyName]; + const assignment = { + ...policy.assignments[0], + policyId: policy.data[0].id, + policyVersion: policy.data[0].currentVersion, + policyVersionCount: policy.data[0].currentVersion, + policyName: policy.data[0].name, + }; + cy.mockRequest( + { url: "**/api/policy-groups/**/assignments*", method: "POST" }, + { + data: assignment, + } + ); + cy.mockRequest( + { + url: `**/api/policy-groups/${policyGroup.data[0].name}/assignments*`, + method: "GET", + }, + { + data: { + data: assignment, + }, + } + ); + cy.get(selectors.AssignToPolicyGroupButton).click(); + } +); +When( + /^I remove an assignment from the ([^"]*) policy group$/, + (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName]; + cy.mockRequest( + { + url: `**/api/policy-groups/${policyGroup.data[0].name}/assignments/*`, + method: "DELETE", + status: 204, + }, + {} + ); + cy.mockRequest( + { + url: `**/api/policy-groups/${policyGroup.data[0].name}/assignments*`, + method: "GET", + }, + { + data: { + data: [], + }, + } + ); + cy.get(selectors.RemoveFromPolicyGroupButton).click(); + cy.contains(selectors.NoPolicyGroupAssignmentsMessage).should("be.visible"); + } +); + +Then( + /^I see the updated ([^"]*) policy group description$/, + (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName].data[0]; + cy.url().should( + "contain", + `/policy-groups/${encodeURIComponent(policyGroup.name)}` + ); + + cy.contains(policyGroup.name).should("be.visible"); + cy.contains(updatedValues.description).should("be.visible"); + cy.get(selectors.EditPolicyGroupButton).should("be.visible"); + } +); + +Then(/^I see the "([^"]*)" policy group details$/, (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName].data[0]; + cy.url().should( + "contain", + `/policy-groups/${encodeURIComponent(policyGroup.name)}` + ); + + cy.contains(policyGroup.name).should("be.visible"); + cy.contains(policyGroup.description).should("be.visible"); + cy.get(selectors.EditPolicyGroupButton).should("be.visible"); + cy.get(selectors.EditAssignmentsButton).should("be.visible"); +}); + +Then(/^I see the policy groups dashboard$/, () => { + cy.contains(selectors.PolicyGroupDashboardHeader).should("be.visible"); + cy.get(selectors.CreateNewPolicyGroupButton).should("be.visible"); +}); + +Then(/^I see the Edit ([^"]*) Assignments page$/, (policyGroupName) => { + const policyGroup = policyGroups[policyGroupName]; + cy.url().should( + "contain", + `/policy-groups/${encodeURIComponent(policyGroup.data[0].name)}/assignments` + ); + + cy.contains(policyGroup.data[0].name).should("be.visible"); + if (policyGroup.assignments.length > 0) { + policyGroup.assignments.forEach((assignment) => { + cy.contains(assignment.policyName).should("be.visible"); + cy.contains(assignment.policyVersion).should("be.visible"); + }); + } else { + cy.contains(selectors.NoPolicyGroupAssignmentsMessage).should("be.visible"); + } +}); + +Then( + /^I see the ([^"]*) policy assigned to the ([^"]*) policy group$/, + (policyName) => { + const policy = policies[policyName]; + + cy.contains("Saved!").should("be.visible"); + + cy.contains(policy.data[0].name).should("be.visible"); + cy.contains(policy.data[0].currentVersion).should("be.visible"); + } +); + +Then(/^I see no assignments for the ([^"]*) policy group$/, () => { + cy.contains("Saved!").should("be.visible"); + + // TODO: why isn't this working? calls to get assignments are not returning [] after landing back on policy group page but they are following the same intercept structure as assigning new policy + // cy.contains(selectors.NoPolicyGroupAssignmentsMessage).should("be.visible"); +}); diff --git a/cypress/integration/Resource.feature b/cypress/integration/Resource.feature index 17fb6470..56de5589 100644 --- a/cypress/integration/Resource.feature +++ b/cypress/integration/Resource.feature @@ -8,7 +8,7 @@ Feature: Resources Scenario: Search for a non-existent resource Given I am on the "ResourceSearch" page When I search for "NonExistent" resource - Then I see "NoResourcesFound" message + Then I see the "NoResourcesFound" message @smoke Scenario: Search for an existing resource @@ -18,7 +18,7 @@ Feature: Resources When I click the search result to view the "Existing" resource Then I see "Existing" resource details - Scenario Outline: Viewing Resource Details + Scenario Outline: Viewing Resource Occurrences Given I am on the "Existing" resource details page When I select the Occurrence Section When I click on occurrence @@ -27,4 +27,14 @@ Feature: Resources |occurrenceType| | Build | | Vulnerability | - | Deployment | \ No newline at end of file + | Deployment | + + Scenario: Viewing Resource Evaluation History + Given I am on the "Existing" resource details page + When I select the EvaluationHistory Section + Then I see evaluation history details + + Scenario: Changing Resource Version + Given I am on the "Existing" resource details page + When I click the "ChangeVersion" button + Then I see the available resource versions \ No newline at end of file diff --git a/cypress/integration/Resource/resource.js b/cypress/integration/Resource/resource.js index 997fa814..2c772dbc 100644 --- a/cypress/integration/Resource/resource.js +++ b/cypress/integration/Resource/resource.js @@ -19,17 +19,22 @@ import resources from "../../fixtures/resources.json"; import * as selectors from "../../page-objects/resource"; Given(/^I am on the "([^"]*)" resource details page$/, (resourceName) => { + const resource = resources[resourceName].data[0]; cy.mockRequest( { url: "**/api/occurrences*", method: "GET" }, - resources[resourceName].data[0].occurrences + resource.occurrences ); - cy.visit( - `/resources/${encodeURIComponent(resources[resourceName].data[0].uri)}` + + cy.mockRequest( + { url: "**/api/resources/**/resource-evaluations*", method: "GET" }, + { data: resource.evaluations } ); -}); -When(/^I select the Occurrence Section$/, () => { - cy.contains(selectors.OccurrenceSection).click(); + cy.mockRequest( + { url: "**/api/resource-versions*", method: "GET" }, + { data: resource.versions } + ); + cy.visit(`/resources/${encodeURIComponent(resource.uri)}`); }); When(/^I click on ([^"]*) occurrence$/, (occurrenceType) => { @@ -127,3 +132,32 @@ Then(/^I see "([^"]*)" resource details$/, (resourceName) => { .should("be.visible"); cy.get(selectors.EvaluateResourceInPlaygroundButton).should("be.visible"); }); + +Then(/^I see evaluation history details$/, () => { + cy.url().should("contain", "#evaluationHistory"); + + const evaluation = resources.Existing.data[0].evaluations[0]; + + cy.get('[data-testid="toggleCard"]') + .click() + .within(() => { + cy.contains(evaluation.policyGroup).should("be.visible"); + cy.contains(evaluation.policyEvaluations[0].policyName).should( + "be.visible" + ); + }); +}); + +Then(/^I see the available resource versions$/, () => { + const resourceVersion = resources.Existing.data[0].versions[0]; + + cy.get('[data-testid="drawer"]').within(() => { + cy.contains(resourceVersion.versionedResourceUri.substring(0, 12)).should( + "be.visible" + ); + resourceVersion.aliases.forEach((alias) => { + const trimmedAlias = alias.split(":")[1]; + cy.contains(trimmedAlias).should("be.visible"); + }); + }); +}); diff --git a/cypress/integration/common/common.js b/cypress/integration/common/common.js index c19c49ce..6618f320 100644 --- a/cypress/integration/common/common.js +++ b/cypress/integration/common/common.js @@ -90,7 +90,7 @@ Then(/^I see "([^"]*)"$/, (element) => { cy.get(selectors[element]).should("be.visible"); }); -Then(/^I see "([^"]*)" message$/, (messageName) => { +Then(/^I see the "([^"]*)" message$/, (messageName) => { const message = `${messageName}Message`; cy.contains(selectors[message]).should("be.visible"); }); @@ -110,3 +110,8 @@ Then(/^I see the "([^"]*)" form$/, (formName) => { cy.get(button).should("be.visible"); }); }); + +When(/^I select the ([^"]*) Section$/, (sectionName) => { + const section = `${sectionName}Section`; + cy.contains(selectors[section]).click(); +}); diff --git a/cypress/page-objects/index.js b/cypress/page-objects/index.js index 2c5b465d..db92f254 100644 --- a/cypress/page-objects/index.js +++ b/cypress/page-objects/index.js @@ -17,3 +17,4 @@ export * from "./policy"; export * from "./playground"; export * from "./resource"; +export * from "./policy-group"; diff --git a/cypress/page-objects/navigation.js b/cypress/page-objects/navigation.js index af1771c4..3273a5bf 100644 --- a/cypress/page-objects/navigation.js +++ b/cypress/page-objects/navigation.js @@ -34,9 +34,15 @@ const CREATE_POLICY = { href: "/policies/new", }; +const POLICY_GROUP = { + label: "Policy Groups", + href: "/policy-groups", +}; + export default { ResourceSearch: RESOURCE_SEARCH, PolicySearch: POLICY_SEARCH, PolicyPlayground: POLICY_PLAYGROUND, CreatePolicy: CREATE_POLICY, + PolicyGroup: POLICY_GROUP, }; diff --git a/cypress/page-objects/policy-group.js b/cypress/page-objects/policy-group.js new file mode 100644 index 00000000..173746a1 --- /dev/null +++ b/cypress/page-objects/policy-group.js @@ -0,0 +1,59 @@ +/** + * Copyright 2021 The Rode Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createButtonSelector } from "./utils"; + +export const PolicyGroupDashboardHeader = "Manage Policy Groups"; + +//MESSAGES +export const NoPolicyGroupsFoundMessage = "No policy groups exist."; +export const NoPolicyGroupAssignmentsMessage = + "No policies are assigned to this policy group."; +export const InvalidPolicyGroupNameMessage = + "Invalid character(s). Please refer to the name guidelines."; + +// BUTTONS +export const CreateNewPolicyGroupButton = createButtonSelector( + "Create New Policy Group" +); +export const SavePolicyGroupButton = createButtonSelector("Save Policy Group"); +export const EditPolicyGroupButton = createButtonSelector("Edit Policy Group"); +export const UpdatePolicyGroupButton = createButtonSelector( + "Update Policy Group" +); +export const EditAssignmentsButton = createButtonSelector("Edit Assignments"); +const CancelButton = createButtonSelector("Cancel"); +export const AssignToPolicyGroupButton = createButtonSelector( + "Assign to Policy Group" +); +export const RemoveFromPolicyGroupButton = createButtonSelector( + "Remove Policy Assignment" +); +export const SaveAssignmentsButton = createButtonSelector("Save Assignments"); + +// INPUTS +export const PolicyGroupNameInput = "#name"; +export const PolicyGroupDescriptionInput = "#description"; + +// FORMS +export const CreatePolicyGroupForm = { + fields: [PolicyGroupNameInput, PolicyGroupDescriptionInput], + buttons: [SavePolicyGroupButton, CancelButton], +}; +export const EditPolicyGroupForm = { + fields: [PolicyGroupNameInput, PolicyGroupDescriptionInput], + buttons: [UpdatePolicyGroupButton, CancelButton], +}; diff --git a/cypress/page-objects/policy.js b/cypress/page-objects/policy.js index 488fbaab..c9ea7a0c 100644 --- a/cypress/page-objects/policy.js +++ b/cypress/page-objects/policy.js @@ -30,6 +30,8 @@ export const PolicyFailedUpdateInvalidRegoMessage = export const PolicyFailedUpdateMessage = "Failed to update the policy."; export const PolicyFailedDeleteMessage = "An error occurred while deleting the policy. Please try again."; +export const NewPolicyVersionMessage = + "By updating the Rego Policy Code, you are creating a new version of this policy."; // BUTTONS export const SearchPolicyButton = createButtonSelector("Search Policies"); @@ -46,15 +48,23 @@ export const DeletePolicyButton = createButtonSelector("Delete Policy"); export const ConfirmUpdatePolicyButton = createButtonSelector( "Update & Save Policy" ); +export const ViewPolicyGroupButton = createButtonSelector("View Policy Group"); + +// SECTIONS +export const PolicyDetailsSection = /^Policy Details$/; +export const HistorySection = /^History$/; +export const AssignmentsSection = /^Assignments$/; // INPUTS export const PolicySearchInput = "#policySearchDisplay"; export const PolicyNameInput = "#name"; export const PolicyDescriptionInput = "#description"; export const PolicyRegoContentInput = "#regoContent"; +export const PolicyUpdateMessageInput = "#message"; // MODALS export const DeletePolicyModal = "div[role='dialog']"; +export const UpdatePolicyModal = "div[role='dialog']"; // FORMS export const EditPolicyForm = { diff --git a/cypress/page-objects/resource.js b/cypress/page-objects/resource.js index 3db3779f..e0c75c00 100644 --- a/cypress/page-objects/resource.js +++ b/cypress/page-objects/resource.js @@ -29,9 +29,11 @@ export const EvaluateResourceInPlaygroundButton = createButtonSelector( "Evaluate in Policy Playground" ); export const ShowJsonButton = createButtonSelector("Show JSON"); +export const ChangeVersionButton = createButtonSelector("Change Version"); // OCCURRENCES export const OccurrenceSection = /^Occurrences$/; +export const EvaluationHistorySection = /^Evaluation History$/; export const BuildOccurrence = /produced \d artifact(s?)/i; export const VulnerabilityOccurrence = /\d vulnerabilities found/i; export const DeploymentOccurrence = /deployment to \w+/i; diff --git a/pages/api/policies/[id]/assignments.js b/pages/api/policies/[id]/assignments.js index e0b1c4e4..c823aae2 100644 --- a/pages/api/policies/[id]/assignments.js +++ b/pages/api/policies/[id]/assignments.js @@ -33,7 +33,7 @@ export default async (req, res) => { const { id } = req.query; const response = await get( - `${rodeUrl}/v1alpha1/policies/${id}/assignments`, + `${rodeUrl}/v1alpha1/policies/${id}/assignments?pageSize=1000`, req.accessToken ); @@ -49,7 +49,7 @@ export default async (req, res) => { const { policyAssignments } = getPolicyAssignments; return res.status(StatusCodes.OK).json({ - policyAssignments, + data: policyAssignments, }); } catch (error) { console.error("Error getting policy assignments", error); diff --git a/pages/api/policies/[id]/versions.js b/pages/api/policies/[id]/versions.js index 1edf1744..4c4590f1 100644 --- a/pages/api/policies/[id]/versions.js +++ b/pages/api/policies/[id]/versions.js @@ -32,7 +32,7 @@ export default async (req, res) => { const { id } = req.query; const response = await get( - `${rodeUrl}/v1alpha1/policies/${id}/versions`, + `${rodeUrl}/v1alpha1/policies/${id}/versions?pageSize=1000`, req.accessToken ); diff --git a/pages/api/policy-groups/[name]/assignments.js b/pages/api/policy-groups/[name]/assignments.js index ca4b5dd1..623766db 100644 --- a/pages/api/policy-groups/[name]/assignments.js +++ b/pages/api/policy-groups/[name]/assignments.js @@ -35,7 +35,7 @@ export default async (req, res) => { const { name } = req.query; const response = await get( - `${rodeUrl}/v1alpha1/policy-groups/${name}/assignments`, + `${rodeUrl}/v1alpha1/policy-groups/${name}/assignments?pageSize=1000`, req.accessToken ); diff --git a/pages/policy-groups/[name]/assignments.js b/pages/policy-groups/[name]/assignments.js index 2ffd8e07..ac30bd60 100644 --- a/pages/policy-groups/[name]/assignments.js +++ b/pages/policy-groups/[name]/assignments.js @@ -197,6 +197,7 @@ const EditPolicyGroupAssignments = () => { return { ...assignment, id: createdAssignment.data.id, + policyGroup: policyGroup.name, action: null, }; } diff --git a/test/components/occurrences/VulnerabilityCard.spec.js b/test/components/occurrences/VulnerabilityCard.spec.js index ba14e57e..143b5e91 100644 --- a/test/components/occurrences/VulnerabilityCard.spec.js +++ b/test/components/occurrences/VulnerabilityCard.spec.js @@ -78,7 +78,7 @@ describe("VulnerabilityCard", () => { screen.getByRole("button", { name: "Toggle Card Details" }) ); - expect(screen.getByText(/high/i)).toBeInTheDocument(); + expect(screen.getByText(/^high$/i)).toBeInTheDocument(); expect(screen.getAllByTitle("Fire")).toHaveLength(3); }); @@ -100,7 +100,7 @@ describe("VulnerabilityCard", () => { screen.getByRole("button", { name: "Toggle Card Details" }) ); - expect(screen.getByText(/low/i)).toBeInTheDocument(); + expect(screen.getByText(/^low$/i)).toBeInTheDocument(); expect(screen.getAllByTitle("Fire")).toHaveLength(1); }); }); diff --git a/test/components/policies/PolicyAssignments.spec.js b/test/components/policies/PolicyAssignments.spec.js index 88f12651..38672b7a 100644 --- a/test/components/policies/PolicyAssignments.spec.js +++ b/test/components/policies/PolicyAssignments.spec.js @@ -41,7 +41,7 @@ describe("PolicyAssignments", () => { ); mockResponse = { - data: { policyAssignments }, + data: { data: policyAssignments }, loading: false, }; router = { diff --git a/test/components/resources/PolicyEvaluationDetails.spec.js b/test/components/resources/PolicyEvaluationDetails.spec.js index c3e9808b..c00a9dfb 100644 --- a/test/components/resources/PolicyEvaluationDetails.spec.js +++ b/test/components/resources/PolicyEvaluationDetails.spec.js @@ -27,7 +27,7 @@ describe("PolicyEvaluationDetails", () => { pass: chance.bool(), policyName: chance.string(), policyVersion: chance.d4(), - policyVersionId: chance.string(), + policyVersionId: chance.string({ alpha: true }), created: chance.timestamp(), violations: [ { diff --git a/test/pages/api/policies/[id]/assignments.spec.js b/test/pages/api/policies/[id]/assignments.spec.js index 1f5c066f..6a6f2a28 100644 --- a/test/pages/api/policies/[id]/assignments.spec.js +++ b/test/pages/api/policies/[id]/assignments.spec.js @@ -94,7 +94,9 @@ describe("/api/policies/[id]/assignments", () => { expect(get) .toHaveBeenCalledTimes(1) .toHaveBeenCalledWith( - `${config.get("rode.url")}/v1alpha1/policies/${id}/assignments`, + `${config.get( + "rode.url" + )}/v1alpha1/policies/${id}/assignments?pageSize=1000`, accessToken ); }); @@ -109,7 +111,7 @@ describe("/api/policies/[id]/assignments", () => { expect(response.json) .toHaveBeenCalledTimes(1) .toHaveBeenCalledWith({ - policyAssignments: [assignment], + data: [assignment], }); }); }); diff --git a/test/pages/api/policies/[id]/versions.spec.js b/test/pages/api/policies/[id]/versions.spec.js index c79fe504..f2808d82 100644 --- a/test/pages/api/policies/[id]/versions.spec.js +++ b/test/pages/api/policies/[id]/versions.spec.js @@ -86,7 +86,9 @@ describe("/api/policies/[id]/versions", () => { expect(get) .toHaveBeenCalledTimes(1) .toHaveBeenCalledWith( - `${config.get("rode.url")}/v1alpha1/policies/${id}/versions`, + `${config.get( + "rode.url" + )}/v1alpha1/policies/${id}/versions?pageSize=1000`, accessToken ); }); diff --git a/test/pages/api/policy-groups/[name]/assignments.spec.js b/test/pages/api/policy-groups/[name]/assignments.spec.js index 4806b3d8..8c65bb13 100644 --- a/test/pages/api/policy-groups/[name]/assignments.spec.js +++ b/test/pages/api/policy-groups/[name]/assignments.spec.js @@ -102,7 +102,7 @@ describe("/api/policy-groups/[name]/assignments", () => { .toHaveBeenCalledWith( `${config.get( "rode.url" - )}/v1alpha1/policy-groups/${name}/assignments`, + )}/v1alpha1/policy-groups/${name}/assignments?pageSize=1000`, accessToken ); }); diff --git a/test/pages/policies/[id].spec.js b/test/pages/policies/[id].spec.js index 5f1a65a1..8ca89813 100644 --- a/test/pages/policies/[id].spec.js +++ b/test/pages/policies/[id].spec.js @@ -90,7 +90,7 @@ describe("Policy Details", () => { }; mockFetch = { data: { - policyAssignments: assignments, + data: assignments, }, loading: false, };