- Added `useAssetPickerController` for managing asset selection logic. - Created `AssetSelectProps` type for defining asset selection properties. - Implemented `AssetPickerDialog` for displaying the asset selection interface. - Developed `AssetPickerList` for rendering the list of selectable assets. - Introduced `AssetSelect` component for integrating the asset picker into forms. - Created `AssetList` component for shared asset listing functionality. - Added tests for workstation creation logic to ensure proper validation. - Implemented workstation asset fields and association fields for managing workstation assets. - Developed workstation lifecycle dialog for managing work profile states. - Added form progress component for visualizing multi-step forms.
177 lines
5.5 KiB
TypeScript
177 lines
5.5 KiB
TypeScript
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<Response>;
|
|
|
|
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");
|
|
}
|