mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
fix(compliance): discover universal frameworks from entry point (#12536)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
This commit is contained in:
co-authored by
alejandrobailo
parent
2f11b16299
commit
f19478f2f6
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user