import assert from "node:assert/strict"; import { InventoryResponseSchema, WorkstationResponseSchema } from "@metis/assets/common"; import { CompanyResponseSchema } from "@metis/companies/common"; import type { Sequelize } from "sequelize"; import { z } from "zod/v4"; import { removeSharedWorkstationAssets } from "../../../modules/assets/src/api/infrastructure/persistence/remove-shared-workstation-assets"; import { initializeWorkstationMembers } from "../../../modules/assets/src/api/infrastructure/persistence/workstation-relational"; type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise; export async function verifyWorkstations( request: Request, companyId: string, otherId: string, database: Sequelize ) { const path = `/assets/${companyId}/workstations`; const own = CompanyResponseSchema.parse( await (await request(`/companies/${companyId}`)).json() ).company; const other = CompanyResponseSchema.parse( await (await request(`/companies/${otherId}`)).json() ).company; const inventory = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); for (const asset of inventory.assets.filter((item) => item.values.status !== "operational")) { assert.equal( ( await request(`/assets/${companyId}/${asset.id}/update`, { values: { ...asset.values, status: "operational" }, }) ).status, 200 ); } const foreign = InventoryResponseSchema.parse(await (await request(`/assets/${otherId}`)).json()) .assets[0]; const first = inventory.assets[0]; const second = inventory.assets[1]; assert.ok(first && second && foreign); const personFields = { name: "Persona puesto", position: null, email: null, phone: null, active: true, isContact: false, }; const personResponse = await request(`/companies/${companyId}/people`, personFields); assert.equal(personResponse.status, 201); const person = CompanyResponseSchema.parse(await personResponse.json()).company.people.at(-1); assert.ok(person); const otherPersonResponse = await request(`/companies/${otherId}/people`, personFields); assert.equal(otherPersonResponse.status, 201); const otherPerson = CompanyResponseSchema.parse( await otherPersonResponse.json() ).company.people.at(-1); assert.ok(otherPerson); const input = { name: "Puesto Ana", active: true, siteId: own.sites[0]?.id ?? null, personId: person.id, assetIds: [first.id, second.id], }; const save = async (values: unknown, id?: string) => { const response = await request(id ? `${path}/${id}/update` : path, { values }); assert.equal(response.status, id ? 200 : 201, await response.clone().text()); return WorkstationResponseSchema.parse(await response.json()).workstation; }; const error = async (values: unknown, code: string, id?: string, status = 409) => { const response = await request(id ? `${path}/${id}/update` : path, { values }); assert.equal(response.status, status); assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code); }; await error({ ...input, assetIds: [foreign.id] }, "ASSET_NOT_FOUND", undefined, 404); await error({ ...input, siteId: other.sites[0]?.id }, "SITE_NOT_FOUND", undefined, 404); await error({ ...input, personId: otherPerson.id }, "PERSON_UNAVAILABLE"); await error({ ...input, assetIds: [first.id, first.id] }, "INVALID_MEMBERS"); await error({ ...input, actorId: first.id }, "INVALID_REQUEST", undefined, 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 [a, b] = await Promise.all([ request(path, { values: input }), request(path, { values: { ...input, name: "Puesto conflictivo" } }), ]); assert.deepEqual([a.status, b.status].sort(), [201, 409]); const success = a.status === 201 ? a : b; const created = WorkstationResponseSchema.parse(await success.json()).workstation; assert.equal(created.id.charAt(14), "7"); assert.equal(created.history[0]?.id.charAt(14), "7"); assert.ok(created.history[0]?.actorName); const unchanged = await save( { ...created.values, assetIds: [...created.values.assetIds].reverse() }, created.id ); assert.deepEqual(unchanged, created); const empty = await save({ ...input, name: "Puesto vacío", siteId: null, assetIds: [] }); assert.equal( ( await request(`/companies/${companyId}/people/${person.id}/update`, { ...personFields, active: false, }) ).status, 200 ); await error({ ...empty.values, name: "Nueva asignación inactiva" }, "PERSON_UNAVAILABLE"); assert.deepEqual(await save(created.values, created.id), created); await error({ ...empty.values, assetIds: [first.id] }, "ASSET_IN_WORKSTATION", empty.id); await error({ ...empty.values, assetIds: [created.id] }, "ASSET_NOT_FOUND", empty.id, 404); const inactive = await save({ ...created.values, active: false }, created.id); assert.equal(inactive.history.length, 2); const receiver = await save({ ...empty.values, assetIds: [first.id] }, empty.id); await error({ ...created.values, active: true }, "ASSET_IN_WORKSTATION", created.id); const removed = await save({ ...receiver.values, assetIds: [] }, receiver.id); assert.equal(removed.history.length, 3); const restored = await save(created.values, created.id); assert.equal(restored.history.length, 3); assert.deepEqual(restored.history[1]?.values.assetIds, created.values.assetIds); const foreignUpdate = await request(`/assets/${otherId}/workstations/${created.id}/update`, { values: created.values, }); assert.equal(foreignUpdate.status, 404); await assert.rejects( database.query("UPDATE workstation_assets SET assetId=:asset WHERE workstationId=:id", { replacements: { asset: foreign.id, id: created.id }, }) ); await database.query("UPDATE workstation_members SET kind='invalid' WHERE workstationId=:id", { replacements: { id: restored.id }, }); try { assert.equal((await request(`/assets/${companyId}`)).status, 500); } finally { await database.query( "UPDATE workstation_members SET kind='exclusive' WHERE workstationId=:id", { replacements: { id: restored.id }, } ); } const current = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); assert.deepEqual( current.workstations.find((item) => item.id === restored.id), restored ); assert.equal( InventoryResponseSchema.parse(await (await request(`/assets/${otherId}`)).json()).workstations .length, 0 ); // Repetir la preparación del esquema conserva composición e historial. const beforeInitialization = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); await initializeWorkstationMembers(database); await initializeWorkstationMembers(database); assert.deepEqual( InventoryResponseSchema.parse(await (await request(`/assets/${companyId}`)).json()), beforeInitialization ); await error({ ...input, sharedAssetIds: [] }, "INVALID_REQUEST", undefined, 400); await save({ ...restored.values, active: false }, restored.id); const exclusive = await save({ ...input, personId: null, assetIds: [first.id] }); await error({ ...exclusive.values }, "ASSET_IN_WORKSTATION"); await save({ ...exclusive.values, active: false }, exclusive.id); const designated = await save( { ...exclusive.values, active: false, computerAssetId: first.id }, exclusive.id ); const reread = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ).workstations.find((item) => item.id === exclusive.id); assert.equal(reread?.values.computerAssetId, first.id); assert.equal(reread?.history.at(-1)?.values.computerAssetId, first.id); const repeated = await save(designated.values, designated.id); assert.equal(repeated.history.length, designated.history.length); await error( { ...designated.values, computerAssetId: second.id }, "INVALID_MEMBERS", designated.id ); // Simula las asociaciones compartidas del esquema anterior, también en su última revisión. const latest = designated.history.at(-1); assert.ok(latest); await database.transaction(async (transaction) => { const replacements = { id: designated.id, revision: latest.id, asset: second.id, company: companyId, }; await database.query( "INSERT INTO workstation_members (workstationId,assetId,kind,position) VALUES (:id,:asset,'shared',1)", { replacements, transaction } ); await database.query( "INSERT INTO workstation_revision_members (revisionId,assetId,kind,position) VALUES (:revision,:asset,'shared',1)", { replacements, transaction } ); await database.query( "INSERT INTO workstation_assets (workstationId,companyId,assetId,activeKey) VALUES (:id,:company,:asset,NULL)", { replacements, transaction } ); }); await removeSharedWorkstationAssets(database); await removeSharedWorkstationAssets(database); const migrated = InventoryResponseSchema.parse( await (await request(`/assets/${companyId}`)).json() ); assert.deepEqual( migrated.workstations.find((item) => item.id === designated.id), designated ); assert.ok(migrated.assets.some((asset) => asset.id === second.id)); const race = await Promise.all([ request(path, { values: { ...input, personId: null, name: "Reserva concurrente", assetIds: [second.id], }, }), request(path, { values: { ...input, personId: null, name: "Segunda reserva concurrente", assetIds: [second.id], }, }), ]); assert.deepEqual(race.map((response) => response.status).sort(), [201, 409]); }