metis-desk/apps/server/src/brands.integration.ts
2026-10-07 18:08:48 +02:00

182 lines
7.2 KiB
TypeScript

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import {
AssetResponseSchema,
BrandResponseSchema,
BrandsResponseSchema,
CatalogResponseSchema,
} from "@metis/assets/common";
import { QueryTypes, type Sequelize } from "sequelize";
import { z } from "zod/v4";
import {
importLegacyBrands,
migrateBrands,
} from "../../../modules/assets/src/api/infrastructure/persistence/brand-migration";
type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>;
export async function verifyBrands(
request: Request,
companyId: string,
otherId: string,
database: Sequelize
) {
const path = "/asset-brands";
async function list() {
const response = await request(path);
assert.equal(response.status, 200);
return BrandsResponseSchema.parse(await response.json()).brands;
}
async function save(values: unknown, id?: string) {
const response = await request(path + (id ? `/${id}/update` : ""), { values });
assert.equal(response.status, id ? 200 : 201, await response.clone().text());
return BrandResponseSchema.parse(await response.json()).brand;
}
async function error(url: string, body: unknown, status: number, code: string) {
const response = await request(url, body);
assert.equal(response.status, status, await response.clone().text());
assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code);
}
const initial = await list();
for (const name of ["Microsoft", "HP", "LG", "Kyocera", "Zebra", "Logitech", "Clónico"]) {
assert.ok(initial.some((brand) => brand.name === name && brand.builtin));
}
const builtin = initial.find((brand) => brand.name === "LG");
assert.ok(builtin);
await error(
`${path}/${builtin.id}/update`,
{ values: { name: "Otro LG", active: true } },
409,
"BUILTIN_PROTECTED"
);
await error(path, { values: { name: " lg ", active: true } }, 409, "NAME_EXISTS");
await error(path, { values: { name: " ", active: true } }, 400, "INVALID_REQUEST");
await error(
`${path}/invalid/update`,
{ values: { name: "Válida", active: true } },
400,
"INVALID_REQUEST"
);
await error(
`${path}/${randomUUID()}/update`,
{ values: { name: "Válida", active: true } },
404,
"BRAND_NOT_FOUND"
);
assert.equal(
(await request(path, { values: { name: "Sin CSRF", active: true } }, undefined, "invalid"))
.status,
403
);
const name = `Marca prueba ${randomUUID()}`;
const created = await save({ name: ` ${name} `, active: true });
assert.equal(created.name, name);
assert.equal(created.id[14], "7");
assert.equal(created.builtin, false);
const concurrentName = `Concurrente ${randomUUID()}`;
const concurrent = await Promise.all([
request(path, { values: { name: concurrentName, active: true } }),
request(path, { values: { name: concurrentName.toUpperCase(), active: true } }),
]);
assert.deepEqual(concurrent.map((response) => response.status).sort(), [201, 409]);
const catalog = CatalogResponseSchema.parse(
await (await request(`/asset-catalog/${companyId}`)).json()
);
const template = catalog.items.find((item) => item.values.name === "Portátil");
assert.ok(template);
const field = catalog.items.find((item) => item.referenceCatalog === "brands");
assert.ok(field);
const values = {
name: "PORT-MARCA",
templateId: template.id,
siteId: null,
personId: null,
status: "available",
notes: "",
brandId: created.id,
characteristics: { [field.id]: "Nombre manipulado" },
};
async function asset(company: string, input: unknown, id?: string) {
const response = await request(`/assets/${company}${id ? `/${id}/update` : ""}`, {
values: input,
});
assert.equal(response.status, id ? 200 : 201, await response.clone().text());
return AssetResponseSchema.parse(await response.json()).asset;
}
const first = await asset(companyId, values);
assert.equal(first.values.brandId, created.id);
assert.equal(first.values.characteristics[field.id], name);
const same = await asset(companyId, { ...first.values }, first.id);
assert.equal(same.history.length, 1);
const foreignCatalog = CatalogResponseSchema.parse(
await (await request(`/asset-catalog/${otherId}`)).json()
);
const foreignTemplate = foreignCatalog.items.find((item) => item.values.name === "Portátil");
assert.ok(foreignTemplate);
const second = await asset(otherId, { ...values, templateId: foreignTemplate.id });
assert.equal(second.values.brandId, created.id);
await save({ name, active: false }, created.id);
await error(`/assets/${companyId}`, { values }, 409, "BRAND_UNAVAILABLE");
const retained = await asset(
companyId,
{ ...first.values, notes: "Conserva marca inactiva" },
first.id
);
assert.equal(retained.values.brandId, created.id);
await save({ name: `${name} renombrada`, active: true }, created.id);
const renamed = await asset(companyId, retained.values, first.id);
assert.equal(renamed.values.characteristics[field.id], `${name} renombrada`);
assert.equal(renamed.history[0]?.values.characteristics[field.id], name);
await error(
`/assets/${companyId}`,
{ values: { ...values, brandId: randomUUID() } },
409,
"BRAND_UNAVAILABLE"
);
const snapshots = await database.query<{ brandId: string; values: unknown }>(
"SELECT brandId,`values` FROM asset_revisions WHERE assetId=:id ORDER BY position",
{ replacements: { id: first.id }, type: QueryTypes.SELECT }
);
assert.ok(snapshots.every((row) => row.brandId === created.id));
const legacyName = `Antigua ${randomUUID()}`;
const legacyValues = {
...values,
brandId: undefined,
characteristics: { [field.id]: legacyName },
};
const legacy = await asset(companyId, legacyValues);
const before = await database.query(
"SELECT * FROM asset_revisions WHERE assetId=:id ORDER BY position",
{ replacements: { id: legacy.id }, type: QueryTypes.SELECT }
);
await database.query("UPDATE brand_catalog_lock SET legacyImported=0 WHERE id=1");
await importLegacyBrands(database);
const imported = (await list()).find((brand) => brand.name === legacyName);
assert.ok(imported);
assert.equal(imported.builtin, false);
await save({ name: `${legacyName} editada`, active: false }, imported.id);
await importLegacyBrands(database);
assert.equal(
(await list()).some((brand) => brand.name === legacyName),
false
);
assert.deepEqual(
await database.query("SELECT * FROM asset_revisions WHERE assetId=:id ORDER BY position", {
replacements: { id: legacy.id },
type: QueryTypes.SELECT,
}),
before
);
const linked = await asset(companyId, { ...legacy.values, brandId: builtin.id }, legacy.id);
assert.equal(linked.history.length, 2);
assert.equal(linked.history[0]?.values.characteristics[field.id], legacyName);
assert.equal(linked.history[0]?.values.brandId, undefined);
await save({ name: builtin.name, active: false }, builtin.id);
await migrateBrands(database);
const after = (await list()).find((brand) => brand.id === builtin.id);
assert.equal(after?.active, false);
assert.equal((await list()).length, new Set((await list()).map((brand) => brand.id)).size);
await save({ name: builtin.name, active: true }, builtin.id);
}