import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { AssetResponseSchema, BrandResponseSchema, BrandsResponseSchema, CatalogResponseSchema, } from "@metis/assets/common"; import type { Sequelize } from "sequelize"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; import { migrateBrands } from "../../../modules/assets/src/api/infrastructure/persistence/brand-migration"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; 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); assert.ok( initial.every((brand) => !brand.builtin || brand.name === "Clónico" || brand.logoSvg !== null) ); assert.equal(initial.find((brand) => brand.name === "Clónico")?.logoSvg, null); const logoSvg = ''; await error( path, { values: { name: "SVG inválido", active: true, logoSvg: "", }, }, 400, "INVALID_REQUEST" ); 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 ); await error(`${path}/${builtin.id}/delete`, {}, 409, "BUILTIN_PROTECTED"); await error(`${path}/invalid/delete`, {}, 400, "INVALID_REQUEST"); await error(`${path}/${randomUUID()}/delete`, {}, 404, "BRAND_NOT_FOUND"); for (const active of [true, false]) { const unused = await save({ name: `Eliminar ${randomUUID()}`, active, logoSvg }); const url = `${path}/${unused.id}/delete`; await error(url, { unexpected: true }, 400, "INVALID_REQUEST"); await error(`${url}?unexpected=true`, {}, 400, "INVALID_REQUEST"); assert.equal((await request(url, {}, undefined, "invalid")).status, 403); assert.ok((await list()).some((brand) => brand.id === unused.id)); const response = await request(url, {}); assert.equal(response.status, 200, await response.clone().text()); assert.deepEqual(BrandResponseSchema.parse(await response.json()).brand, unused); await error(url, {}, 404, "BRAND_NOT_FOUND"); await migrateBrands(database); assert.equal( (await list()).some((brand) => brand.id === unused.id), false ); const recreated = await save({ name: unused.name, active }); assert.notEqual(recreated.id, unused.id); assert.equal((await request(`${path}/${recreated.id}/delete`, {})).status, 200); } const name = `Marca prueba ${randomUUID()}`; const created = await save({ name: ` ${name} `, active: true, logoSvg }); assert.equal(created.name, name); assert.equal(created.id[14], "7"); assert.equal(created.builtin, false); assert.equal(created.logoSvg, logoSvg); assert.equal((await list()).find((brand) => brand.id === created.id)?.logoSvg, logoSvg); 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, status: "operational", 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 error(`${path}/${created.id}/delete`, {}, 409, "BRAND_IN_USE"); assert.ok((await list()).some((brand) => brand.id === created.id)); await save({ name, active: false }, created.id); assert.equal((await list()).find((brand) => brand.id === created.id)?.logoSvg, logoSvg); 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 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)); await save({ name: builtin.name, active: false }, builtin.id); const changed = await save({ name: builtin.name, active: false, logoSvg }, builtin.id); assert.equal(changed.logoSvg, logoSvg); await migrateBrands(database); const after = (await list()).find((brand) => brand.id === builtin.id); assert.ok(after); assert.equal(after.active, false); assert.equal(after.logoSvg, logoSvg); await save({ name: builtin.name, active: false, logoSvg: null }, builtin.id); await migrateBrands(database); assert.equal((await list()).find((brand) => brand.id === builtin.id)?.logoSvg, null); assert.equal((await list()).length, new Set((await list()).map((brand) => brand.id)).size); await save({ name: builtin.name, active: true, logoSvg: builtin.logoSvg }, builtin.id); // El historial sigue protegiendo la marca aunque ningún activo la use actualmente. await asset(companyId, { ...renamed.values, brandId: null }, first.id); await asset(otherId, { ...second.values, brandId: null }, second.id); await error(`${path}/${created.id}/delete`, {}, 409, "BRAND_IN_USE"); assert.ok((await list()).some((brand) => brand.id === created.id)); const racing = await save({ name: `Concurrente eliminación ${randomUUID()}`, active: true }); const [association, deletion] = await Promise.all([ request(`/assets/${companyId}`, { values: { ...values, brandId: racing.id } }), request(`${path}/${racing.id}/delete`, {}), ]); if (deletion.status === 200) { assert.equal(association.status, 409, await association.clone().text()); assert.equal( (await list()).some((brand) => brand.id === racing.id), false ); } else { assert.equal(association.status, 201, await association.clone().text()); assert.equal(deletion.status, 409, await deletion.clone().text()); assert.equal(z.object({ code: z.string() }).parse(await deletion.json()).code, "BRAND_IN_USE"); } }