- 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.
140 lines
4.8 KiB
TypeScript
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");
|
|
}
|