Skip to content

Added API tests for the Latest SBOM Analysis endpoint.#949

Open
vobratil wants to merge 2 commits intoguacsec:mainfrom
vobratil:latest-tests
Open

Added API tests for the Latest SBOM Analysis endpoint.#949
vobratil wants to merge 2 commits intoguacsec:mainfrom
vobratil:latest-tests

Conversation

@vobratil
Copy link
Contributor

@vobratil vobratil commented Mar 9, 2026

This pull request adds a set of API tests for the Latest SBOM Analysis endpoint and refactors the uploadFiles method, so that it can be reused in tests.

Summary by Sourcery

Add end-to-end API test coverage for the Latest SBOM Analysis endpoint and introduce reusable helpers for uploading and cleaning up SBOM test data.

New Features:

  • Add basic Latest SBOM analysis tests covering CDX and SPDX products and components for older and latest SBOM versions.
  • Add issue-focused tests to validate Latest analysis behavior for specific ancestry and creator scenarios.

Enhancements:

  • Extract reusable API test helpers for uploading SBOM/advisory files, deleting SBOMs by ID, and constructing full SBOM file paths.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 9, 2026

Reviewer's Guide

Adds comprehensive end-to-end API tests for the /api/v2/analysis/latest/component endpoint (covering CycloneDX and SPDX SBOMs, older vs latest versions, and specific issue regressions) and refactors SBOM upload/teardown logic into reusable helpers that return created SBOM IDs and support cleanup.

File-Level Changes

Change Details Files
Refactor SBOM/advisory upload logic into a reusable helper that returns created resource IDs for later teardown.
  • Extract upload logic from the global setup into a new uploadFiles helper that streams files to `/api/v2/{sbom
advisory}with the correct content type.</li><li>Change the upload helper to collect and return theidfield from each POST response instead of just awaiting completion.</li><li>Introduce adeleteSbomshelper that deletes SBOMs by ID and tolerates missing SBOMs by logging 404s instead of failing tests.</li><li>Introduce agetFullSbomPaths` helper to construct absolute SBOM asset paths from a base directory and a list of filenames.
Add end-to-end "basic" latest-analysis tests that verify published timestamps and ancestor/descendant relationships for both CycloneDX and SPDX SBOMs, across older and latest versions.
  • Define SBOM fixture sets for CycloneDX and SPDX (older vs latest) and upload them per test suite using the new helpers, tracking returned SBOM IDs for cleanup.
  • Add CDX tests that query latest analysis by product CPE, component pURL (exact and partial), and component name (exact and partial), asserting the expected published timestamps at product, index, and binary levels via nested ancestors/descendants expectations.
  • Add SPDX tests that perform similar latest-analysis queries (CPE and partial pURL/name) and assert correct published timestamps for product and binary components.
  • Configure each describe block to run serially and register beforeAll/afterAll hooks to upload SBOMs once per suite and delete them afterward using collected IDs.
e2e/tests/api/features/analysis-latest-basic.ts
e2e/tests/api/helpers.ts
Add targeted "issue" latest-analysis tests that cover specific upstream-ancestor and creator-filtering edge cases tied to tracked bugs.
  • Introduce an "Top-level ancestor is 'upstream'" test suite that uploads dedicated SBOMs and asserts latest-analysis responses preserve the expected upstream-to-product ancestry both when querying by CPE and by partial pURL, using deep ancestors/descendants chains.
  • Introduce a "Creator is 'Red Hat'" test suite that uploads multiple ocp-tools SBOMs and verifies that latest-analysis results for a given component pURL exclude a product with incorrect creator metadata while still returning valid products, guarding against regression of TC-3278.
  • Annotate these tests with issue metadata in the Playwright test options to link them back to their corresponding Red Hat issue tracker tickets.
