import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; import { ISalesContext } from "../../infrastructure/Sales.context"; import { Quote } from "../../domain"; import { UniqueID } from "@shared/contexts"; import { chapterA, chapterB, createDTO, dealer, mapper, quoteId, rawQuote, updateDTO } from "../../infrastructure/__fixtures__/quoteChapters.mock"; import { CreateQuoteUseCase } from "./CreateQuote.useCase"; import { UpdateQuoteUseCase } from "./UpdateQuote.useCase"; import { DuplicateQuoteUseCase } from "./DuplicateQuote.useCase"; import { GetQuoteUseCase } from "./GetQuote.useCase"; const id = UniqueID.create(quoteId).object; const transaction = { marker: "transaction" }; const repo = { getById: jest.fn(), create: jest.fn(), update: jest.fn(), exists: jest.fn(), existsByReference: jest.fn(), findLastReferenceByDealerId: jest.fn(), }; const repositoryFactory = jest.fn(() => repo); const adapter = { startTransaction: () => ({ complete: async (work: (transaction: unknown) => Promise) => work(transaction), }) } as unknown as ISequelizeAdapter; const context = { dealer, adapter, repositoryManager: { getRepository: () => repositoryFactory }, } as unknown as ISalesContext; beforeEach(() => { jest.clearAllMocks(); repo.getById.mockResolvedValue(mapper.mapToDomain(rawQuote())); repo.create.mockResolvedValue(undefined); repo.update.mockResolvedValue(undefined); repo.exists.mockResolvedValue(false); repo.existsByReference.mockResolvedValue(false); repo.findLastReferenceByDealerId.mockResolvedValue(null); }); it("creates chapters and memberships without trusting submitted totals", async () => { const result = await new CreateQuoteUseCase(context).execute(createDTO()); expect(result.isSuccess).toBe(true); expect(repo.create).toHaveBeenCalledWith(result.object); expect(repositoryFactory).toHaveBeenCalledWith({ transaction }); expect(result.object.chapters.map((chapter) => chapter.id.toString())).toEqual([chapterA, chapterB]); expect(result.object.items.toArray().map((item) => item.chapterId?.toString())).toEqual([chapterA, chapterB, chapterB]); expect(result.object.getChapterSubtotal(result.object.chapters[0].id).object.toPrimitive()).toBe(9000); expect(result.object.totalPrice.toPrimitive()).toBe(16335); expect(result.object.showChapterLineAmounts).toBe(false); }); it("creates an old request without chapters in free mode", async () => { const dto = createDTO(false); delete dto.chapters; delete dto.show_chapter_line_amounts; dto.items.forEach((item) => delete item.chapter_id); const result = await new CreateQuoteUseCase(context).execute(dto); expect(result.isSuccess).toBe(true); expect(result.object.chapters).toEqual([]); expect(result.object.showChapterLineAmounts).toBe(true); }); it("loads chapters through the existing get use case", async () => { const result = await new GetQuoteUseCase(context).execute({ id }); expect(result.isSuccess).toBe(true); expect(result.object.chapters).toHaveLength(2); expect(repositoryFactory).toHaveBeenCalledWith({ transaction }); }); it("updates the full structure while preserving an omitted PDF preference", async () => { const dto = updateDTO(); delete dto.show_chapter_line_amounts; dto.chapters?.reverse(); const result = await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: dto }); expect(result.isSuccess).toBe(true); expect(result.object.showChapterLineAmounts).toBe(false); expect(result.object.chapters.map((chapter) => chapter.id.toString())).toEqual([chapterB, chapterA]); expect(repo.getById).toHaveBeenCalledWith(id, { forUpdate: true }); expect(repo.update).toHaveBeenCalledWith(result.object); }); it("rejects an old PUT that omits chapters on an already grouped quote", async () => { const dto = updateDTO(false); delete dto.chapters; const result = await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: dto }); expect(result.isFailure).toBe(true); expect(result.error.code).toBe("INVALID_INPUT_DATA"); expect(repo.update).not.toHaveBeenCalled(); }); it("accepts an old PUT on a free quote and explicit ungrouping on a grouped quote", async () => { const dto = updateDTO(false); const explicit = await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: dto }); expect(explicit.isSuccess).toBe(true); expect(explicit.object.chapters).toEqual([]); expect(explicit.object.items.toArray().every((item) => item.chapterId === null)).toBe(true); repo.getById.mockResolvedValue(mapper.mapToDomain(rawQuote(false))); delete dto.chapters; expect((await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: dto })).isSuccess).toBe(true); }); it.each(["create", "update"])("rejects orphan details before writing in %s", async (operation) => { const dto = createDTO(); dto.items[0].chapter_id = UniqueID.generateNewID().object.toString(); const result = operation === "create" ? await new CreateQuoteUseCase(context).execute(dto) : await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: { ...updateDTO(), items: dto.items.map((item) => ({ ...item, subtotal_price: item.price })) } }); expect(result.isFailure).toBe(true); expect(result.error.code).toBe("INVALID_INPUT_DATA"); expect(repo.create).not.toHaveBeenCalled(); expect(repo.update).not.toHaveBeenCalled(); }); it.each([true, false])("duplicates grouped=%s with new identities and preserved values", async (grouped) => { const source = mapper.mapToDomain(rawQuote(grouped)); repo.getById.mockResolvedValue(source); const result = await new DuplicateQuoteUseCase(context).execute({ sourceId: id }); expect(result.isSuccess).toBe(true); const copy: Quote = result.object; expect(copy.id.equals(source.id)).toBe(false); expect(copy.showChapterLineAmounts).toBe(false); expect(copy.totalPrice.toPrimitive()).toBe(source.totalPrice.toPrimitive()); expect(copy.chapters.map((chapter) => chapter.description.toString())).toEqual(source.chapters.map((chapter) => chapter.description.toString())); copy.chapters.forEach((chapter, index) => { expect(chapter.id.equals(source.chapters[index].id)).toBe(false); expect(chapter.quoteId.equals(copy.id)).toBe(true); }); copy.items.toArray().forEach((item, index) => { expect(item.id.equals(source.items.toArray()[index].id)).toBe(false); expect(item.description.toString()).toBe(source.items.toArray()[index].description.toString()); if (grouped) expect(copy.chapters.some((chapter) => chapter.id.equals(item.chapterId!))).toBe(true); else expect(item.chapterId).toBeNull(); }); }); it("returns a repository error when writing fails", async () => { repo.update.mockRejectedValue(new Error("Write failed")); const result = await new UpdateQuoteUseCase(context).execute({ id, quoteDTO: updateDTO() }); expect(result.isFailure).toBe(true); expect(result.error.code).toBe("REPOSITORY_ERROR"); });