Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
ROX-35420: Decouple OCP plugin tests from CVE data (#21633)
  • Loading branch information
dvail committed Oct 2, 2026
commit 47ad18bf3674697879688d534f67119979a2bb7e
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,46 @@
"registry": "docker.io",
"remote": "cypress-test/image",
"tag": "v0.0.1",
"fullName": "docker.io/cypress-test/image:v0.0.1"
"fullName": "docker.io/cypress-test/image:v0.0.1",
"__typename": "ImageName"
},
"imageCVECountBySeverity": {
"critical": { "total": 2 },
"important": { "total": 0 },
"moderate": { "total": 0 },
"low": { "total": 0 },
"unknown": { "total": 0 }
"critical": {
"total": 0,
"__typename": "ResourceCountByFixability"
},
"important": {
"total": 2,
"__typename": "ResourceCountByFixability"
},
"moderate": {
"total": 0,
"__typename": "ResourceCountByFixability"
},
"low": {
"total": 0,
"__typename": "ResourceCountByFixability"
},
"unknown": {
"total": 0,
"__typename": "ResourceCountByFixability"
},
"__typename": "ResourceCountByCVESeverity"
},
"operatingSystem": "debian:11",
"operatingSystem": "debian:8",
"deploymentCount": 1,
"watchStatus": "NOT_WATCHED",
"metadata": { "v1": { "created": "2024-01-15T10:30:00Z" } },
"scanTime": "2024-06-01T12:00:00Z",
"metadata": {
"v1": {
"created": "2020-09-15T22:35:00.560827473Z",
"__typename": "V1Metadata"
},
"__typename": "ImageMetadata"
},
"scanTime": "2024-12-02T13:51:19.876935617Z",
"scanNotes": [],
"notes": []
"notes": [],
"__typename": "Image"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"data": {
"image": {
"id": "sha256:abcxyz",
"deploymentCount": 1,
"deployments": [
{
"id": "mock-deployment-001",
"name": "central",
"type": "Deployment",
"clusterName": "remote",
"namespace": "stackrox",
"created": "2024-03-01T17:45:32.757812569Z",
"__typename": "Deployment"
}
],
"__typename": "Image"
}
}
}
85 changes: 85 additions & 0 deletions ui/apps/platform/cypress/integration-ocp/routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { interceptAndWatchRequests, interceptRequests } from '../helpers/request';
import { hasFeatureFlag } from '../helpers/features';
import { toImageV2Response } from '../integration/vulnerabilities/workloadCves/WorkloadCves.helpers';

export const acsAuthNamespaceHeader = 'acs-auth-namespace-scope';

export const metadataRoute = 'metadata';
Expand Down Expand Up @@ -33,3 +37,84 @@ export const getCVEsForDeploymentRouteMatcher = {
method: 'POST',
url: '**/api-service/**/api/graphql?opname=getCvesForDeployment',
};

export function getOcpRouteMatcherMapForGraphQL<T extends string>(opnames: T[]) {
return Object.fromEntries(
opnames.map((opname) => [
opname,
{ method: 'POST' as const, url: `**/api-service/**/api/graphql?opname=${opname}` },
])
) as Record<T, { method: 'POST'; url: string }>;
}

type FixtureMap = Record<string, { fixture: string } | { body: unknown }>;

export function interceptOcpGraphQL(fixtureMap: FixtureMap) {
interceptRequests(getOcpRouteMatcherMapForGraphQL(Object.keys(fixtureMap)), fixtureMap);
}

export function watchOcpGraphQL(fixtureMap: FixtureMap) {
return interceptAndWatchRequests(
getOcpRouteMatcherMapForGraphQL(Object.keys(fixtureMap)),
fixtureMap
);
}

