fix(compliance): discover universal frameworks from entry point (#12536)

Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
This commit is contained in:
Pedro Martín
2026-08-27 09:17:39 +02:00
committed by GitHub
co-authored by alejandrobailo
parent 2f11b16299
commit f19478f2f6
25 changed files with 820 additions and 331 deletions
@@ -93,6 +93,7 @@ describe("getComplianceCatalog", () => {
watchlistCount: 0,
eligibleProviderTypes: [],
},
unavailable: true,
});
expect(fetchMock).not.toHaveBeenCalled();
});
@@ -163,7 +164,7 @@ describe("getComplianceCatalog", () => {
);
});
it("degrades to an empty catalog when the request fails", async () => {
it("flags an empty catalog as unavailable when the request fails", async () => {
fetchMock.mockResolvedValue(jsonResponse({ errors: [] }, 500));
const catalog = await getComplianceCatalog();
@@ -175,9 +176,21 @@ describe("getComplianceCatalog", () => {
watchlistCount: 0,
eligibleProviderTypes: [],
},
unavailable: true,
});
});
it("does not flag a catalog that is legitimately empty", async () => {
fetchMock.mockResolvedValue(
jsonResponse({ data: [], meta: { pagination: { page: 1, pages: 1 } } }),
);
const catalog = await getComplianceCatalog();
expect(catalog.entries).toEqual([]);
expect(catalog.unavailable).toBe(false);
});
it("degrades to an empty catalog when fetch throws", async () => {
fetchMock.mockRejectedValue(new Error("network down"));
@@ -186,7 +199,8 @@ describe("getComplianceCatalog", () => {
expect(catalog.entries).toEqual([]);
});
it("keeps the rest of the catalog when a single page fails", async () => {
it("flags the catalog as unavailable when a single page fails", async () => {
// Given - one of three catalog pages times out
fetchMock
.mockResolvedValueOnce(
jsonResponse(catalogPage("cis_1.4_aws", { page: 1, pages: 3 })),
@@ -196,12 +210,15 @@ describe("getComplianceCatalog", () => {
jsonResponse(catalogPage("iso27001_aws", { page: 3, pages: 3 })),
);
// When - the catalog keeps the pages it could read
const catalog = await getComplianceCatalog();
// Then - consumers know the merged result is incomplete
expect(catalog.entries.map((entry) => entry.complianceId)).toEqual([
"cis_1.4_aws",
"iso27001_aws",
]);
expect(catalog.unavailable).toBe(true);
});
it("bounds how many pages it requests at once", async () => {
@@ -12,6 +12,7 @@ import {
} from "@/lib/compliance/watchlist";
import type {
ComplianceCatalog,
ComplianceCatalogLoad,
ComplianceWatchlistActionResult,
ComplianceWatchlistBulkDiff,
ComplianceWatchlistTarget,
@@ -45,6 +46,12 @@ const EMPTY_CATALOG: ComplianceCatalog = {
meta: { totalEntries: 0, watchlistCount: 0, eligibleProviderTypes: [] },
};
// Gave up without an answer: distinct from a legitimately empty catalog.
const UNAVAILABLE_CATALOG: ComplianceCatalogLoad = {
...EMPTY_CATALOG,
unavailable: true,
};
const GENERIC_ERROR = "Could not update the compliance watchlist.";
const watchlistTargetSchema = z.object({
@@ -97,9 +104,9 @@ const buildCatalogUrl = (page: number, providerTypes?: string[]): string => {
export const getComplianceCatalog = async (
input: { providerTypes?: string[] } = {},
): Promise<ComplianceCatalog> => {
): Promise<ComplianceCatalogLoad> => {
const parsedInput = complianceCatalogInputSchema.safeParse(input);
if (!parsedInput.success) return EMPTY_CATALOG;
if (!parsedInput.success) return UNAVAILABLE_CATALOG;
const { providerTypes } = parsedInput.data;
@@ -127,15 +134,16 @@ export const getComplianceCatalog = async (
};
const firstPage = await fetchPage(1);
if (!firstPage) return EMPTY_CATALOG;
if (!firstPage) return UNAVAILABLE_CATALOG;
const pageCount = Math.min(
Math.max(1, firstPage.pageCount),
MAX_CATALOG_PAGES,
);
if (pageCount <= 1) return firstPage.catalog;
if (pageCount <= 1) return { ...firstPage.catalog, unavailable: false };
const rest: ComplianceCatalog[] = [];
let incomplete = firstPage.pageCount > MAX_CATALOG_PAGES;
for (let page = 2; page <= pageCount; page += CATALOG_FETCH_CONCURRENCY) {
const batch = await Promise.all(
Array.from(
@@ -143,13 +151,17 @@ export const getComplianceCatalog = async (
(_, index) => fetchPage(page + index),
),
);
if (batch.some((pageResult) => pageResult === null)) incomplete = true;
rest.push(...batch.flatMap((page) => (page ? [page.catalog] : [])));
}
return mergeCatalogPages([firstPage.catalog, ...rest]);
return {
...mergeCatalogPages([firstPage.catalog, ...rest]),
unavailable: incomplete,
};
} catch (error) {
console.error("Error fetching compliance catalog:", error);
return EMPTY_CATALOG;
return UNAVAILABLE_CATALOG;
}
};