Presupuestador_web/server/src/contexts/sales/application/Quote/QuoteChapters.useCases.test.ts

138 lines
6.8 KiB
TypeScript

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 <T>(work: (transaction: unknown) => Promise<T>) => 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");
});