109 lines
3.4 KiB
TypeScript

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",
}),
);
});
});