import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { generateSharedNumber, generateOfferNumber, previewSharedNumber, isSharedNumberTaken, previewOfferNumber, isOfferNumberTaken, } from "../services/numbering.service"; import prisma from "../config/database"; describe("generateSharedNumber", () => { beforeEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "shared" } }); }); afterEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "shared" } }); }); it("returns a non-empty string", async () => { const num = await generateSharedNumber(); expect(typeof num).toBe("string"); expect(num.length).toBeGreaterThan(0); }); it("increments on consecutive calls", async () => { const num1 = await generateSharedNumber(); const num2 = await generateSharedNumber(); expect(num1).not.toBe(num2); }); }); describe("generateOfferNumber", () => { beforeEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "offer" } }); }); afterEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "offer" } }); }); it("returns a non-empty string", async () => { const num = await generateOfferNumber(); expect(typeof num).toBe("string"); expect(num.length).toBeGreaterThan(0); }); it("increments on consecutive calls", async () => { const num1 = await generateOfferNumber(); const num2 = await generateOfferNumber(); expect(num1).not.toBe(num2); }); }); describe("previewSharedNumber", () => { let createdProjectId: number | null = null; beforeEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "shared" } }); }); afterEach(async () => { if (createdProjectId !== null) { await prisma.projects .delete({ where: { id: createdProjectId } }) .catch(() => {}); createdProjectId = null; } await prisma.number_sequences.deleteMany({ where: { type: "shared" } }); }); it("returns a number not already used by an order or project", async () => { const preview = await previewSharedNumber(); expect(typeof preview).toBe("string"); expect(await isSharedNumberTaken(preview)).toBe(false); }); it("skips a number already taken when the sequence counter lags behind", async () => { // Fresh counter → preview points at the first sequence number. With the // skip-taken logic this is guaranteed free, so we can safely claim it. const first = await previewSharedNumber(); const project = await prisma.projects.create({ data: { project_number: first, name: "test-preview-skip-taken" }, }); createdProjectId = project.id; // The counter still lags behind (no row was consumed), but `first` is now // taken — the preview must advance past it instead of re-offering it. const second = await previewSharedNumber(); expect(second).not.toBe(first); expect(await isSharedNumberTaken(second)).toBe(false); }); }); describe("previewOfferNumber", () => { let createdQuotationId: number | null = null; beforeEach(async () => { await prisma.number_sequences.deleteMany({ where: { type: "offer" } }); }); afterEach(async () => { if (createdQuotationId !== null) { await prisma.quotations .delete({ where: { id: createdQuotationId } }) .catch(() => {}); createdQuotationId = null; } await prisma.number_sequences.deleteMany({ where: { type: "offer" } }); }); it("returns a number not already used by a quotation", async () => { const preview = await previewOfferNumber(); expect(typeof preview).toBe("string"); expect(await isOfferNumberTaken(preview)).toBe(false); }); it("skips a number already taken when the sequence counter lags behind", async () => { // Fresh counter → preview points at the first sequence number; with the // skip-taken logic it's guaranteed free, so we can claim it. const first = await previewOfferNumber(); const quotation = await prisma.quotations.create({ data: { quotation_number: first }, }); createdQuotationId = quotation.id; // The counter still lags, but `first` is now taken — the preview must // advance past it instead of re-offering it. const second = await previewOfferNumber(); expect(second).not.toBe(first); expect(await isOfferNumberTaken(second)).toBe(false); }); });