Files
Checkmate/server/test/unit/providers/network/pageSpeedProvider.test.ts
T
2026-09-25 11:39:16 -07:00

166 lines
6.5 KiB
TypeScript

import { describe, expect, it, jest } from "@jest/globals";
import { PageSpeedProvider } from "../../../../src/service/networkProviders/PageSpeedProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
import type { HttpProvider } from "../../../../src/service/networkProviders/HttpProvider.ts";
import type { ISettingsService } from "../../../../src/domain/app-settings/app-settings.service.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
id: "mon-1",
teamId: "team-1",
type: "pagespeed",
url: "https://example.com",
...overrides,
}) as Monitor;
const createMockHttpProvider = () =>
({
handle: jest.fn().mockResolvedValue({
monitorId: "mon-1",
teamId: "team-1",
type: "pagespeed",
status: true,
code: 200,
message: "OK",
responseTime: 2000,
payload: { lighthouseResult: {} },
}),
}) as unknown as jest.Mocked<HttpProvider>;
const createMockSettingsService = (apiKey?: string) =>
({
getCachedDBSettings: jest.fn().mockResolvedValue({ pagespeedApiKey: apiKey }),
}) as unknown as jest.Mocked<ISettingsService>;
const createProvider = (opts?: { apiKey?: string }) => {
const logger = createMockLogger();
const httpProvider = createMockHttpProvider();
const settingsService = createMockSettingsService(opts?.apiKey);
const provider = new PageSpeedProvider(httpProvider, settingsService, logger as any);
return { provider, httpProvider, settingsService, logger };
};
// ── Contract ─────────────────────────────────────────────────────────────────
testStatusProviderContract("PageSpeedProvider", {
create: () => createProvider({ apiKey: "test-key" }).provider,
supportedType: "pagespeed",
unsupportedType: "http",
makeMonitor: () => makeMonitor(),
});
// ── Tests ────────────────────────────────────────────────────────────────────
describe("PageSpeedProvider", () => {
it("delegates to httpProvider with PageSpeed API URL", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "my-key" });
await provider.handle(makeMonitor({ url: "https://example.com" }));
expect(httpProvider.handle).toHaveBeenCalledWith(
expect.objectContaining({
url: expect.stringContaining("pagespeedonline.googleapis.com"),
})
);
});
it("includes API key in URL when available", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "my-key" });
await provider.handle(makeMonitor());
const calledUrl = (httpProvider.handle as jest.Mock).mock.calls[0][0].url;
expect(calledUrl).toContain("key=my-key");
});
it("omits API key and logs warning when not configured", async () => {
const { provider, httpProvider, logger } = createProvider({ apiKey: undefined });
await provider.handle(makeMonitor());
const calledUrl = (httpProvider.handle as jest.Mock).mock.calls[0][0].url;
expect(calledUrl).not.toContain("key=");
expect(logger.warn).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining("API key not found"),
service: "PageSpeedProvider",
})
);
});
it("encodes the monitor URL in the PageSpeed API URL", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "key" });
await provider.handle(makeMonitor({ url: "https://example.com/path?q=1" }));
const calledUrl = (httpProvider.handle as jest.Mock).mock.calls[0][0].url;
expect(calledUrl).toContain(encodeURIComponent("https://example.com/path?q=1"));
});
it("includes all PageSpeed categories in the URL", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "key" });
await provider.handle(makeMonitor());
const calledUrl = (httpProvider.handle as jest.Mock).mock.calls[0][0].url;
expect(calledUrl).toContain("category=seo");
expect(calledUrl).toContain("category=accessibility");
expect(calledUrl).toContain("category=best-practices");
expect(calledUrl).toContain("category=performance");
});
it("passes all monitor properties through to httpProvider", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "key" });
const monitor = makeMonitor({ id: "mon-5", teamId: "team-9" });
await provider.handle(monitor);
expect(httpProvider.handle).toHaveBeenCalledWith(expect.objectContaining({ id: "mon-5", teamId: "team-9" }));
});
it("throws AppError when url is missing", async () => {
const { provider } = createProvider();
await expect(provider.handle(makeMonitor({ url: "" }))).rejects.toThrow("URL is required for PageSpeed monitor");
});
it("throws AppError when httpProvider.handle throws", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "key" });
(httpProvider.handle as jest.Mock).mockRejectedValue(new Error("timeout"));
await expect(provider.handle(makeMonitor())).rejects.toThrow("timeout");
});
it("throws AppError with default message for non-Error thrown values", async () => {
const { provider, httpProvider } = createProvider({ apiKey: "key" });
(httpProvider.handle as jest.Mock).mockRejectedValue("string error");
await expect(provider.handle(makeMonitor())).rejects.toThrow("Error performing PageSpeed request");
});
it("handles null apiKey from DB settings", async () => {
const { provider, logger } = createProvider({ apiKey: null as any });
await provider.handle(makeMonitor());
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining("API key not found") }));
});
it("handles null dbSettings response", async () => {
const logger = createMockLogger();
const httpProvider = createMockHttpProvider();
const settingsService = {
getCachedDBSettings: jest.fn().mockResolvedValue(null),
} as unknown as jest.Mocked<ISettingsService>;
const provider = new PageSpeedProvider(httpProvider, settingsService, logger as any);
await provider.handle(makeMonitor());
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining("API key not found") }));
});
});