import assert from "node:assert/strict"; import { AssetResponseSchema, CatalogResponseSchema, InventoryResponseSchema, RelationshipResponseSchema, SaveDefinitionResponseSchema, } from "@metis/assets/common"; import type { Sequelize } from "sequelize"; import { z } from "zod/v4"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; export async function verifyRelationships( request: Request, companyId: string, otherId: string, database: Sequelize ) { const path = `/assets/${companyId}`; const catalog = CatalogResponseSchema.parse( await (await request(`/asset-catalog/${companyId}`)).json() ); const type = catalog.items.find( (item) => item.values.kind === "relation" && item.values.symmetric ); assert.ok(type); const template = catalog.items.find( (item) => item.values.kind === "template" && item.values.name === "Monitor" ); assert.ok(template); const own = InventoryResponseSchema.parse(await (await request(path)).json()).assets; const first = own[0]; const second = own[1]; assert.ok(first && second); const foreignResponse = await request(`/assets/${otherId}`, { values: { name: "MON-Foreign", templateId: template.id, status: "available", siteId: null, personId: null, characteristics: {}, notes: "", }, }); assert.equal(foreignResponse.status, 201); const foreign = AssetResponseSchema.parse(await foreignResponse.json()).asset; const input = { sourceId: first.id, targetId: second.id, typeId: type.id, notes: "USB" }; const linkPath = `${path}/relationships`; const save = async (values: unknown) => { const response = await request(linkPath, { values }); assert.equal(response.status, 201, await response.clone().text()); return RelationshipResponseSchema.parse(await response.json()).relationship; }; const error = async (values: unknown, code: string, status = 409) => { const response = await request(linkPath, { values }); assert.equal(response.status, status); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); }; await error({ ...input, targetId: first.id }, "SELF_RELATIONSHIP"); await error({ ...input, targetId: foreign.id }, "ASSET_NOT_FOUND", 404); await error({ ...input, typeId: template.id }, "RELATION_TYPE_UNAVAILABLE"); await error({ ...input, createdBy: first.id }, "INVALID_REQUEST", 400); assert.equal((await request(linkPath, { values: input }, "https://other.example")).status, 403); assert.equal((await request(linkPath, { values: input }, undefined, "a".repeat(64))).status, 403); const [created, retry] = await Promise.all([save(input), save(input)]); assert.equal(created.id, retry.id); assert.equal(created.id.charAt(14), "7"); assert.ok(created.createdByName); assert.equal((await save({ ...input, sourceId: second.id, targetId: first.id })).id, created.id); await error({ ...input, notes: "Otro cable" }, "RELATIONSHIP_EXISTS"); await assert.rejects( database.query("UPDATE asset_relationships SET targetId=:target WHERE id=:id", { replacements: { target: foreign.id, id: created.id }, }) ); assert.equal( (await request(`/asset-catalog/${companyId}/${type.id}/status`, { active: false })).status, 200 ); await error(input, "RELATION_TYPE_UNAVAILABLE"); const endPath = `${path}/relationships/${created.id}/end`; const ending = await request(endPath, {}); assert.equal(ending.status, 200); const ended = RelationshipResponseSchema.parse(await ending.json()).relationship; assert.ok(ended.endedAt); assert.ok(ended.endedByName); assert.deepEqual( RelationshipResponseSchema.parse(await (await request(endPath, {})).json()).relationship, ended ); assert.equal( (await request(`/assets/${otherId}/relationships/${created.id}/end`, {})).status, 404 ); assert.equal( (await request(`/asset-catalog/${companyId}/${type.id}/status`, { active: null })).status, 200 ); const replacement = await save(input); assert.notEqual(replacement.id, created.id); await verifyDirectional(request, companyId, otherId, first.id, second.id); const stored = InventoryResponseSchema.parse(await (await request(path)).json()).relationships; assert.deepEqual( stored.find((item) => item.id === ended.id), ended ); assert.ok( !InventoryResponseSchema.parse(await (await request(`/assets/${otherId}`)).json()).relationships .length ); } async function verifyDirectional( request: Request, companyId: string, otherId: string, sourceId: string, targetId: string ) { const definition = { kind: "relation", name: "Replica a", inverseName: "Recibe réplica de", description: "", symmetric: false, }; const response = await request(`/asset-catalog/${companyId}`, { values: definition }); assert.equal(response.status, 201); const typeId = SaveDefinitionResponseSchema.parse(await response.json()).savedId; const path = `/assets/${companyId}/relationships`; const values = { sourceId, targetId, typeId, notes: "" }; const added = await request(path, { values }); assert.equal(added.status, 201); const link = RelationshipResponseSchema.parse(await added.json()).relationship; const reverse = await request(path, { values: { ...values, sourceId: targetId, targetId: sourceId }, }); assert.equal(reverse.status, 201); assert.notEqual(RelationshipResponseSchema.parse(await reverse.json()).relationship.id, link.id); const edited = await request(`/asset-catalog/${companyId}/${typeId}/update`, { values: { ...definition, name: "Replica hacia" }, }); assert.equal(edited.status, 200); const conversion = await request(`/asset-catalog/${companyId}/${typeId}/update`, { values: { ...definition, symmetric: true }, }); assert.equal(conversion.status, 409); const stored = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ).relationships.find((item) => item.id === link.id); assert.equal(stored?.values.name, "Replica a"); const other = await request(`/assets/${otherId}/relationships`, { values }); assert.equal(other.status, 404); }