Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions extensions/package-vulnerability-scanner/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
"dist/assets/index-CKUnK_gI.css": {
"checksum": "910900dfbf81f7ac4caf125b844a495c"
},
"dist/assets/index-BR_SAMDl.js": {
"checksum": "1641c59b4d18d7029985a3b98890a397"
"dist/assets/index-PKuzyQYK.js": {
"checksum": "bd612838b6ccf0c3a03241de2048eec3"
},
"dist/index.html": {
"checksum": "5bb81dbee09e839af32e17caedc8a436"
"checksum": "bdc6e932f6c401d4db24a1157178352a"
},
"main.py": {
"checksum": "ea913b00aaac84531ee46021969f4eaa"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// @vitest-environment happy-dom
import { describe, it, expect, beforeEach } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { mount } from "@vue/test-utils";

import ContentCard from "./ContentCard.vue";
import type { Content } from "../stores/scanner";
import { useVulnsStore } from "../stores/vulns";

function makeContent(overrides: Partial<Content> = {}): Content {
return {
guid: "g1",
title: "My Report",
bundle_id: "b1",
packages: [],
vulnerabilityCount: 0,
isLoadingPackages: false,
...overrides,
} as Content;
}

describe("ContentCard", () => {
beforeEach(() => {
setActivePinia(createPinia());
});

it("shows a Not scanned badge when the scan has failed", () => {
useVulnsStore().error = new Error(
"Couldn't fetch vulnerabilities from Package Manager.",
);

const wrapper = mount(ContentCard, {
props: { content: makeContent() },
});

expect(wrapper.text()).toContain("Not scanned");
expect(wrapper.text()).not.toContain("No vulnerabilities");
});

it("shows a Loading badge while the scan is in progress", () => {
// A fresh vulns store has not fetched yet, so the scan is still in progress.
const wrapper = mount(ContentCard, {
props: { content: makeContent() },
});

expect(wrapper.text()).toContain("Loading...");
});

it("shows the vulnerability count once the scan has completed", () => {
useVulnsStore().isFetched = true; // scan no longer in progress

const wrapper = mount(ContentCard, {
props: { content: makeContent({ vulnerabilityCount: 2 }) },
});

expect(wrapper.text()).toContain("2 vulnerabilities");
});

it("hides the scan badge entirely when this item's own packages failed to load", () => {
useVulnsStore().error = new Error("vuln service down");

const wrapper = mount(ContentCard, {
props: {
content: makeContent({ packageFetchError: new Error("boom") }),
},
});

expect(wrapper.text()).not.toContain("Not scanned");
expect(wrapper.text()).not.toContain("Loading...");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ function handleClick() {
Loading...
</ColorBadge>

<ColorBadge v-else-if="scannerStore.scanFailed" type="neutral">
Not scanned
</ColorBadge>

<ColorBadge v-else :type="hasVulnerabilities ? 'error' : 'success'">
{{ vulnerabilityText }}
</ColorBadge>
Expand Down Expand Up @@ -88,7 +92,7 @@ function handleClick() {
Incomplete content
</span>
<span v-else-if="content.packageFetchError" class="text-red-700">
Error loading packages
Couldn't load packages
</span>
<span v-else-if="content.isLoadingPackages" class="text-gray-600">
Loading packages...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @vitest-environment happy-dom
import { describe, it, expect, beforeEach } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { mount } from "@vue/test-utils";

import VulnerabilityChecker from "./VulnerabilityChecker.vue";
import type { Content } from "../stores/scanner";
import { useVulnsStore } from "../stores/vulns";
import { usePackagesStore } from "../stores/packages";

function makeContent(overrides: Partial<Content> = {}): Content {
return {
guid: "g1",
title: "My Report",
bundle_id: "b1",
packages: [],
vulnerabilityCount: 0,
isLoadingPackages: false,
...overrides,
} as Content;
}

describe("VulnerabilityChecker", () => {
beforeEach(() => {
setActivePinia(createPinia());
});

it("shows the specific reason from Connect when packages can't be read", () => {
const content = makeContent({
packageFetchError: new Error("Connect API error: Content not found"),
});

const wrapper = mount(VulnerabilityChecker, { props: { content } });

expect(wrapper.text()).toContain("Couldn't load this content's packages");
expect(wrapper.text()).toContain("Connect API error: Content not found");
});

it("shows the scan failure reason when the vuln lookup itself fails", () => {
// Unlike packageFetchError (this item's own packages), scanFailed reflects a
// store-wide failure (e.g. the vulnerability service), so it applies even
// though this item's packages loaded fine.
usePackagesStore().error = null;
useVulnsStore().error = new Error(
"Couldn't fetch vulnerabilities from Package Manager.",
);

const wrapper = mount(VulnerabilityChecker, {
props: { content: makeContent() },
});

expect(wrapper.text()).toContain("Scan couldn't complete");
expect(wrapper.text()).toContain(
"Couldn't fetch vulnerabilities from Package Manager.",
);
});

it("shows a loading state, not a verdict, while the vuln lookup is pending", () => {
// A fresh vulns store has not fetched yet, so the scan is still in progress
// even though this item's packages have loaded.
const wrapper = mount(VulnerabilityChecker, {
props: { content: makeContent() },
});

expect(wrapper.text()).toContain("Checking for vulnerabilities");
expect(wrapper.text()).not.toContain("No vulnerabilities found");
});

it("shows the result once the scan has completed", () => {
useVulnsStore().isFetched = true; // scan no longer in progress

const wrapper = mount(VulnerabilityChecker, {
props: { content: makeContent() },
});

expect(wrapper.text()).not.toContain("Checking for vulnerabilities");
expect(wrapper.text()).toContain("No vulnerabilities found");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,31 @@ const hasLanguageVersion = computed(() => {
/>

<StatusMessage
v-if="isIncomplete"
v-else-if="isIncomplete"
type="error"
message="This has not been fully deployed. Try publishing again."
/>

<StatusMessage
v-else-if="content.packageFetchError"
type="error"
message="Error analyzing packages. The content may not be fully deployed."
message="Couldn't load this content's packages"
:details="content.packageFetchError.message"
/>

<StatusMessage
v-else-if="scannerStore.scanFailed"
type="error"
message="Scan couldn't complete"
:details="scannerStore.scanError || undefined"
/>

<!-- The item's packages have loaded, but the vulnerability lookup runs as a
later step; wait for it so the view never shows a premature "0 vulnerabilities". -->
<LoadingSpinner
v-else-if="scannerStore.scanInProgress"
message="Checking for vulnerabilities..."
size="md"
/>

<!-- Content loaded successfully -->
Expand Down
Loading