e2e/tests/api/features/analysis-latest-issues.ts
e2e/tests/api/helpers.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.79%. Comparing base (0dff4d0) to head (c55dfa2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #949      +/-   ##
==========================================
+ Coverage   61.76%   61.79%   +0.02%     
==========================================
  Files         207      207              
  Lines        3688     3688              
  Branches      836      836              
==========================================
+ Hits         2278     2279       +1     
+ Misses       1101     1099       -2     
- Partials      309      310       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vobratil vobratil marked this pull request as ready for review March 9, 2026 12:17
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@vobratil vobratil marked this pull request as draft March 9, 2026 14:43
@vobratil
Copy link
Contributor Author

vobratil commented Mar 9, 2026

I've just noticed an issue with the ingestion and cleanup of SPDX SBOMs. I'll have to make a few quick changes.

Signed-off-by: Vilem Obratil <vobratil@redhat.com>
@vobratil vobratil marked this pull request as ready for review March 9, 2026 15:20
@vobratil
Copy link
Contributor Author

vobratil commented Mar 9, 2026

Okay, we should be back in business.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new test files repeat very similar deeply nested expect.objectContaining/arrayContaining structures across many cases; consider extracting shared assertion helpers (e.g., expectCdxPublishedDates(...), expectSpdxPublishedDates(...)) to reduce duplication and make the intent clearer.
  • In the test suites you declare mutable arrays with var for SBOM IDs (e.g., var sbomIdsLatestBasicOlder: string[] = []); switching to block-scoped let or const would avoid potential scoping surprises and better reflect how they are used.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new test files repeat very similar deeply nested `expect.objectContaining`/`arrayContaining` structures across many cases; consider extracting shared assertion helpers (e.g., `expectCdxPublishedDates(...)`, `expectSpdxPublishedDates(...)`) to reduce duplication and make the intent clearer.
- In the test suites you declare mutable arrays with `var` for SBOM IDs (e.g., `var sbomIdsLatestBasicOlder: string[] = []`); switching to block-scoped `let` or `const` would avoid potential scoping surprises and better reflect how they are used.

## Individual Comments

### Comment 1
<location path="e2e/tests/api/features/analysis-latest-basic.ts" line_range="67" />
<code_context>
+  });
+
+  test.describe("CDX", () => {
+    test.describe("Older SBOM", () => {
+      test.beforeAll(async ({ axios }) => {
+        const fullSbomPaths = getFullSbomPaths(sbomDir, sbomsCdxOlder);
</code_context>
<issue_to_address>
**issue (testing):** Missing teardown for SPDX "Older SBOM" uploads, which can leave test data behind and influence other tests

In this SPDX block you collect `sbomIdsLatestBasicOlderSpdx` in `beforeAll` but never clean them up (unlike the CDX older SBOMs and SPDX latest SBOMs, which call `deleteSboms`). This can leave extra SBOMs in the system and interfere with later tests. Add an `afterAll` in this "Older SBOM" describe that calls `await deleteSboms(axios, sbomIdsLatestBasicOlderSpdx);` so the fixture remains isolated and repeatable.
</issue_to_address>

### Comment 2
<location path="e2e/tests/api/features/analysis-latest-issues.ts" line_range="39-30" />
<code_context>
+      await deleteSboms(axios, sbomIdsTopLevelAncestorIsUpstream);
+    });
+
+    test(
+      "Get product by CPE",
+      {
+        annotation: {
+          type: "issue",
+          description: "https://issues.redhat.com/browse/TC-3624",
+        },
+      },
+      async ({ axios }) => {
+        const urlEncodedComponentCpe = encodeURIComponent(
+          topLevelAncestorIsUpstreamProductCpe,
+        );
+
+        const response = await axios.get(
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen TC-3278 coverage by constraining the full result set, not just a few expected products

In the "Creator is 'Red Hat' / Get component by pURL" test, you only check that one unwanted product is absent and two expected products are present. The test would still pass if extra, unrelated products were included. To make it more robust, assert either the exact set of product names returned (e.g., `items.map(i => i.product_name)` equals `[4.13, 4.12]` in any order) or that every returned item’s `product_name` is in an allowed set.

Suggested implementation:

```typescript
    test(
      "Creator is 'Red Hat' / Get component by pURL",
      {
        annotation: {
          type: "issue",
          description: "https://issues.redhat.com/browse/TC-3278",
        },
      },
      async ({ axios }) => {
        const encodedPurl = encodeURIComponent(creatorIsRedHatComponentPurl);

        const response = await axios.get(
          `/api/v2/analysis/latest/component/${encodedPurl}?creator=Red%20Hat`,
        );

        const items = response.data.items ?? [];
        const productNames = items.map((item: { product_name?: string }) => item.product_name);

        const allowedProductNames = [
          "OpenShift Container Platform 4.12",
          "OpenShift Container Platform 4.13",
        ];

        // Ensure we got exactly the expected set of product names (no more, no less)
        expect(productNames).toHaveLength(allowedProductNames.length);
        expect(productNames).toEqual(expect.arrayContaining(allowedProductNames));

        // Extra safety: every returned product_name must be in the allowed set
        const allowedSet = new Set(allowedProductNames);
        for (const name of productNames) {
          expect(allowedSet.has(name)).toBe(true);
        }
      },
    );

```

If the exact product names or the excluded product (`4.11`) differ in your codebase, update `allowedProductNames` to match the real expected values.  
If your Jest/Playwright setup uses a stricter `items` type, you may want to replace the inline type `{ product_name?: string }` with the appropriate interface/type already defined in the tests.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

});

test.describe("CDX", () => {
test.describe("Older SBOM", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Missing teardown for SPDX "Older SBOM" uploads, which can leave test data behind and influence other tests

In this SPDX block you collect sbomIdsLatestBasicOlderSpdx in beforeAll but never clean them up (unlike the CDX older SBOMs and SPDX latest SBOMs, which call deleteSboms). This can leave extra SBOMs in the system and interfere with later tests. Add an afterAll in this "Older SBOM" describe that calls await deleteSboms(axios, sbomIdsLatestBasicOlderSpdx); so the fixture remains isolated and repeatable.

const fullSbomPaths = getFullSbomPaths(
sbomDir,
topLevelAncestorIsUpstreamSboms,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Strengthen TC-3278 coverage by constraining the full result set, not just a few expected products

In the "Creator is 'Red Hat' / Get component by pURL" test, you only check that one unwanted product is absent and two expected products are present. The test would still pass if extra, unrelated products were included. To make it more robust, assert either the exact set of product names returned (e.g., items.map(i => i.product_name) equals [4.13, 4.12] in any order) or that every returned item’s product_name is in an allowed set.

Suggested implementation:

    test(
      "Creator is 'Red Hat' / Get component by pURL",
      {
        annotation: {
          type: "issue",
          description: "https://issues.redhat.com/browse/TC-3278",
        },
      },
      async ({ axios }) => {
        const encodedPurl = encodeURIComponent(creatorIsRedHatComponentPurl);

        const response = await axios.get(
          `/api/v2/analysis/latest/component/${encodedPurl}?creator=Red%20Hat`,
        );

        const items = response.data.items ?? [];
        const productNames = items.map((item: { product_name?: string }) => item.product_name);

        const allowedProductNames = [
          "OpenShift Container Platform 4.12",
          "OpenShift Container Platform 4.13",
        ];

        // Ensure we got exactly the expected set of product names (no more, no less)
        expect(productNames).toHaveLength(allowedProductNames.length);
        expect(productNames).toEqual(expect.arrayContaining(allowedProductNames));

        // Extra safety: every returned product_name must be in the allowed set
        const allowedSet = new Set(allowedProductNames);
        for (const name of productNames) {
          expect(allowedSet.has(name)).toBe(true);
        }
      },
    );

If the exact product names or the excluded product (4.11) differ in your codebase, update allowedProductNames to match the real expected values.
If your Jest/Playwright setup uses a stricter items type, you may want to replace the inline type { product_name?: string } with the appropriate interface/type already defined in the tests.

Signed-off-by: Vilem Obratil <vobratil@redhat.com>
Copy link
Contributor

@matejnesuta matejnesuta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small suggestion.

`/api/v2/analysis/latest/component/${urlEncodedComponentCpe}?descendants=100`,
);

expect(response.data.items).toEqual(
Copy link
Contributor

@matejnesuta matejnesuta Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not that familiar with the test cases here, but I feel like this assertion could be generalized somehow.

`/api/v2/analysis/latest/component?ancestors=100&q=purl~${urlEncodedComponentPurl}&limit=100&offset=0`,
);

expect(response.data.items).toEqual(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as my previous comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants