import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import type { Sequelize } from "sequelize"; import { QueryTypes } from "sequelize"; import { z } from "zod/v4"; type Request = (path: string, body?: unknown) => Promise; async function verifyProfileDeletionBlocked( request: Request, row: { companyId: string; profileId?: string }, table: string ) { if (!row.profileId) { return; } const path = `/work-profiles/${row.companyId}/${row.profileId}`; assert.equal((await request(`${path}/deactivate`, {})).status, 200); const blocked = await request(`${path}/delete`, {}); assert.equal(blocked.status, 409); assert.equal( z.object({ code: z.string() }).parse(await blocked.json()).code, "PROFILE_IN_USE", `La referencia de ${table} debe impedir eliminar el perfil` ); } export async function verifyRecordDeletion(request: Request, database: Sequelize) { const counts = async () => { const result: number[] = []; for (const table of [ "company_contacts", "assets", "resources", "work_profiles", "access_items", "mailbox_uses", ]) { const rows = await database.query<{ count: number }>( `SELECT COUNT(*) AS count FROM ${table}`, { type: QueryTypes.SELECT } ); result.push(Number(rows[0]?.count)); } return result; }; const folderSubjects = await database.query<{ profileId: string; companyId: string; needId: string; actorId: string; }>( "SELECT p.id AS profileId,p.companyId,i.id AS needId,u.id AS actorId FROM work_profiles p JOIN access_workspaces w ON w.companyId=p.companyId JOIN access_items i ON i.workspaceId=w.id CROSS JOIN users u WHERE i.kind='need' LIMIT 1", { type: QueryTypes.SELECT } ); const subject = folderSubjects[0]; assert.ok(subject, "Debe existir un perfil y una necesidad de recurso para la prueba de carpeta"); const mappingId = randomUUID(); const values = { profileId: subject.profileId, needId: subject.needId, path: "\\\\SERVER\\Test", driveLetter: null, state: "pending", notes: null, }; await database.query( "INSERT INTO folder_mappings (id,companyId,profileId,needId,path,driveLetter,state,notes,currentKey,driveKey) VALUES (:id,:companyId,:profileId,:needId,:path,:driveLetter,:state,:notes,:currentKey,NULL)", { replacements: { ...subject, id: mappingId, ...values, currentKey: `${subject.profileId}:${subject.needId}`, }, } ); await database.query( "INSERT INTO folder_mapping_revisions (id,mappingId,position,profileId,needId,path,driveLetter,state,notes,actorId,recordedAt) VALUES (:id,:mappingId,0,:profileId,:needId,:path,:driveLetter,:state,:notes,:actorId,:recordedAt)", { replacements: { id: randomUUID(), mappingId, ...values, actorId: subject.actorId, recordedAt: new Date().toISOString(), }, } ); const before = await counts(); const specs = [ [ "application_configurations", "application_configuration_revisions", "configurationId", "application-configurations", ], [ "certificate_configurations", "certificate_configuration_revisions", "configurationId", "certificate-configurations", ], [ "printer_configurations", "printer_configuration_revisions", "configurationId", "printer-configurations", ], [ "mail_configurations", "mail_configuration_revisions", "configurationId", "mail-configurations", ], ["folder_mappings", "folder_mapping_revisions", "mappingId", "folder-mappings"], ["asset_support", "asset_support_revisions", "connectionId", "asset-support"], ["asset_relationships", null, null, "assets"], ] as const; for (const [table, history, foreignKey, endpoint] of specs) { const rows = await database.query<{ id: string; companyId: string; assetId?: string; profileId?: string; }>(`SELECT * FROM ${table}`, { type: QueryTypes.SELECT }); assert.ok(rows.length > 0, `No existen datos para probar ${table}`); // Todos los estados, incluidos los cerrados, admiten corregir un alta equivocada. for (const row of rows) { const suffix = endpoint === "asset-support" ? `${row.assetId}/${row.id}` : endpoint === "assets" ? `relationships/${row.id}` : row.id; const path = `/${endpoint}/${row.companyId}/${suffix}/delete`; await verifyProfileDeletionBlocked(request, row, table); assert.equal( (await request(`/${endpoint}/${randomUUID()}/${suffix}/delete`, {})).status, 404 ); assert.equal((await request(path.replace(row.companyId, "invalid"), {})).status, 400); assert.equal((await request(path, { unexpected: true })).status, 400); assert.equal((await request(path, {})).status, 200); assert.equal((await request(path, {})).status, 404); const remaining = await database.query(`SELECT id FROM ${table} WHERE id=:id`, { replacements: { id: row.id }, type: QueryTypes.SELECT, }); assert.equal(remaining.length, 0); if (history && foreignKey) { const revisions = await database.query( `SELECT id FROM ${history} WHERE ${foreignKey}=:id`, { replacements: { id: row.id }, type: QueryTypes.SELECT } ); assert.equal(revisions.length, 0); } } } assert.deepEqual(await counts(), before, "La eliminación debe conservar las entidades de origen"); }