FactuGES_ERP/apps/server/src/testing/http/document-series.integration.test.ts

313 lines
11 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import test from "node:test";
import { QueryTypes } from "sequelize";
import { DocumentSeriesNumberAssigner } from "../../../../../modules/document-series/src/api/application/services/document-series-number-assigner.ts";
import { SequelizeDocumentSeriesDomainMapper } from "../../../../../modules/document-series/src/api/infrastructure/persistence/sequelize/mappers/sequelize-document-series-domain.mapper.ts";
import { SequelizeDocumentSeriesRepository } from "../../../../../modules/document-series/src/api/infrastructure/persistence/sequelize/repositories/document-series.repository.ts";
import {
buildCompanyHeaders,
createTestAccount,
createTestCompany,
createTestMembership,
loginByApi,
} from "./http-test-fixtures.ts";
import { createHttpTestRuntime } from "./http-test-runtime.ts";
type SeriesRow = {
id: string;
company_id: string;
document_type: string;
code: string;
name: string;
prefix: string | null;
next_number: number;
is_default: number;
is_active: number;
has_assigned_numbers: number;
};
async function createSession(runtime: Awaited<ReturnType<typeof createHttpTestRuntime>>) {
const account = await createTestAccount(runtime.database, {
email: `document-series-${randomUUID()}@example.test`,
password: "Password123!",
});
const company = await createTestCompany(runtime.database, { legalName: "Document series test" });
await createTestMembership(runtime.database, { accountId: account.id, companyId: company.id });
const login = await loginByApi({
request: runtime.request,
email: account.email,
password: account.password,
});
assert.equal(login.status, 200);
assert.equal(typeof login.body, "object");
assert.notEqual(login.body, null);
const accessToken = (login.body as { access_token: string }).access_token;
return {
company,
headers: {
...buildCompanyHeaders({ accessToken, companyId: company.id }),
"content-type": "application/json",
},
};
}
async function findSeries(runtime: Awaited<ReturnType<typeof createHttpTestRuntime>>, id: string) {
const rows = await runtime.database.query<SeriesRow>(
"SELECT id, company_id, document_type, code, name, prefix, next_number, is_default, is_active, has_assigned_numbers FROM document_series WHERE id = :id",
{ replacements: { id }, type: QueryTypes.SELECT }
);
return rows[0] ?? null;
}
async function createSeries(params: {
runtime: Awaited<ReturnType<typeof createHttpTestRuntime>>;
headers: Record<string, string>;
code: string;
documentType?: "proforma" | "issued_invoice";
nextNumber?: number;
isDefault?: boolean;
isActive?: boolean;
}) {
const response = await params.runtime.request("/api/v1/document-series", {
method: "POST",
headers: params.headers,
body: JSON.stringify({
document_type: params.documentType ?? "proforma",
code: params.code,
name: `Series ${params.code}`,
next_number: params.nextNumber ?? 100,
padding: 3,
is_default: params.isDefault ?? false,
is_active: params.isActive ?? true,
}),
});
assert.equal(response.status, 201);
assert.equal(typeof response.body, "object");
assert.notEqual(response.body, null);
return response.body as { id: string };
}
test("document-series HTTP persiste creación, asignación y avance en MariaDB", async (t) => {
const runtime = await createHttpTestRuntime();
t.after(async () => runtime.stop());
const session = await createSession(runtime);
const created = await createSeries({
runtime,
headers: session.headers,
code: "PF",
nextNumber: 100,
});
const createdRow = await findSeries(runtime, created.id);
assert.ok(createdRow);
assert.equal(createdRow.company_id, session.company.id);
assert.equal(createdRow.code, "PF");
assert.equal(createdRow.document_type, "proforma");
assert.equal(createdRow.next_number, 100);
assert.equal(createdRow.is_active, 1);
assert.equal(createdRow.is_default, 0);
assert.equal(createdRow.has_assigned_numbers, 0);
const first = await runtime.request("/api/v1/document-series/assign-next", {
method: "POST",
headers: session.headers,
body: JSON.stringify({ document_type: "proforma", series_code: "PF" }),
});
assert.equal(first.status, 200);
assert.deepEqual(first.body, {
series_id: created.id,
series_code: "PF",
number: "100",
reference: "100",
});
const afterFirst = await findSeries(runtime, created.id);
assert.ok(afterFirst);
assert.equal(afterFirst.next_number, 101);
assert.equal(afterFirst.has_assigned_numbers, 1);
const second = await runtime.request("/api/v1/document-series/assign-next", {
method: "POST",
headers: session.headers,
body: JSON.stringify({ document_type: "proforma", series_code: "PF" }),
});
assert.equal(second.status, 200);
assert.equal((second.body as { number: string }).number, "101");
const afterSecond = await findSeries(runtime, created.id);
assert.ok(afterSecond);
assert.equal(afterSecond.next_number, 102);
assert.equal(afterSecond.has_assigned_numbers, 1);
const advance = await runtime.request(`/api/v1/document-series/${created.id}/next-number`, {
method: "PATCH",
headers: session.headers,
body: JSON.stringify({ next_number: 150 }),
});
assert.equal(advance.status, 200);
const afterAdvance = await findSeries(runtime, created.id);
assert.ok(afterAdvance);
assert.equal(afterAdvance.next_number, 150);
assert.equal(afterAdvance.has_assigned_numbers, 1);
for (const nextNumber of [150, 149]) {
const rejected = await runtime.request(`/api/v1/document-series/${created.id}/next-number`, {
method: "PATCH",
headers: session.headers,
body: JSON.stringify({ next_number: nextNumber }),
});
assert.equal(rejected.status, 422);
assert.equal((await findSeries(runtime, created.id))?.next_number, 150);
}
});
test("document-series HTTP mantiene default, estado, aislamiento y update restringido", async (t) => {
const runtime = await createHttpTestRuntime();
t.after(async () => runtime.stop());
const session = await createSession(runtime);
const a = await createSeries({ runtime, headers: session.headers, code: "A" });
const b = await createSeries({ runtime, headers: session.headers, code: "B" });
const invoice = await createSeries({
runtime,
headers: session.headers,
code: "F",
documentType: "issued_invoice",
isDefault: true,
});
for (const id of [a.id, b.id]) {
const response = await runtime.request(`/api/v1/document-series/${id}/default`, {
method: "PATCH",
headers: session.headers,
});
assert.equal(response.status, 200);
}
assert.equal((await findSeries(runtime, a.id))?.is_default, 0);
assert.equal((await findSeries(runtime, b.id))?.is_default, 1);
assert.equal((await findSeries(runtime, invoice.id))?.is_default, 1);
const createdDefault = await createSeries({
runtime,
headers: session.headers,
code: "C",
isDefault: true,
});
assert.equal((await findSeries(runtime, b.id))?.is_default, 0);
assert.equal((await findSeries(runtime, createdDefault.id))?.is_default, 1);
const disable = await runtime.request(`/api/v1/document-series/${b.id}/disable`, {
method: "PATCH",
headers: session.headers,
});
assert.equal(disable.status, 204);
assert.equal((await findSeries(runtime, b.id))?.is_active, 0);
assert.equal((await findSeries(runtime, b.id))?.is_default, 0);
const enable = await runtime.request(`/api/v1/document-series/${b.id}/enable`, {
method: "PATCH",
headers: session.headers,
});
assert.equal(enable.status, 200);
assert.equal((await findSeries(runtime, b.id))?.is_active, 1);
assert.equal((await findSeries(runtime, b.id))?.is_default, 0);
const update = await runtime.request(`/api/v1/document-series/${a.id}`, {
method: "PUT",
headers: session.headers,
body: JSON.stringify({
name: "Renamed",
prefix: "P-",
code: "OTHER",
next_number: 999,
is_default: true,
}),
});
assert.equal(update.status, 200);
const afterUpdate = await findSeries(runtime, a.id);
assert.ok(afterUpdate);
assert.equal(afterUpdate.name, "Renamed");
assert.equal(afterUpdate.prefix, "P-");
assert.equal(afterUpdate.code, "A");
assert.equal(afterUpdate.next_number, 100);
assert.equal(afterUpdate.is_default, 0);
const inactive = await createSeries({
runtime,
headers: session.headers,
code: "I",
isActive: false,
});
const inactiveDefault = await runtime.request(`/api/v1/document-series/${inactive.id}/default`, {
method: "PATCH",
headers: session.headers,
});
assert.equal(inactiveDefault.status, 422);
assert.equal((await findSeries(runtime, inactive.id))?.is_default, 0);
const rejectedCreate = await runtime.request("/api/v1/document-series", {
method: "POST",
headers: session.headers,
body: JSON.stringify({
document_type: "proforma",
code: "INVALID",
name: "Invalid default",
next_number: 100,
padding: 0,
is_active: false,
is_default: true,
}),
});
assert.equal(rejectedCreate.status, 422);
const invalidRows = await runtime.database.query<SeriesRow>(
"SELECT id, company_id, document_type, code, name, prefix, next_number, is_default, is_active, has_assigned_numbers FROM document_series WHERE company_id = :companyId AND code = :code",
{ replacements: { companyId: session.company.id, code: "INVALID" }, type: QueryTypes.SELECT }
);
assert.equal(invalidRows.length, 0);
const other = await createSession(runtime);
const forbidden = await runtime.request(`/api/v1/document-series/${a.id}`, {
headers: other.headers,
});
assert.equal(forbidden.status, 404);
assert.equal((await findSeries(runtime, a.id))?.company_id, session.company.id);
});
test("document-series revierte contador y hasAssignedNumbers con una transacción MariaDB real", async (t) => {
const runtime = await createHttpTestRuntime();
t.after(async () => runtime.stop());
const session = await createSession(runtime);
const created = await createSeries({
runtime,
headers: session.headers,
code: "RB",
nextNumber: 200,
});
const assigner = new DocumentSeriesNumberAssigner(
new SequelizeDocumentSeriesRepository(
new SequelizeDocumentSeriesDomainMapper(),
runtime.database
)
);
await assert.rejects(
() =>
runtime.database.transaction(async (transaction) => {
const assigned = await assigner.assignNext({
companyId: session.company.id,
documentType: "proforma",
seriesCode: "RB",
transaction,
});
assert.equal(assigned.isSuccess, true);
throw new Error("rollback requested by integration test");
}),
/rollback requested by integration test/
);
const persisted = await findSeries(runtime, created.id);
assert.ok(persisted);
assert.equal(persisted.next_number, 200);
assert.equal(persisted.has_assigned_numbers, 0);
});