import assert from "node:assert/strict"; import { AssetResponseSchema, CatalogResponseSchema, InventoryResponseSchema, SaveDefinitionResponseSchema, } from "@metis/assets/common"; import { CompanyResponseSchema } from "@metis/companies/common"; import type { Sequelize } from "sequelize"; import { z } from "zod/v4"; export async function verifyInventory( request: (path: string, body?: unknown, origin?: string, token?: string) => Promise, companyId: string, otherId: string, database: Sequelize ) { const path = `/assets/${companyId}`; async function save(url: string, values: unknown) { const response = await request(url, { values }); assert.ok([200, 201].includes(response.status), await response.clone().text()); return AssetResponseSchema.parse(await response.json()).asset; } async function error(url: string, values: unknown, code: string, status = 409) { const response = await request(url, { values }); assert.equal(response.status, status); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); } async function read(url = path) { const response = await request(url); assert.equal(response.status, 200); return InventoryResponseSchema.parse(await response.json()); } const catalog = CatalogResponseSchema.parse( await (await request(`/asset-catalog/${companyId}`)).json() ); const monitor = catalog.items.find((item) => item.values.name === "Monitor"); assert.ok(monitor); const laptop = catalog.items.find((item) => item.values.name === "Portátil"); assert.ok(laptop); const inch = catalog.items.find((item) => item.values.name === "Pulgadas"); assert.ok(inch); async function person(company: string) { const response = await request(`/companies/${company}/people`, { name: "Persona inventario", position: null, email: null, phone: null, active: true, isContact: false, }); assert.equal(response.status, 201); const saved = CompanyResponseSchema.parse(await response.json()).company.people.at(-1); assert.ok(saved); return saved; } async function site(company: string) { const response = await request(`/companies/${company}/sites`, { name: "Sede inventario", address: null, }); assert.equal(response.status, 201); const saved = CompanyResponseSchema.parse(await response.json()).company.sites.at(-1); assert.ok(saved); return saved; } const ownPerson = await person(companyId); const foreignPerson = await person(otherId); const ownSite = await site(companyId); const foreignSite = await site(otherId); const input = { name: "MON-01", templateId: monitor.id, siteId: null, personId: null, status: "available", notes: "", characteristics: {}, }; assert.equal((await read()).assets.length, 0); const created = await save(path, input); assert.equal(created.history.length, 1); assert.equal(created.id.charAt(14), "7"); assert.equal(created.history[0]?.id.charAt(14), "7"); assert.ok(created.history[0]?.actorName); assert.ok(created.history[0]?.actorId); assert.equal((await read(`/assets/${otherId}`)).assets.length, 0); await error(`/assets/${otherId}/${created.id}/update`, input, "ASSET_NOT_FOUND", 404); await error(path, { ...input, siteId: foreignSite.id }, "SITE_NOT_FOUND", 404); await error(path, { ...input, personId: foreignPerson.id }, "PERSON_UNAVAILABLE"); await error(path, { ...input, characteristics: { [inch.id]: "24" } }, "INVALID_CHARACTERISTICS"); await error( path, { ...input, characteristics: { [ownPerson.id]: 24 } }, "INVALID_CHARACTERISTICS" ); await error(path, { ...input, actorId: ownPerson.id }, "INVALID_REQUEST", 400); assert.equal((await request(path, { values: input }, "https://other.example")).status, 403); assert.equal((await request(path, { values: input }, undefined, "a".repeat(64))).status, 403); const assigned = await save(`${path}/${created.id}/update`, { ...input, siteId: ownSite.id, personId: ownPerson.id, status: "in_use", characteristics: { [inch.id]: 24 }, }); assert.equal(assigned.id, created.id); assert.equal(assigned.history.length, 2); assert.equal(assigned.history[0]?.values.personId, null); const unchanged = await save(`${path}/${created.id}/update`, assigned.values); assert.deepEqual(unchanged.history, assigned.history); await error( `${path}/${created.id}/update`, { ...assigned.values, templateId: laptop.id }, "TEMPLATE_FIXED" ); await error( `${path}/${created.id}/update`, { ...assigned.values, status: "retired" }, "RETIRED_ASSIGNED" ); const deactivated = await request(`/companies/${companyId}/people/${ownPerson.id}/update`, { name: ownPerson.name, position: null, email: null, phone: null, active: false, isContact: false, }); assert.equal(deactivated.status, 200); const kept = await save(`${path}/${created.id}/update`, { ...assigned.values, notes: "Persona inactiva: revisar entrega.", }); assert.equal(kept.values.personId, ownPerson.id); await error(path, { ...input, personId: ownPerson.id }, "PERSON_UNAVAILABLE"); const retired = await save(`${path}/${created.id}/update`, { ...kept.values, personId: null, status: "retired", }); assert.equal(retired.history.length, 4); assert.equal(retired.history[1]?.values.personId, ownPerson.id); const templateResponse = await request(`/asset-catalog/${companyId}`, { values: { kind: "template", name: "Monitor obligatorio inventario", description: "", fields: [{ fieldId: inch.id, required: true }], }, }); assert.equal(templateResponse.status, 201); const templateId = SaveDefinitionResponseSchema.parse(await templateResponse.json()).savedId; await error(path, { ...input, templateId }, "REQUIRED_CHARACTERISTICS"); await error(`/assets/${otherId}`, { ...input, templateId }, "TEMPLATE_UNAVAILABLE"); const custom = await save(path, { ...input, templateId, name: "MON-02", characteristics: { [inch.id]: 0 }, }); const revisionId = custom.history[0]?.id; assert.equal( ( await request(`/asset-catalog/${companyId}/${templateId}/update`, { values: { kind: "template", name: "Plantilla renombrada", description: "", fields: [{ fieldId: inch.id, required: false }], }, }) ).status, 200 ); const current = (await read()).assets.find((asset) => asset.id === custom.id); assert.ok(current); assert.equal(current.specification.templateName, "Monitor obligatorio inventario"); assert.equal(current.specification.fields[0]?.required, true); await error( `${path}/${custom.id}/update`, { ...custom.values, characteristics: {} }, "REQUIRED_CHARACTERISTICS" ); assert.equal( (await request(`/asset-catalog/${companyId}/${templateId}/status`, { active: false })).status, 200 ); await error(path, { ...input, templateId }, "TEMPLATE_UNAVAILABLE"); const updated = await save(`${path}/${custom.id}/update`, { ...custom.values, notes: "Plantilla conservada", }); assert.equal(updated.history[0]?.id, revisionId); const stored = (await read()).assets.find((asset) => asset.id === updated.id); assert.deepEqual(stored, updated); // Corrupción cruzada simulada solo en la base de pruebas y restaurada incluso ante fallo. const corrupt = { ...updated.values, personId: foreignPerson.id }; const lastRevision = updated.history.at(-1); assert.ok(lastRevision); await database.query("UPDATE assets SET personId=:person, `values`=:values WHERE id=:id", { replacements: { person: foreignPerson.id, values: JSON.stringify(corrupt), id: updated.id }, }); await database.query( "UPDATE asset_revisions SET personId=:person, `values`=:values WHERE id=:id", { replacements: { person: foreignPerson.id, values: JSON.stringify(corrupt), id: lastRevision.id, }, } ); try { assert.equal((await request(path)).status, 500); } finally { await database.query( "UPDATE asset_revisions SET personId=:person, `values`=:values WHERE id=:id", { replacements: { person: updated.values.personId, values: JSON.stringify(updated.values), id: lastRevision.id, }, } ); await database.query("UPDATE assets SET personId=:person, `values`=:values WHERE id=:id", { replacements: { person: updated.values.personId, values: JSON.stringify(updated.values), id: updated.id, }, }); } assert.equal((await request("/assets/00000000-0000-4000-8000-000000000001")).status, 404); }