Files
prowler/ui/actions/manage-groups/manage-groups.test.ts
T
Pablo Fernandez Guerra (PFE) 5b9824c379 feat(ui): filter by provider group across main views (#11659)
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:32:00 +02:00

139 lines
3.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const {
fetchMock,
getAuthHeadersMock,
handleApiErrorMock,
handleApiResponseMock,
} = vi.hoisted(() => ({
fetchMock: vi.fn(),
getAuthHeadersMock: vi.fn(),
handleApiErrorMock: vi.fn(),
handleApiResponseMock: vi.fn(),
}));
vi.mock("next/cache", () => ({
revalidatePath: vi.fn(),
}));
vi.mock("next/navigation", () => ({
redirect: vi.fn(),
}));
vi.mock("@/lib", () => ({
apiBaseUrl: "https://api.example.com/api/v1",
getAuthHeaders: getAuthHeadersMock,
getErrorMessage: vi.fn(),
}));
vi.mock("@/lib/server-actions-helper", () => ({
handleApiError: handleApiErrorMock,
handleApiResponse: handleApiResponseMock,
}));
import { getAllProviderGroups } from "./manage-groups";
const makeGroup = (id: string, name: string) => ({
type: "provider-groups" as const,
id,
attributes: { name, inserted_at: "", updated_at: "" },
relationships: {
providers: { meta: { count: 0 }, data: [] },
roles: { meta: { count: 0 }, data: [] },
},
links: { self: "" },
});
const makePage = (
data: ReturnType<typeof makeGroup>[],
page: number,
pages: number,
) => ({
links: { first: "", last: "", next: null, prev: null },
data,
meta: { pagination: { page, pages, count: data.length } },
});
describe("getAllProviderGroups", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.stubGlobal("fetch", fetchMock);
getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" });
fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
});
it("merges every page into a single response with collapsed pagination", async () => {
handleApiResponseMock
.mockResolvedValueOnce(
makePage(
[makeGroup("g1", "Group 1"), makeGroup("g2", "Group 2")],
1,
2,
),
)
.mockResolvedValueOnce(makePage([makeGroup("g3", "Group 3")], 2, 2));
const result = await getAllProviderGroups();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(result?.data.map((group) => group.id)).toEqual(["g1", "g2", "g3"]);
expect(result?.meta.pagination).toMatchObject({
page: 1,
pages: 1,
count: 3,
});
});
it("stops after the first page when there is only one page", async () => {
handleApiResponseMock.mockResolvedValueOnce(
makePage([makeGroup("g1", "Group 1")], 1, 1),
);
const result = await getAllProviderGroups();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(result?.data).toHaveLength(1);
});
it("returns undefined when the first page has no data", async () => {
handleApiResponseMock.mockResolvedValueOnce(makePage([], 1, 1));
const result = await getAllProviderGroups();
expect(result).toBeUndefined();
});
it("returns undefined when the request throws", async () => {
fetchMock.mockRejectedValueOnce(new Error("network down"));
const result = await getAllProviderGroups();
expect(result).toBeUndefined();
});
it("returns undefined when a later page resolves to an error payload", async () => {
handleApiResponseMock
.mockResolvedValueOnce(makePage([makeGroup("g1", "Group 1")], 1, 2))
.mockResolvedValueOnce({ error: "Forbidden", status: 403 });
const result = await getAllProviderGroups();
expect(result).toBeUndefined();
});
it("returns undefined instead of a truncated list when the max-page cap is hit", async () => {
// Given an API that always reports more pages than the 50-page safety cap
handleApiResponseMock.mockImplementation((response: Response) => {
void response;
return Promise.resolve(makePage([makeGroup("g", "Group")], 1, 9999));
});
// When fetching every page
const result = await getAllProviderGroups();
// Then it must not return a partial/truncated list; bail out instead
expect(result).toBeUndefined();
expect(fetchMock).toHaveBeenCalledTimes(50);
});
});