metis-desk/apps/server/src/record-deletion.integration.ts
David 6c6eee8f93 feat: implement record deletion functionality across various configurations
- Added remove methods to repositories for application, certificate, folder, mail, and printer configurations.
- Implemented use cases for removing configurations, including validation for company and identity.
- Created HTTP routes for deleting configurations with appropriate request handling and error responses.
- Introduced a DeleteRecordDialog component for user confirmation before deletion.
- Updated UI pages to include delete actions for configurations with modal confirmation.
- Added integration tests to verify record deletion functionality and ensure data integrity.
- Documented the record deletion process and its implications in the project documentation.
2026-10-10 00:19:35 +02:00

140 lines
4.8 KiB
TypeScript

import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import type { Sequelize } from "sequelize";
import { QueryTypes } from "sequelize";
type Request = (path: string, body?: unknown) => Promise<Response>;
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 JSON_UNQUOTE(JSON_EXTRACT(i.`values`, '$.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 = JSON.stringify({
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,`values`,currentKey,driveKey) VALUES (:id,:companyId,:profileId,:needId,:values,:currentKey,NULL)",
{
replacements: {
...subject,
id: mappingId,
values,
currentKey: `${subject.profileId}:${subject.needId}`,
},
}
);
await database.query(
"INSERT INTO folder_mapping_revisions (id,mappingId,position,`values`,actorId,recordedAt) VALUES (:id,:mappingId,0,:values,: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 }>(
`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`;
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");
}