Skip to content

Commit 864b6d3

Browse files
committed
LicenseExport feature CDX
1 parent 56d5583 commit 864b6d3

File tree

5 files changed

+546
-0
lines changed

5 files changed

+546
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
Feature: License Explorer
2+
As a Platform Eng
3+
I want to be able to download the licenses in a CSV file format from a specific SBOM
4+
5+
Background:
6+
7+
Scenario: Verify Download Licences option on SBOM Search Results page for CycloneDX SBOM
8+
Given User Searches for CycloneDX SBOM "<sbomName>" using Search Text box
9+
When User Selects CycloneDX SBOM "<sbomName>" from the Search Results
10+
And User Clicks on SBOM name hyperlink from the Search Results
11+
And User Clicks "Action" button
12+
Then "Download License Report" Option should be visible
13+
14+
Examples:
15+
| sbomName |
16+
| liboqs |
17+
18+
Scenario Outline: User Downloads license information for CycloneDX SBOM from SBOM Search Results page
19+
Given User Searches for CycloneDX SBOM "<sbomName>" using Search Text box
20+
When User Selects CycloneDX SBOM "<sbomName>" from the Search Results
21+
And User Clicks on SBOM name hyperlink from the Search Results
22+
And User Clicks "Action" button
23+
And Selects "Download License Report" option
24+
Then Licenses associated with the SBOM should be downloaded in TAR.GZ format using the SBOM name
25+
26+
Examples:
27+
| sbomName |
28+
| liboqs |
29+
30+
Scenario: Verify Download Licences option on SBOM Explorer page for CycloneDX SBOM
31+
Given User Searches for CycloneDX SBOM "<sbomName>" using Search Text box and Navigates to Search results page
32+
When User Selects CycloneDX SBOM "<sbomName>" from the Search Results
33+
And User Clicks on SBOM name hyperlink from the Search Results
34+
Then Application Navigates to SBOM Explorer page
35+
And User Clicks "Action" button
36+
And "Download License Report" Option should be visible
37+
38+
Examples:
39+
| sbomName |
40+
| liboqs |
41+
42+
Scenario: User Downloads license information for CycloneDX SBOM from SBOM Explorer page
43+
Given User is on SBOM Explorer page for the CycloneDX SBOM "<sbomName>"
44+
And User Clicks on "Download License Report" button
45+
Then Licenses associated with the SBOM should be downloaded in TAR.GZ format using the SBOM name
46+
47+
Examples:
48+
| sbomName |
49+
| liboqs |
50+
51+
Scenario: Verify the files on downloaded CycloneDX SBOM license TAR.GZ
52+
Given User has Downloaded the License information for CycloneDX SBOM "<sbomName>"
53+
When User extracts the Downloaded license TAR.GZ file
54+
Then Extracted files should contain two CSVs, one for Package license information and another one for License reference
55+
56+
Examples:
57+
| sbomName |
58+
| liboqs |
59+
60+
Scenario: Verify the headers on CycloneDX SBOM package License CSV file
61+
Given User extracted the CycloneDX SBOM "<sbomName>" license compressed file
62+
When User Opens the package license information file
63+
Then The file should have the following headers - SBOM name, SBOM id, package name, package group, package version, package purl, package cpe and license
64+
65+
Examples:
66+
| sbomName |
67+
| liboqs |
68+
69+
Scenario: Verify the headers on CycloneDX SBOM License reference CSV file
70+
Given User extracted the CycloneDX SBOM "<sbomName>" license compressed file
71+
When User Opens the license reference file
72+
Then The file should have the following headers - licenseId, name, extracted text and comment
73+
74+
Examples:
75+
| sbomName |
76+
| liboqs |
77+
78+
Scenario: Verify the contents on CycloneDX SBOM license reference CSV file
79+
Given User is on license reference "<sbomName>" file
80+
Then The License reference CSV should be empty
81+
82+
Examples:
83+
| sbomName |
84+
| liboqs |
85+
86+
Scenario: Verify the license information for a package on the CycloneDX SBOM with single license id
87+
Given User is on SBOM license information file for "<sbomName>"
88+
When User selects the package "<packageName>" with Single license id
89+
Then "SBOM name" column should match "<name>" from SBOM
90+
And "SBOM id" column should match "<id>" from SBOM
91+
And "package name" column should match "<packageName>" from SBOM
92+
And "package group" column should match "<packageGroup>" from SBOM
93+
And "package version" column should match "<packageVersion>" from SBOM
94+
And "package purl" column should match "<packagePurl>" from SBOM
95+
And "license" column should match "<license>" from SBOM
96+
And "package cpe" column should be empty
97+
98+
Examples:
99+
| sbomName | packageName | name | id | packageGroup | packageVersion | packagePurl | license |
100+
| Red Hat build of Quarkus | quarkus-bom | Red Hat build of Quarkus | urn:uuid:ed823a98-ef10-395b-a95d-eea9ef659984/1 | com.redhat.quarkus.platform | 2.13.9.SP2-redhat-00003 | pkg:maven/com.redhat.quarkus.platform/quarkus-bom@2.13.9.SP2-redhat-00003?type=pom pkg:maven/com.redhat.quarkus.platform/quarkus-bom@2.13.9.SP2-redhat-00003?repository_url=https://maven.repository.redhat.com/ga/&type=pom | Apache-2.0 |
101+
102+
Scenario: Verify the license information for a package on the CycloneDX SBOM with single license id with alternate package reference
103+
Given User is on SBOM license information file for "<sbomName>"
104+
When User selects a package "<packageName>" with Single license id with cpe information
105+
Then "SBOM name" column should match "<name>" from SBOM
106+
And "SBOM id" column should match "<id>" from SBOM
107+
And "package name" column should match "<packageName>" from SBOM
108+
And "package group" column should match "<packageGroup>" from SBOM
109+
And "package version" column should match "<packageVersion>" from SBOM
110+
And "package purl" column should match "<packagePurl>" from SBOM
111+
And "license" column should match "<license>" from SBOM
112+
And "package cpe" column should match "<cpe>" from SBOM
113+
114+
Examples:
115+
| sbomName | packageName | name | id | packageGroup | packageVersion | packagePurl | license | cpe |
116+
| quarkus-bom-3.8.3.redhat-00003 | quarkus-bom | quarkus-bom-3.8.3.redhat-00003 | https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.8.3.redhat-00003 | | 3.8.3.redhat-00003 | pkg:maven/com.redhat.quarkus.platform/quarkus-bom@3.8.3.redhat-00003?repository_url=https://maven.repository.redhat.com/ga/&type=pom | Apache-2.0 | cpe:/a:redhat:quarkus:3.8:*:el8:* |
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { createBdd } from "playwright-bdd";
2+
import * as fs from "node:fs";
3+
4+
import { test } from "../../fixtures";
5+
6+
import { expect } from "../../assertions";
7+
8+
import { SbomListPage } from "../../pages/sbom-list/SbomListPage";
9+
import { SbomDetailsPage } from "../../pages/sbom-details/SbomDetailsPage";
10+
import { DetailsPage } from "../../helpers/DetailsPage";
11+
import { SearchPage } from "../../helpers/SearchPage";
12+
import { clickAndVerifyDownload } from "../../pages/Helpers";
13+
import {
14+
downloadLicenseReport,
15+
extractLicenseReport,
16+
findCsvWithHeader,
17+
findPackageRow,
18+
} from "../../pages/LicenseExportHelpers";
19+
20+
export const { Given, When, Then } = createBdd(test);
21+
22+
let downloadedFilename: string;
23+
let downloadedFilePath: string;
24+
let extractionPath: string;
25+
let packageLicenseFilePath: string;
26+
let licenseReferenceFilePath: string;
27+
let selectedPackageRow: Record<string, string>;
28+
29+
Given(
30+
"User Searches for CycloneDX SBOM {string} using Search Text box",
31+
async ({ page }, sbomName: string) => {
32+
const listPage = await SbomListPage.build(page);
33+
const toolbar = await listPage.getToolbar();
34+
await toolbar.applyFilter({ "Filter text": sbomName });
35+
},
36+
);
37+
38+
Given(
39+
"User Searches for CycloneDX SBOM {string} using Search Text box and Navigates to Search results page",
40+
async ({ page }, sbomName: string) => {
41+
const searchPage = new SearchPage(page, "Search");
42+
await searchPage.generalSearch("SBOMs", sbomName);
43+
},
44+
);
45+
46+
When(
47+
"User Selects CycloneDX SBOM {string} from the Search Results",
48+
async ({ page }, sbomName: string) => {
49+
const listPage = await SbomListPage.fromCurrentPage(page);
50+
const table = await listPage.getTable();
51+
await table.waitUntilDataIsLoaded();
52+
53+
await expect(table).toHaveColumnWithValue("Name", sbomName);
54+
},
55+
);
56+
57+
When(
58+
"User Clicks on SBOM name hyperlink from the Search Results",
59+
async ({ page }) => {
60+
const listPage = await SbomListPage.fromCurrentPage(page);
61+
const table = await listPage.getTable();
62+
const rows = await table.getRows();
63+
await rows.first().getByRole("link").click();
64+
},
65+
);
66+
67+
Then("Application Navigates to SBOM Explorer page", async ({ page }) => {
68+
await expect(page).toHaveURL(/\/sboms\/[^/]+/);
69+
});
70+
71+
When('User Clicks "Action" button', async ({ page }) => {
72+
const detailsPage = new DetailsPage(page);
73+
await detailsPage.openActionsMenu();
74+
});
75+
76+
Then('"Download License Report" Option should be visible', async ({ page }) => {
77+
const detailsPage = new DetailsPage(page);
78+
await detailsPage.verifyActionIsVisibleInMenu("Download License Report");
79+
});
80+
81+
When('Selects "Download License Report" option', async ({ page }) => {
82+
const detailsPage = new DetailsPage(page);
83+
downloadedFilename = await clickAndVerifyDownload(
84+
page,
85+
async () =>
86+
await detailsPage.page
87+
.getByRole("menuitem", { name: "Download License Report" })
88+
.click(),
89+
);
90+
});
91+
92+
Then(
93+
"Licenses associated with the SBOM should be downloaded in TAR.GZ format using the SBOM name",
94+
async ({ page }) => {
95+
const sbomName = await page.getByRole("heading").first().innerText();
96+
expect(downloadedFilename).toContain(sbomName);
97+
expect(downloadedFilename.endsWith(".tar.gz")).toBeTruthy();
98+
},
99+
);
100+
101+
Given(
102+
"User is on SBOM Explorer page for the CycloneDX SBOM {string}",
103+
async ({ page }, sbomName: string) => {
104+
await SbomDetailsPage.build(page, sbomName);
105+
},
106+
);
107+
108+
When('User Clicks on "Download License Report" button', async ({ page }) => {
109+
const detailsPage = new DetailsPage(page);
110+
await detailsPage.openActionsMenu();
111+
112+
downloadedFilename = await clickAndVerifyDownload(
113+
page,
114+
async () =>
115+
await detailsPage.page
116+
.getByRole("menuitem", { name: "Download License Report" })
117+
.click(),
118+
);
119+
});
120+
121+
Given(
122+
"User has Downloaded the License information for CycloneDX SBOM {string}",
123+
async ({ page }, sbomName: string) => {
124+
await SbomDetailsPage.build(page, sbomName);
125+
downloadedFilePath = await downloadLicenseReport(page);
126+
},
127+
);
128+
129+
When("User extracts the Downloaded license TAR.GZ file", async () => {
130+
extractionPath = await extractLicenseReport(downloadedFilePath);
131+
});
132+
133+
Then(
134+
"Extracted files should contain two CSVs, one for Package license information and another one for License reference",
135+
async () => {
136+
const files = fs.readdirSync(extractionPath);
137+
const csvFiles = files.filter((file) => file.endsWith(".csv"));
138+
expect(csvFiles.length).toBe(2);
139+
},
140+
);
141+
142+
Given(
143+
"User extracted the CycloneDX SBOM {string} license compressed file",
144+
async ({ page }, sbomName: string) => {
145+
await SbomDetailsPage.build(page, sbomName);
146+
downloadedFilePath = await downloadLicenseReport(page);
147+
extractionPath = await extractLicenseReport(downloadedFilePath);
148+
},
149+
);
150+
151+
When("User Opens the package license information file", async () => {
152+
packageLicenseFilePath = findCsvWithHeader(
153+
extractionPath,
154+
"SBOM name",
155+
"Package license information file",
156+
);
157+
});
158+
159+
Then(
160+
"The file should have the following headers - SBOM name, SBOM id, package name, package group, package version, package purl, package cpe and license",
161+
async () => {
162+
const content = fs.readFileSync(packageLicenseFilePath, "utf-8");
163+
const headers = content.split("\n")[0].trim();
164+
const expectedHeaders =
165+
'"SBOM name"\t"SBOM id"\t"package name"\t"package group"\t"package version"\t"package purl"\t"package cpe"\t"license"\t"license type"';
166+
expect(headers).toBe(expectedHeaders);
167+
},
168+
);
169+
170+
When("User Opens the license reference file", async () => {
171+
licenseReferenceFilePath = findCsvWithHeader(
172+
extractionPath,
173+
"licenseId",
174+
"License reference file",
175+
);
176+
});
177+
178+
Then(
179+
"The file should have the following headers - licenseId, name, extracted text and comment",
180+
async () => {
181+
const content = fs.readFileSync(licenseReferenceFilePath, "utf-8");
182+
const headers = content.split("\n")[0].trim();
183+
const expectedHeaders = '"licenseId"\t"name"\t"extracted text"\t"comment"';
184+
expect(headers).toBe(expectedHeaders);
185+
},
186+
);
187+
188+
Given(
189+
"User is on license reference {string} file",
190+
async ({ page }, sbomName: string) => {
191+
await SbomDetailsPage.build(page, sbomName);
192+
downloadedFilePath = await downloadLicenseReport(page);
193+
extractionPath = await extractLicenseReport(downloadedFilePath);
194+
licenseReferenceFilePath = findCsvWithHeader(
195+
extractionPath,
196+
"licenseId",
197+
"License reference file",
198+
);
199+
},
200+
);
201+
202+
Then("The License reference CSV should be empty", async () => {
203+
const content = fs.readFileSync(licenseReferenceFilePath, "utf-8");
204+
const lines = content.trim().split("\n");
205+
// Only header should be present
206+
expect(lines.length).toBe(1);
207+
});
208+
209+
Given(
210+
"User is on SBOM license information file for {string}",
211+
async ({ page }, sbomName: string) => {
212+
await SbomDetailsPage.build(page, sbomName);
213+
downloadedFilePath = await downloadLicenseReport(page);
214+
extractionPath = await extractLicenseReport(downloadedFilePath);
215+
packageLicenseFilePath = findCsvWithHeader(
216+
extractionPath,
217+
"SBOM name",
218+
"Package license information file",
219+
);
220+
},
221+
);
222+
223+
When(
224+
"User selects the package {string} with Single license id",
225+
async ({ page: _page }, packageName: string) => {
226+
selectedPackageRow = findPackageRow(packageLicenseFilePath, packageName);
227+
},
228+
);
229+
230+
When(
231+
"User selects a package {string} with Single license id with cpe information",
232+
async ({ page: _page }, packageName: string) => {
233+
selectedPackageRow = findPackageRow(packageLicenseFilePath, packageName);
234+
},
235+
);
236+
237+
Then(
238+
"{string} column should match {string} from SBOM",
239+
async ({ page: _page }, columnName: string, expectedValue: string) => {
240+
const actual = selectedPackageRow[columnName].replace(/\n/g, " ");
241+
expect(actual).toBe(expectedValue);
242+
},
243+
);
244+
245+
Then(
246+
"{string} column should be empty",
247+
async ({ page: _page }, columnName: string) => {
248+
expect(selectedPackageRow[columnName]).toBe("");
249+
},
250+
);

e2e/tests/ui/helpers/DetailsPage.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export class DetailsPage {
2222
await this.page.getByRole("menuitem", { name: actionName }).click();
2323
}
2424

25+
async openActionsMenu() {
26+
const actionsButton = this.page.getByRole("button", { name: "Actions" });
27+
await actionsButton.click();
28+
await expect(actionsButton).toHaveAttribute("aria-expanded", "true");
29+
}
30+
2531
async clickOnPageButton(buttonName: string) {
2632
await this.page.getByRole("button", { name: buttonName }).click();
2733
}
@@ -37,6 +43,12 @@ export class DetailsPage {
3743
).toBeVisible();
3844
}
3945

46+
async verifyActionIsVisibleInMenu(actionName: string) {
47+
await expect(
48+
this.page.getByRole("menuitem", { name: actionName }),
49+
).toBeVisible();
50+
}
51+
4052
async verifyButtonIsVisible(button: string) {
4153
await expect(this.page.getByRole("button", { name: button })).toBeVisible();
4254
}

0 commit comments

Comments
 (0)