Files
prowler/ui/actions/attack-paths/queries.test.ts
T
2026-03-23 15:36:37 +00:00

208 lines
5.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const { fetchMock, getAuthHeadersMock, handleApiResponseMock } = vi.hoisted(
() => ({
fetchMock: vi.fn(),
getAuthHeadersMock: vi.fn(),
handleApiResponseMock: vi.fn(),
}),
);
vi.mock("@/lib", () => ({
apiBaseUrl: "https://api.example.com/api/v1",
getAuthHeaders: getAuthHeadersMock,
}));
vi.mock("@/lib/server-actions-helper", () => ({
handleApiResponse: handleApiResponseMock,
}));
import {
executeCustomQuery,
executeQuery,
getCartographySchema,
} from "./queries";
describe("executeQuery", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("fetch", fetchMock);
getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" });
});
it("returns a friendly message when API response handling throws", async () => {
// Given
fetchMock.mockResolvedValue(
new Response(null, {
status: 500,
}),
);
handleApiResponseMock.mockRejectedValue(
new Error("Server error (500): backend database unavailable"),
);
// When
const result = await executeQuery(
"550e8400-e29b-41d4-a716-446655440000",
"aws-iam-statements-allow-all-actions",
);
// Then
expect(handleApiResponseMock).toHaveBeenCalledTimes(1);
expect(result).toEqual({
error:
"Server is temporarily unavailable. Please try again in a few minutes.",
status: 503,
});
});
it("returns undefined and skips fetch for invalid scan ids", async () => {
// When
const result = await executeQuery(
"not-a-uuid",
"aws-iam-statements-allow-all-actions",
);
// Then
expect(result).toBeUndefined();
expect(fetchMock).not.toHaveBeenCalled();
expect(handleApiResponseMock).not.toHaveBeenCalled();
});
});
describe("executeCustomQuery", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("fetch", fetchMock);
getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" });
handleApiResponseMock.mockResolvedValue({
data: {
type: "attack-paths-query-run-requests",
id: null,
attributes: {
nodes: [],
relationships: [],
},
},
});
});
it("posts the custom query to the dedicated endpoint", async () => {
// Given
fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
// When
await executeCustomQuery(
"550e8400-e29b-41d4-a716-446655440000",
"MATCH (n) RETURN n LIMIT 10",
);
// Then
expect(fetchMock).toHaveBeenCalledWith(
"https://api.example.com/api/v1/attack-paths-scans/550e8400-e29b-41d4-a716-446655440000/queries/custom",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
data: {
type: "attack-paths-custom-query-run-requests",
attributes: {
query: "MATCH (n) RETURN n LIMIT 10",
},
},
}),
}),
);
});
it("rejects empty custom queries before calling the API", async () => {
// When
const result = await executeCustomQuery(
"550e8400-e29b-41d4-a716-446655440000",
" ",
);
// Then
expect(result).toEqual({
error: "Custom query cannot be empty",
status: 400,
});
expect(fetchMock).not.toHaveBeenCalled();
expect(handleApiResponseMock).not.toHaveBeenCalled();
});
it("rejects custom queries longer than 10000 characters before calling the API", async () => {
// When
const result = await executeCustomQuery(
"550e8400-e29b-41d4-a716-446655440000",
"x".repeat(10001),
);
// Then
expect(result).toEqual({
error: "Custom query must be 10000 characters or fewer",
status: 400,
});
expect(fetchMock).not.toHaveBeenCalled();
expect(handleApiResponseMock).not.toHaveBeenCalled();
});
it("rejects custom queries with write operations before calling the API", async () => {
// When
const result = await executeCustomQuery(
"550e8400-e29b-41d4-a716-446655440000",
"MATCH (n) SET n.name = 'updated' RETURN n",
);
// Then
expect(result).toEqual({
error: "Only read-only queries are allowed",
status: 400,
});
expect(fetchMock).not.toHaveBeenCalled();
expect(handleApiResponseMock).not.toHaveBeenCalled();
});
});
describe("getCartographySchema", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("fetch", fetchMock);
getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" });
});
it("fetches the schema metadata for the selected scan", async () => {
// Given
const apiResponse = {
data: {
type: "attack-paths-cartography-schemas",
id: "aws-0.129.0",
attributes: {
id: "aws-0.129.0",
provider: "aws",
cartography_version: "0.129.0",
schema_url:
"https://github.com/cartography-cncf/cartography/blob/0.129.0/docs/root/modules/aws/schema.md",
raw_schema_url:
"https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/0.129.0/docs/root/modules/aws/schema.md",
},
},
};
fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
handleApiResponseMock.mockResolvedValue(apiResponse);
// When
const result = await getCartographySchema(
"550e8400-e29b-41d4-a716-446655440000",
);
// Then
expect(fetchMock).toHaveBeenCalledWith(
"https://api.example.com/api/v1/attack-paths-scans/550e8400-e29b-41d4-a716-446655440000/schema",
expect.objectContaining({
method: "GET",
}),
);
expect(result).toEqual(apiResponse);
});
});