258 lines
9.6 KiB
TypeScript
258 lines
9.6 KiB
TypeScript
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"
|
|
);
|
|
});
|