metis-desk/modules/companies/src/web/shared/client-contracts.test.ts

124 lines
4.9 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import { test } from "node:test";
import { COMPANY_LOGO_LIMITS, COMPANY_LOGO_TYPES } from "../../common";
import { getAccess } from "../access/list/api";
import { saveAccess } from "../access/shared/api";
import { validateCompanyLogo } from "../companies/change-logo/helpers";
import { getCompany } from "../companies/detail/api";
import { listCompanies } from "../companies/list/api";
import { saveMailboxUse } from "../mailbox-uses/shared/api";
import { getMailboxes } from "../mailboxes/list/api";
import { buildPersonRequest } from "../people/shared/adapters";
import { buildProcessTemplateInput } from "../process-templates/shared/adapters";
import { buildSiteRequest } from "../sites/shared/adapters";
const COMPANY_ID = "e0420eb9-2aab-4368-96a0-2ce83dd23eb4";
test("las consultas de compañías conservan sus rutas y rechazan respuestas inválidas", async (context) => {
context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => {
assert.equal(options.method, "GET");
if (url === "/api/companies") {
return Response.json({ companies: [] });
}
assert.equal(url, `/api/companies/${COMPANY_ID}`);
return Response.json({ company: { id: COMPANY_ID } });
});
assert.deepEqual(await listCompanies(), { companies: [] });
await assert.rejects(getCompany(COMPANY_ID));
});
test("lectura y guardado de accesos están separados sin cambiar el contrato HTTP", async (context) => {
const calls: { url: unknown; options: RequestInit }[] = [];
context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => {
calls.push({ url, options });
return Response.json({ items: [], reviews: [], coverage: [] });
});
await getAccess("global");
const values = { kind: "system", name: "Directory", active: true } as const;
await saveAccess("global", values, COMPANY_ID, "csrf");
assert.deepEqual(
calls.map((call) => call.url),
["/api/access/global", `/api/access/global/${COMPANY_ID}/update`]
);
assert.equal(calls[0]?.options.method, "GET");
const save = calls[1]?.options;
assert.ok(save);
assert.equal(save.method, "POST");
assert.equal(save.body, JSON.stringify({ values }));
assert.equal(new Headers(save.headers).get("x-csrf-token"), "csrf");
});
test("el guardado de usos de buzón mantiene su endpoint y no se confunde con la consulta", async (context) => {
const calls: { url: unknown; options: RequestInit }[] = [];
context.mock.method(globalThis, "fetch", async (url: unknown, options: RequestInit) => {
calls.push({ url, options });
return Response.json({ mailboxes: [], uses: [] });
});
await getMailboxes(COMPANY_ID);
const values = {
mailboxId: COMPANY_ID,
personId: COMPANY_ID,
startDate: "2026-10-09",
endDate: null,
};
await saveMailboxUse(COMPANY_ID, values, "csrf", COMPANY_ID);
assert.deepEqual(
calls.map((call) => call.url),
[`/api/mailboxes/${COMPANY_ID}`, `/api/mailboxes/${COMPANY_ID}/uses/${COMPANY_ID}/update`]
);
assert.equal(calls[0]?.options.method, "GET");
assert.equal(calls[1]?.options.method, "POST");
assert.equal(calls[1]?.options.body, JSON.stringify({ values }));
});
test("los adapters mantienen la ausencia semántica sin modificar los campos del formulario", () => {
const person = {
name: "Ana",
position: " ",
email: " ana@example.test ",
phone: "",
active: false,
isContact: true,
};
const request = buildPersonRequest(person);
assert.equal(request.position, null);
assert.equal(request.email, "ana@example.test");
assert.equal(request.phone, null);
assert.equal(request.active, false);
assert.equal(person.email, " ana@example.test ");
assert.deepEqual(buildSiteRequest({ name: "Office", address: " " }), {
name: "Office",
address: null,
});
assert.equal(
buildSiteRequest({ name: "Office", address: " Street\n Floor " }).address,
" Street\n Floor "
);
});
test("las instrucciones de proceso conservan el orden y excluyen líneas vacías", () => {
const values = {
name: "Starter",
kind: "onboarding",
active: true,
notes: null,
lines: " First\n\n Second \r\n ",
} as const;
assert.deepEqual(buildProcessTemplateInput(values).instructions, ["First", "Second"]);
assert.equal(values.lines, " First\n\n Second \r\n ");
assert.deepEqual(buildProcessTemplateInput({ ...values, lines: "\n " }).instructions, []);
});
test("la validación de logos conserva tipos permitidos, vacío y límite exacto de tamaño", () => {
for (const type of COMPANY_LOGO_TYPES) {
assert.equal(validateCompanyLogo({ type, size: COMPANY_LOGO_LIMITS.inputBytes }), null);
assert.equal(validateCompanyLogo({ type, size: 0 }), "LOGO_TOO_LARGE");
assert.equal(
validateCompanyLogo({ type, size: COMPANY_LOGO_LIMITS.inputBytes + 1 }),
"LOGO_TOO_LARGE"
);
}
assert.equal(validateCompanyLogo({ type: "image/svg+xml", size: 1 }), "INVALID_LOGO");
});