import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ getConfigByProjectDomainLocation: vi.fn(), getConfigsForProject: vi.fn(), createConfig: vi.fn(), updateConfig: vi.fn(), })); vi.mock("cloudflare:workers", () => ({ env: {} })); vi.mock("@/server/lib/dataforseo", () => ({ createDataforseoClient: vi.fn() })); vi.mock( "@/server/features/rank-tracking/repositories/RankTrackingRepository", () => ({ RankTrackingRepository: mocks }), ); const archivedConfig = { id: "config_archived", projectId: "project_1", domain: "acme.com", locationCode: 2840, languageCode: "en", devices: "both" as const, serpDepth: 20, scheduleInterval: "weekly" as const, isActive: false, lastSkipReason: "insufficient_credits", }; const baseInput = { projectId: "project_1", domain: "acme.com", locationCode: 2840, languageCode: "es", devices: "desktop" as const, serpDepth: 40, scheduleInterval: "daily" as const, }; describe("RankTrackingService.createConfig", () => { beforeEach(() => { vi.resetModules(); for (const mock of Object.values(mocks)) mock.mockReset(); }); it("reactivates an archived config instead of throwing, applying the new settings", async () => { mocks.getConfigByProjectDomainLocation.mockResolvedValue(archivedConfig); mocks.updateConfig.mockResolvedValue(undefined); const { RankTrackingService } = await import("./RankTrackingService"); await expect(RankTrackingService.createConfig(baseInput)).resolves.toEqual({ configId: "config_archived", }); expect(mocks.updateConfig).toHaveBeenCalledTimes(1); expect(mocks.updateConfig).toHaveBeenCalledWith( "config_archived", "project_1", expect.objectContaining({ isActive: true, languageCode: "es", devices: "desktop", serpDepth: 40, scheduleInterval: "daily", lastSkipReason: null, }), ); // Reactivation must not insert a duplicate row. expect(mocks.createConfig).not.toHaveBeenCalled(); }); it("throws when an active config already tracks the same domain + location", async () => { mocks.getConfigByProjectDomainLocation.mockResolvedValue({ ...archivedConfig, isActive: true, }); const { RankTrackingService } = await import("./RankTrackingService"); await expect( RankTrackingService.createConfig(baseInput), ).rejects.toMatchObject({ code: "VALIDATION_ERROR" }); expect(mocks.updateConfig).not.toHaveBeenCalled(); expect(mocks.createConfig).not.toHaveBeenCalled(); }); it("creates a new config when none exists for the domain + location", async () => { mocks.getConfigByProjectDomainLocation.mockResolvedValue(null); mocks.getConfigsForProject.mockResolvedValue([]); mocks.createConfig.mockResolvedValue(undefined); const { RankTrackingService } = await import("./RankTrackingService"); const result = await RankTrackingService.createConfig(baseInput); expect(result.configId).toBeTruthy(); expect(mocks.createConfig).toHaveBeenCalledTimes(1); expect(mocks.updateConfig).not.toHaveBeenCalled(); expect(mocks.createConfig).toHaveBeenCalledWith( expect.objectContaining({ id: result.configId, projectId: "project_1", domain: "acme.com", devices: "desktop", serpDepth: 40, scheduleInterval: "daily", }), ); }); });