import { describe, expect, it, jest } from "@jest/globals"; import { StatusPageService } from "../../../src/domain/status-pages/status-page.service.ts"; import type { IStatusPagesRepository } from "../../../src/domain/status-pages/status-page-repository.interface.ts"; import type { ISettingsService } from "../../../src/domain/app-settings/app-settings.service.ts"; import type { StatusPage } from "../../../src/domain/status-pages/status-page.type.ts"; import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/domain/status-pages/status-page.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── const makeStatusPage = (overrides?: Partial): StatusPage => ({ id: "sp-1", teamId: "team-1", userId: "user-1", url: "my-status-page", companyName: "Test Co", monitors: ["mon-1"], theme: "modern", themeMode: "dark", createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", ...overrides, }) as StatusPage; const createRepo = () => ({ create: jest.fn().mockResolvedValue(makeStatusPage()), findByUrl: jest.fn().mockResolvedValue(makeStatusPage()), findByCustomDomain: jest.fn().mockResolvedValue(makeStatusPage()), findByTeamId: jest.fn().mockResolvedValue([makeStatusPage()]), updateById: jest.fn().mockResolvedValue(makeStatusPage()), deleteById: jest.fn().mockResolvedValue(makeStatusPage()), removeMonitorFromStatusPages: jest.fn().mockResolvedValue(1), }) as unknown as jest.Mocked; const createSettingsService = (themesEnabled: boolean, clientHost = "http://localhost:5173") => ({ areStatusPageThemesEnabled: jest.fn().mockReturnValue(themesEnabled), getSettings: jest.fn().mockReturnValue({ clientHost }), }) as unknown as jest.Mocked; const createService = (themesEnabled = true, clientHost = "http://localhost:5173") => { const repo = createRepo(); const settingsService = createSettingsService(themesEnabled, clientHost); const service = new StatusPageService(repo, settingsService); return { service, repo, settingsService }; }; // ── Tests ──────────────────────────────────────────────────────────────────── describe("StatusPageService", () => { describe("createStatusPage", () => { it("delegates to repository with all parameters when themes enabled", async () => { const { service, repo } = createService(true); const data = { companyName: "New Co" }; const file = { originalname: "logo.png" } as Express.Multer.File; const result = await service.createStatusPage("user-1", "team-1", file, data); expect(repo.create).toHaveBeenCalledWith("user-1", "team-1", file, data); expect(result).toEqual(makeStatusPage()); }); it("passes undefined image when not provided", async () => { const { service, repo } = createService(true); await service.createStatusPage("user-1", "team-1", undefined, {}); expect(repo.create).toHaveBeenCalledWith("user-1", "team-1", undefined, {}); }); it("strips theme fields from input and applies defaults to result when themes disabled", async () => { const { service, repo } = createService(false); const data = { companyName: "New Co", theme: "bold" as const, themeMode: "light" as const }; const result = await service.createStatusPage("user-1", "team-1", undefined, data); expect(repo.create).toHaveBeenCalledWith("user-1", "team-1", undefined, { companyName: "New Co" }); expect(result.theme).toBe(DEFAULT_STATUS_PAGE_THEME); expect(result.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); }); }); describe("getStatusPageByUrl", () => { it("delegates to repository", async () => { const { service, repo } = createService(true); const result = await service.getStatusPageByUrl("my-status-page"); expect(repo.findByUrl).toHaveBeenCalledWith("my-status-page"); expect(result).toEqual(makeStatusPage()); }); it("applies default theme when themes disabled", async () => { const { service } = createService(false); const result = await service.getStatusPageByUrl("my-status-page"); expect(result.theme).toBe(DEFAULT_STATUS_PAGE_THEME); expect(result.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); }); }); describe("getStatusPageByCustomDomain", () => { it("delegates to repository", async () => { const { service, repo } = createService(true); const result = await service.getStatusPageByCustomDomain("status.example.com"); expect(repo.findByCustomDomain).toHaveBeenCalledWith("status.example.com"); expect(result).toEqual(makeStatusPage()); }); }); describe("createStatusPage custom domain validation", () => { it("rejects custom domains that match the Checkmate instance host", async () => { const { service } = createService(true, "https://checkmate.example.com"); await expect( service.createStatusPage("user-1", "team-1", undefined, { customDomain: "checkmate.example.com", }) ).rejects.toMatchObject({ status: 400, }); }); it("normalizes custom domains before create", async () => { const { service, repo } = createService(true); await service.createStatusPage("user-1", "team-1", undefined, { customDomain: "https://Status.Example.COM", }); expect(repo.create).toHaveBeenCalledWith("user-1", "team-1", undefined, expect.objectContaining({ customDomain: "status.example.com" })); }); it("allows multiple status pages without a custom domain", async () => { const { service, repo } = createService(true); await service.createStatusPage("user-1", "team-1", undefined, { companyName: "First" }); await service.createStatusPage("user-1", "team-1", undefined, { companyName: "Second" }); expect(repo.create).toHaveBeenCalledTimes(2); }); }); describe("getStatusPagesByTeamId", () => { it("delegates to repository", async () => { const { service, repo } = createService(true); const result = await service.getStatusPagesByTeamId("team-1"); expect(repo.findByTeamId).toHaveBeenCalledWith("team-1"); expect(result).toEqual([makeStatusPage()]); }); it("applies default theme to every result when themes disabled", async () => { const { service, repo } = createService(false); (repo.findByTeamId as jest.Mock).mockResolvedValue([ makeStatusPage({ id: "sp-1", theme: "bold" }), makeStatusPage({ id: "sp-2", theme: "editorial" }), ]); const result = await service.getStatusPagesByTeamId("team-1"); expect(result).toHaveLength(2); for (const sp of result) { expect(sp.theme).toBe(DEFAULT_STATUS_PAGE_THEME); expect(sp.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); } }); }); describe("updateStatusPage", () => { it("delegates to repository with all parameters", async () => { const { service, repo } = createService(true); const updated = makeStatusPage({ companyName: "Updated Co" }); (repo.updateById as jest.Mock).mockResolvedValue(updated); const file = { originalname: "new-logo.png" } as Express.Multer.File; const result = await service.updateStatusPage("sp-1", "team-1", file, { companyName: "Updated Co" }); expect(repo.updateById).toHaveBeenCalledWith("sp-1", "team-1", file, { companyName: "Updated Co" }); expect(result).toBe(updated); }); it("passes theme and themeMode through to the repository when themes enabled", async () => { const { service, repo } = createService(true); const patch = { theme: "modern" as const, themeMode: "dark" as const }; await service.updateStatusPage("sp-1", "team-1", undefined, patch); expect(repo.updateById).toHaveBeenCalledWith("sp-1", "team-1", undefined, patch); }); it("strips theme fields from input and applies defaults to result when themes disabled", async () => { const { service, repo } = createService(false); const patch = { companyName: "Updated Co", theme: "modern" as const, themeMode: "dark" as const }; const result = await service.updateStatusPage("sp-1", "team-1", undefined, patch); expect(repo.updateById).toHaveBeenCalledWith("sp-1", "team-1", undefined, { companyName: "Updated Co" }); expect(result.theme).toBe(DEFAULT_STATUS_PAGE_THEME); expect(result.themeMode).toBe(DEFAULT_STATUS_PAGE_THEME_MODE); }); }); describe("deleteStatusPage", () => { it("delegates to repository and returns deleted page", async () => { const { service, repo } = createService(true); const result = await service.deleteStatusPage("sp-1", "team-1"); expect(repo.deleteById).toHaveBeenCalledWith("sp-1", "team-1"); expect(result).toEqual(makeStatusPage()); }); }); });