mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-10 13:34:16 +00:00
- Add maintenance mode landing page and view component - Gate maintenance status check and redirect behind isCloud() - Add changelog fragment
105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import type { NextFetchEvent } from "next/server";
|
|
import { NextRequest } from "next/server";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const {
|
|
authHandlerSpy,
|
|
authMock,
|
|
fetchMaintenanceStatusMock,
|
|
maintenanceResponseMock,
|
|
} = vi.hoisted(() => ({
|
|
authHandlerSpy: vi.fn(),
|
|
// Minimal stand-in for next-auth's `auth()` wrapper: it takes the route
|
|
// handler and returns a function with the (req, ctx) signature `proxy()`
|
|
// calls `authProxy` with. The real handler reads `req.auth`, which a
|
|
// plain NextRequest doesn't have, so `user`/`sessionError` resolve to
|
|
// `undefined` here — irrelevant to what this suite asserts (the MM gate
|
|
// itself), it only needs to observe whether authProxy ran.
|
|
authMock: vi.fn(
|
|
(handler: (req: unknown) => unknown) => (req: unknown, _ctx: unknown) => {
|
|
authHandlerSpy(req);
|
|
return handler(req);
|
|
},
|
|
),
|
|
fetchMaintenanceStatusMock: vi.fn(),
|
|
maintenanceResponseMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/auth.config", () => ({
|
|
auth: authMock,
|
|
}));
|
|
|
|
vi.mock("@/lib", () => ({
|
|
apiBaseUrl: "http://api:8000/api/v1",
|
|
}));
|
|
|
|
vi.mock("@/lib/maintenance", () => ({
|
|
fetchMaintenanceStatus: fetchMaintenanceStatusMock,
|
|
maintenanceResponse: maintenanceResponseMock,
|
|
MAINTENANCE_PATH: "/maintenance",
|
|
}));
|
|
|
|
import proxy from "./proxy";
|
|
|
|
const makeRequest = (path: string) =>
|
|
new NextRequest(new URL(`https://app.prowler.com${path}`));
|
|
|
|
describe("proxy() Maintenance Mode gate (isCloud gating)", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('runs the maintenance status check when NEXT_PUBLIC_IS_CLOUD_ENV is "true"', async () => {
|
|
// Given
|
|
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
|
|
fetchMaintenanceStatusMock.mockResolvedValueOnce({
|
|
enabled: false,
|
|
message: null,
|
|
started_at: null,
|
|
});
|
|
maintenanceResponseMock.mockReturnValueOnce(null);
|
|
|
|
// When
|
|
const response = await proxy(
|
|
makeRequest("/scans") as any,
|
|
{} as NextFetchEvent,
|
|
);
|
|
|
|
// Then: the gate ran (and, since it returned null here, fell through to
|
|
// the auth-wrapped handler, which redirects unauthenticated visitors).
|
|
expect(fetchMaintenanceStatusMock).toHaveBeenCalledTimes(1);
|
|
expect(maintenanceResponseMock).toHaveBeenCalledTimes(1);
|
|
expect(authHandlerSpy).toHaveBeenCalledTimes(1);
|
|
expect(response.status).toBe(307);
|
|
});
|
|
|
|
it('is a no-op in self-hosted (NEXT_PUBLIC_IS_CLOUD_ENV is not "true"): the status fetch is never made and the request falls straight through to auth', async () => {
|
|
// Given
|
|
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");
|
|
|
|
// When
|
|
const response = await proxy(
|
|
makeRequest("/scans") as any,
|
|
{} as NextFetchEvent,
|
|
);
|
|
|
|
// Then
|
|
expect(fetchMaintenanceStatusMock).not.toHaveBeenCalled();
|
|
expect(maintenanceResponseMock).not.toHaveBeenCalled();
|
|
expect(authHandlerSpy).toHaveBeenCalledTimes(1);
|
|
expect(response.status).toBe(307);
|
|
});
|
|
|
|
it("is also a no-op when NEXT_PUBLIC_IS_CLOUD_ENV is unset", async () => {
|
|
// Given
|
|
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", undefined);
|
|
|
|
// When
|
|
await proxy(makeRequest("/scans") as any, {} as NextFetchEvent);
|
|
|
|
// Then
|
|
expect(fetchMaintenanceStatusMock).not.toHaveBeenCalled();
|
|
expect(maintenanceResponseMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|