Files
prowler/ui/actions/attack-paths/queries.adapter.test.ts

147 lines
4.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { DOCS_URLS } from "@/lib/external-urls";
import { isCloud } from "@/lib/shared/env";
import {
ATTACK_PATH_QUERY_IDS,
type AttackPathQuery,
type AttackPathQueryParameter,
type AttackPathQueryResultSummary,
} from "@/types/attack-paths";
import {
adaptAttackPathQueriesResponse,
buildAttackPathQueries,
} from "./queries.adapter";
vi.mock("@/lib/shared/env", () => ({ isCloud: vi.fn() }));
// Empty-query filtering only applies in Prowler Cloud; default the flag on for
// the filtering suite and cover the self-hosted (flag off) case explicitly.
beforeEach(() => {
vi.mocked(isCloud).mockReturnValue(true);
});
const presetQuery: AttackPathQuery = {
type: "attack-paths-scans",
id: "preset-query",
attributes: {
name: "Preset Query",
short_description: "Returns privileged attack paths",
description: "Returns privileged attack paths.",
provider: "aws",
attribution: null,
parameters: [],
},
};
const makeQuery = (
id: string,
overrides: {
result_summary?: AttackPathQueryResultSummary | null;
parameters?: AttackPathQueryParameter[];
} = {},
): AttackPathQuery => ({
type: "attack-paths-scans",
id,
attributes: {
name: id,
short_description: "",
description: "",
provider: "aws",
attribution: null,
parameters: overrides.parameters ?? [],
result_summary: overrides.result_summary,
},
});
describe("adaptAttackPathQueriesResponse filtering", () => {
it("keeps only queries with data, plus everything without a definite empty verdict", () => {
const response = {
data: [
makeQuery("has-data", {
result_summary: { status: "ok", has_data: true },
}),
makeQuery("empty", {
result_summary: { status: "ok", has_data: false },
}),
makeQuery("errored", {
result_summary: { status: "error", has_data: null },
}),
makeQuery("parameterized", {
parameters: [
{
name: "ip",
label: "IP",
data_type: "string",
} as AttackPathQueryParameter,
],
result_summary: null,
}),
makeQuery("no-summary"), // scan predating the precompute step
],
};
const ids = adaptAttackPathQueriesResponse(response).data.map((q) => q.id);
// the only one hidden is the parameterless query known to be empty
expect(ids).toEqual(["has-data", "errored", "parameterized", "no-summary"]);
expect(ids).not.toContain("empty");
});
it("returns an empty list for a missing response", () => {
expect(adaptAttackPathQueriesResponse(undefined).data).toEqual([]);
});
it("updates the pagination count to the filtered length", () => {
const response = {
data: [
makeQuery("a", { result_summary: { status: "ok", has_data: true } }),
makeQuery("b", { result_summary: { status: "ok", has_data: false } }),
],
};
const { metadata } = adaptAttackPathQueriesResponse(response);
expect(metadata?.pagination.count).toBe(1);
});
it("shows every query when not running in Cloud (feature flag off)", () => {
vi.mocked(isCloud).mockReturnValue(false);
const response = {
data: [
makeQuery("has-data", {
result_summary: { status: "ok", has_data: true },
}),
makeQuery("empty", {
result_summary: { status: "ok", has_data: false },
}),
],
};
const ids = adaptAttackPathQueriesResponse(response).data.map((q) => q.id);
// self-hosted keeps current behaviour: no filtering, even the empty one
expect(ids).toEqual(["has-data", "empty"]);
});
});
describe("buildAttackPathQueries", () => {
it("prepends a custom query that links to the Prowler documentation", () => {
// When
const result = buildAttackPathQueries([presetQuery]);
// Then
expect(result[0]).toMatchObject({
id: ATTACK_PATH_QUERY_IDS.CUSTOM,
attributes: {
name: "Custom openCypher query",
short_description: "Write and run your own read-only query",
documentation_link: {
text: "Learn how to write custom openCypher queries",
link: DOCS_URLS.ATTACK_PATHS_CUSTOM_QUERIES,
},
},
});
expect(result[1]).toEqual(presetQuery);
});
});