+
+
+
+
-
+
diff --git a/extensions/package-vulnerability-scanner/src/stores/content.test.ts b/extensions/package-vulnerability-scanner/src/stores/content.test.ts
new file mode 100644
index 00000000..0c2b459e
--- /dev/null
+++ b/extensions/package-vulnerability-scanner/src/stores/content.test.ts
@@ -0,0 +1,78 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { setActivePinia, createPinia } from "pinia";
+
+import { useContentStore } from "./content";
+
+function mockFetch(impl: (url: string) => unknown) {
+ const fn = vi.fn(async (url: string) => impl(url));
+ vi.stubGlobal("fetch", fn);
+ return fn;
+}
+
+function ok(body: unknown) {
+ return { ok: true, status: 200, json: async () => body };
+}
+
+beforeEach(() => {
+ setActivePinia(createPinia());
+ vi.spyOn(console, "error").mockImplementation(() => {});
+});
+
+afterEach(() => {
+ vi.unstubAllGlobals();
+ vi.restoreAllMocks();
+});
+
+describe("content store fetchContentList", () => {
+ it("loads the content list and marks it loaded", async () => {
+ mockFetch(() => ok([{ guid: "g1", title: "A" }]));
+ const store = useContentStore();
+
+ await store.fetchContentList();
+
+ expect(store.contentList).toHaveLength(1);
+ expect(store.isContentLoaded).toBe(true);
+ expect(store.isLoading).toBe(false);
+ expect(store.error).toBeNull();
+ });
+
+ it("requests all content when showAllContent is set", async () => {
+ const fetchFn = mockFetch(() => ok([]));
+ const store = useContentStore();
+ store.showAllContent = true;
+
+ await store.fetchContentList();
+
+ expect(fetchFn).toHaveBeenCalledWith("api/content?show_all=true");
+ });
+
+ it("flags setupRequired and rethrows on a 424", async () => {
+ mockFetch(() => ({ ok: false, status: 424 }));
+ const store = useContentStore();
+
+ await expect(store.fetchContentList()).rejects.toThrow("424");
+ expect(store.setupRequired).toBe(true);
+ expect(store.error).not.toBeNull();
+ });
+
+ it("records the error without flagging setup on a non-424 failure", async () => {
+ mockFetch(() => ({ ok: false, status: 500 }));
+ const store = useContentStore();
+
+ await expect(store.fetchContentList()).rejects.toThrow("500");
+ expect(store.setupRequired).toBe(false);
+ expect(store.error).not.toBeNull();
+ });
+
+ it("skips a refetch once loaded unless forced", async () => {
+ const fetchFn = mockFetch(() => ok([{ guid: "g1", title: "A" }]));
+ const store = useContentStore();
+
+ await store.fetchContentList();
+ await store.fetchContentList();
+ expect(fetchFn).toHaveBeenCalledTimes(1);
+
+ await store.fetchContentList(true);
+ expect(fetchFn).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/extensions/package-vulnerability-scanner/src/stores/content.ts b/extensions/package-vulnerability-scanner/src/stores/content.ts
index d3a4745d..d80b819d 100644
--- a/extensions/package-vulnerability-scanner/src/stores/content.ts
+++ b/extensions/package-vulnerability-scanner/src/stores/content.ts
@@ -1,6 +1,8 @@
import { defineStore } from "pinia";
import { ref } from "vue";
+import { errorDetail } from "../lib/errorDetail";
+
export interface ContentListItem {
guid: string;
title: string;
@@ -20,6 +22,7 @@ export const useContentStore = defineStore("content", () => {
const contentList = ref
([]);
const isLoading = ref(false);
const error = ref(null);
+ const setupRequired = ref(false);
const showAllContent = ref(false);
// Track if content has been loaded for the current mode
@@ -33,6 +36,7 @@ export const useContentStore = defineStore("content", () => {
isLoading.value = true;
error.value = null;
+ setupRequired.value = false;
try {
const url = showAllContent.value
@@ -41,7 +45,14 @@ export const useContentStore = defineStore("content", () => {
const response = await fetch(url);
if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
+ // 424: the Connect Visitor API Key integration this app needs is not configured.
+ if (response.status === 424) setupRequired.value = true;
+ throw new Error(
+ await errorDetail(
+ response,
+ "Couldn't load your content from Connect",
+ ),
+ );
}
const data = await response.json();
@@ -61,6 +72,7 @@ export const useContentStore = defineStore("content", () => {
contentList,
isLoading,
error,
+ setupRequired,
isContentLoaded,
showAllContent,