import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import test from "node:test"; import { Name, UniqueID } from "@repo/rdx-ddd"; import { Maybe } from "@repo/rdx-utils"; import { RecurringProforma } from "../../../../../modules/customer-invoices/src/api/domain"; import { RecurringProformaModel } from "../../../../../modules/customer-invoices/src/api/infrastructure/common"; import { SequelizeRecurringProformaRepository } from "../../../../../modules/customer-invoices/src/api/infrastructure/recurring-proformas/persistence/sequelize/repositories/sequelize-recurring-proforma.repository"; import { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper, } from "../../../../../modules/customers/src/api/infrastructure/persistence/sequelize/mappers"; import { CustomerModel } from "../../../../../modules/customers/src/api/infrastructure/persistence/sequelize/models/sequelize-customer.model"; import { CustomerRepository } from "../../../../../modules/customers/src/api/infrastructure/persistence/sequelize/repositories/customer.repository"; import type { DocumentSeriesCreateProps } from "../../../../../modules/document-series/src/api/domain"; import { DocumentSeries, DocumentSeriesCode, DocumentSeriesNumber, DocumentType, } from "../../../../../modules/document-series/src/api/domain"; import { DocumentSeriesModel } from "../../../../../modules/document-series/src/api/infrastructure/common"; import { SequelizeDocumentSeriesDomainMapper } from "../../../../../modules/document-series/src/api/infrastructure/persistence/sequelize/mappers"; import { SequelizeDocumentSeriesRepository } from "../../../../../modules/document-series/src/api/infrastructure/persistence/sequelize/repositories/document-series.repository"; import { buildCompanyHeaders, createTestAccount, createTestCompany, createTestMembership, loginByApi, } from "./http-test-fixtures"; import { createHttpTestRuntime } from "./http-test-runtime"; test("ORM real: Customer update, Series save y Recurring save preservan tenant y transacción", async (t) => { const runtime = await createHttpTestRuntime(); t.after(() => runtime.stop()); const a = await createTestCompany(runtime.database, { legalName: "A" }); const b = await createTestCompany(runtime.database, { legalName: "B" }); const companyId = UniqueID.create(a.id).data; const otherCompanyId = UniqueID.create(b.id).data; const account = await createTestAccount(runtime.database, { email: `${randomUUID()}@example.test`, password: "TestPassword123!", }); await createTestMembership(runtime.database, { accountId: account.id, companyId: a.id }); const login = await loginByApi({ request: runtime.request, email: account.email, password: account.password, }); assert.ok(login.body && typeof login.body === "object" && "access_token" in login.body); const id = UniqueID.generateNewID(); const created = await runtime.request("/api/v1/customers", { method: "POST", headers: { ...buildCompanyHeaders({ accessToken: String(login.body.access_token), companyId: a.id }), "content-type": "application/json", }, body: JSON.stringify({ id: id.toPrimitive(), is_company: true, name: "Original", address: { country: "ES" }, uses_retention: false, language_code: "es", currency_code: "EUR", }), }); assert.equal(created.status, 201); const customers = new CustomerRepository( new SequelizeCustomerDomainMapper(), new SequelizeCustomerSummaryMapper(), runtime.database ); const loaded = await customers.getByIdInCompany(companyId, id); assert.ok(loaded.isSuccess); // El agregado válido de A nunca puede escribirse con contexto explícito B. assert.ok(loaded.data.update({ name: Name.create("Allowed customer").data }).isSuccess); const wrong = await runtime.database.transaction((transaction) => customers.update(otherCompanyId, loaded.data, transaction) ); assert.ok(wrong.isFailure); const originalRow = await CustomerModel.findByPk(id.toPrimitive()); assert.ok(originalRow); const foreignAggregate = new SequelizeCustomerDomainMapper().mapToDomain( CustomerModel.build({ ...originalRow.get(), company_id: b.id }) ); assert.ok(foreignAggregate.isSuccess); const deniedAtSql = await runtime.database.transaction((transaction) => customers.update(otherCompanyId, foreignAggregate.data, transaction) ); assert.ok(deniedAtSql.isFailure); assert.ok(deniedAtSql.error.name.includes("NotFound")); assert.equal((await CustomerModel.findByPk(id.toPrimitive()))?.name, "Original"); const missing = await customers.getByIdInCompany(otherCompanyId, id); assert.ok(missing.isFailure); assert.equal((await CustomerModel.findByPk(id.toPrimitive()))?.company_id, a.id); await assert.rejects( runtime.database.transaction(async (transaction) => { const updated = await customers.update(companyId, loaded.data, transaction); assert.ok(updated.isSuccess); assert.equal( (await CustomerModel.findByPk(id.toPrimitive(), { transaction }))?.name, "Allowed customer" ); // El mismo update sin cambios debe funcionar también con driver real. assert.ok((await customers.update(companyId, loaded.data, transaction)).isSuccess); throw new Error("forced rollback"); }), /forced rollback/ ); assert.equal((await CustomerModel.findByPk(id.toPrimitive()))?.name, "Original"); assert.ok( ( await runtime.database.transaction((transaction) => customers.update(companyId, loaded.data, transaction) ) ).isSuccess ); assert.equal((await CustomerModel.findByPk(id.toPrimitive()))?.name, "Allowed customer"); const seriesProps: DocumentSeriesCreateProps = { companyId, branchId: Maybe.none(), documentType: DocumentType.proforma(), code: DocumentSeriesCode.create("PF").data, name: Name.create("Original series").data, description: Maybe.none(), prefix: Maybe.none(), suffix: Maybe.none(), nextNumber: DocumentSeriesNumber.create(1).data, padding: 4, validFrom: Maybe.none(), validTo: Maybe.none(), isDefault: false, isActive: true, hasAssignedNumbers: false, createdAt: new Date(), updatedAt: new Date(), deletedAt: Maybe.none(), }; const series = DocumentSeries.create(seriesProps).data; const seriesRepo = new SequelizeDocumentSeriesRepository( new SequelizeDocumentSeriesDomainMapper(), runtime.database ); assert.ok( (await runtime.database.transaction((transaction) => seriesRepo.save(series, transaction))) .isSuccess ); const foreignSeries = DocumentSeries.create( { ...seriesProps, companyId: otherCompanyId, name: Name.create("Foreign change").data }, series.id ).data; assert.ok( ( await runtime.database.transaction((transaction) => seriesRepo.save(foreignSeries, transaction) ) ).isFailure ); assert.equal((await DocumentSeriesModel.findByPk(series.id.toPrimitive()))?.company_id, a.id); assert.equal( (await DocumentSeriesModel.findByPk(series.id.toPrimitive()))?.name, "Original series" ); const changedSeries = DocumentSeries.create( { ...seriesProps, name: Name.create("Allowed series").data }, series.id ).data; await assert.rejects( runtime.database.transaction(async (transaction) => { assert.ok((await seriesRepo.save(changedSeries, transaction)).isSuccess); assert.equal( (await DocumentSeriesModel.findByPk(series.id.toPrimitive(), { transaction }))?.name, "Allowed series" ); throw new Error("forced rollback"); }), /forced rollback/ ); assert.equal( (await DocumentSeriesModel.findByPk(series.id.toPrimitive()))?.name, "Original series" ); assert.ok( ( await runtime.database.transaction((transaction) => seriesRepo.save(changedSeries, transaction) ) ).isSuccess ); assert.equal( (await DocumentSeriesModel.findByPk(series.id.toPrimitive()))?.name, "Allowed series" ); const recurring = RecurringProforma.create({ companyId, name: "Original recurring" }).data; const recurringRepo = new SequelizeRecurringProformaRepository(); assert.ok( ( await runtime.database.transaction((transaction) => recurringRepo.save(recurring, transaction) ) ).isSuccess ); const foreignRecurring = RecurringProforma.create( { companyId: otherCompanyId, name: "Foreign recurring" }, recurring.id ).data; assert.ok( ( await runtime.database.transaction((transaction) => recurringRepo.save(foreignRecurring, transaction) ) ).isFailure ); assert.equal( (await RecurringProformaModel.findByPk(recurring.id.toPrimitive()))?.company_id, a.id ); const changedRecurring = RecurringProforma.create( { companyId, name: "Allowed recurring" }, recurring.id ).data; await assert.rejects( runtime.database.transaction(async (transaction) => { assert.ok((await recurringRepo.save(changedRecurring, transaction)).isSuccess); assert.equal( (await RecurringProformaModel.findByPk(recurring.id.toPrimitive(), { transaction }))?.name, "Allowed recurring" ); throw new Error("forced rollback"); }), /forced rollback/ ); assert.equal( (await RecurringProformaModel.findByPk(recurring.id.toPrimitive()))?.name, "Original recurring" ); assert.ok( ( await runtime.database.transaction((transaction) => recurringRepo.save(changedRecurring, transaction) ) ).isSuccess ); assert.equal( (await RecurringProformaModel.findByPk(recurring.id.toPrimitive()))?.name, "Allowed recurring" ); });