export function interceptWorkloadCveFixtures() {
const isFlattenImageData = hasFeatureFlag('ROX_FLATTEN_IMAGE_DATA');

interceptOcpGraphQL({
getImageCVEList: { fixture: 'vulnerabilities/workloadCves/getImageCVEList.json' },
getImageCveMetadata: { fixture: 'vulnerabilities/workloadCves/getImageCveMetadata.json' },
getImageCveSummaryData: {
fixture: 'vulnerabilities/workloadCves/getImageCveSummaryData.json',
},
getImagesForCVE: { fixture: 'vulnerabilities/workloadCves/getImagesForCVE.json' },
getImageList: { fixture: 'vulnerabilities/workloadCves/getImageList.json' },
});

// When ROX_FLATTEN_IMAGE_DATA is enabled, queries return ImageV2 types instead of
// Image types. Fixtures use the v1 shape, so we transform them for v2 compatibility.
// getImageDetails uses an alias (image: imageV2) so the root key stays `image`,
// but __typename must be ImageV2 for the fragment to match.
// getCVEsForImage and getImageResources use `imageV2` as the root key directly.
if (isFlattenImageData) {
cy.fixture('vulnerabilities/workloadCves/imageWithMultipleCves.json').then((v1Response) => {
interceptOcpGraphQL({
getImageDetails: {
body: {
data: {
image: {
...v1Response.data.image,
__typename: 'ImageV2',
},
},
},
},
});
});

const imageV2Fixtures = [
{
opname: 'getCVEsForImage',
fixture: 'vulnerabilities/workloadCves/multipleCvesForImage.json',
},
{
opname: 'getImageResources',
fixture: 'vulnerabilities/workloadCves/getImageResources.json',
},
];

imageV2Fixtures.forEach(({ opname, fixture }) => {
cy.fixture(fixture).then((v1Response) => {
interceptOcpGraphQL({ [opname]: { body: toImageV2Response(v1Response) } });
});
});
} else {
interceptOcpGraphQL({
getImageDetails: { fixture: 'vulnerabilities/workloadCves/imageWithMultipleCves.json' },
getCVEsForImage: { fixture: 'vulnerabilities/workloadCves/multipleCvesForImage.json' },
getImageResources: { fixture: 'vulnerabilities/workloadCves/getImageResources.json' },
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { selectors } from '../../integration/vulnerabilities/workloadCves/Worklo
import { selectors as vulnerabilitiesSelectors } from '../../integration/vulnerabilities/vulnerabilities.selectors';
import pf6 from '../../selectors/pf6';
import { getRouteMatcherMapForGraphQL, interactAndWaitForResponses } from '../../helpers/request';
import { interceptWorkloadCveFixtures } from '../routes';

function visitFirstCve() {
withOcpAuth();
Expand All @@ -23,6 +24,10 @@ function visitFirstCve() {
}

describe('Security vulnerabilities - CVE Detail page', () => {
beforeEach(() => {
interceptWorkloadCveFixtures();
});

it('should navigate to the CVE Detail page and account for the project filter', () => {
visitFirstCve().then(() => {
// Verify that "All projects" is selected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { assertVisibleTableColumns } from '../../helpers/tableHelpers';
import { selectors } from '../../integration/vulnerabilities/workloadCves/WorkloadCves.selectors';
import { selectors as vulnerabilitiesSelectors } from '../../integration/vulnerabilities/vulnerabilities.selectors';
import { selectProject } from '../../helpers/ocpConsole';
import { hasFeatureFlag } from '../../helpers/features';
import { toImageV2Response } from '../../integration/vulnerabilities/workloadCves/WorkloadCves.helpers';
import { acsAuthNamespaceHeader, interceptWorkloadCveFixtures, watchOcpGraphQL } from '../routes';

function visitImageDetailPage() {
withOcpAuth();
Expand All @@ -23,28 +26,46 @@ function visitImageDetailPage() {
}

describe('Security vulnerabilities - Image Detail page', () => {
beforeEach(() => {
interceptWorkloadCveFixtures();
});

it('should show the appropriate table columns on the workload resources tab', () => {
visitImageDetailPage()
.then(() => {
cy.get('button[role="tab"]:contains("Resources")').click();
visitImageDetailPage().then(() => {
cy.get('button[role="tab"]:contains("Resources")').click();

// By default, the project filter should be "All projects" which will show the Namespace column
const expectedColumns = ['Name', 'Namespace', 'Created'];
assertVisibleTableColumns('table', expectedColumns);
const resourcesFixture = 'vulnerabilities/workloadCves/getImageResources.json';
cy.fixture(resourcesFixture)
.then((v1Response) => {
const isFlattenImageData = hasFeatureFlag('ROX_FLATTEN_IMAGE_DATA');
const resourcesFixtures = {
getImageResources: {
body: isFlattenImageData ? toImageV2Response(v1Response) : v1Response,
},
};

// The user could also navigate to this page when viewing a project that has a workload containing the image.
// Grab the namespace of a known workload so we can select that project.
return cy
.get(`${selectors.firstTableRow} td[data-label="Namespace"]`)
.then(([$ns]) => Promise.resolve($ns.innerText));
})
.then((namespace) => {
// Select the project that has the workload containing the image and verify the columns
selectProject(namespace);
return watchOcpGraphQL(resourcesFixtures);
})
.then(({ waitForRequests }) => {
// We manually set 'stackrox' as the namespace for the first request
waitForRequests(['getImageResources']).then((interception) => {
const req = Array.isArray(interception) ? interception[0] : interception;
expect(req.request.headers[acsAuthNamespaceHeader]).to.equal('stackrox');
});

const expectedColumns = ['Name', 'Created'];
assertVisibleTableColumns('table', expectedColumns);
});
assertVisibleTableColumns('table', ['Name', 'Created']);

// Change to 'All Projects' to test the 'Namespace' column
selectProject('All Projects');

waitForRequests(['getImageResources']).then((interception) => {
const req = Array.isArray(interception) ? interception[0] : interception;
expect(req.request.headers[acsAuthNamespaceHeader]).to.equal('*');
});

assertVisibleTableColumns('table', ['Name', 'Namespace', 'Created']);
});
});
});

it('should navigate to the CVE Detail from the vulnerability table for the image', () => {
Expand All @@ -55,8 +76,9 @@ describe('Security vulnerabilities - Image Detail page', () => {
.click()
.then(([$cveLink]) => Promise.resolve($cveLink.innerText.replace('\n', '')));
})
.then((cveName) => {
cy.get('h1').contains(cveName);
.then(() => {
cy.get('div').contains('Affected images');
cy.get('div').contains('Images by severity');
});
});
});
Loading