- Changed asset status label from "status" to "technicalStatus" in AssetForm component. - Added help text for technical status in AssetForm. - Updated test cases to reflect changes in asset status from "available" to "operational". - Enhanced workstation form to utilize new asset status checks and display appropriate messages. - Refactored asset context to replace "available" with "notRetired" across various application policies and repositories. - Introduced new utility functions for asset status management, including technical status determination and assignment validation. - Added integration tests for asset state verification and unlinking assets from workstations.
316 lines
10 KiB
TypeScript
316 lines
10 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
import { InventoryResponseSchema as AssetsSchema } from "@metis/assets/common";
|
|
import type { RemoteConnectionInput } from "@metis/companies/common";
|
|
import {
|
|
CompanyResponseSchema,
|
|
RemoteConnectionResponseSchema,
|
|
RemoteConnectionsResponseSchema,
|
|
TeleworkAuthorizationResponseSchema,
|
|
} from "@metis/companies/common";
|
|
import { CatalogResponseSchema, ResourceResponseSchema } from "@metis/resources/common";
|
|
import type { Sequelize } from "sequelize";
|
|
import { z } from "zod/v4";
|
|
|
|
import { unlinkAssetFromWorkstations } from "./unlink-asset.integration-helper";
|
|
|
|
type Request = (path: string, body?: unknown, origin?: string, token?: string) => Promise<Response>;
|
|
|
|
export async function verifyRemoteConnections(
|
|
request: Request,
|
|
companyId: string,
|
|
otherId: string,
|
|
database: Sequelize
|
|
) {
|
|
const personValues = {
|
|
name: "Persona acceso remoto",
|
|
position: null,
|
|
email: null,
|
|
phone: null,
|
|
active: true,
|
|
isContact: false,
|
|
};
|
|
const personResponse = await request(`/companies/${companyId}/people`, personValues);
|
|
|
|
assert.equal(personResponse.status, 201);
|
|
const person = CompanyResponseSchema.parse(await personResponse.json()).company.people.at(-1);
|
|
|
|
assert.ok(person);
|
|
const assets = AssetsSchema.parse(await (await request(`/assets/${companyId}`)).json()).assets;
|
|
const host = assets.find((a) => a.values.status !== "retired");
|
|
|
|
assert.ok(host);
|
|
const catalog = CatalogResponseSchema.parse(
|
|
await (await request(`/resource-catalog/${companyId}`)).json()
|
|
);
|
|
const template = catalog.items.find((item) => item.builtinKey === "template:application");
|
|
|
|
assert.ok(template);
|
|
const resourceValues = {
|
|
name: "ERP acceso remoto",
|
|
templateId: template.id,
|
|
active: true,
|
|
notes: "",
|
|
characteristics: {},
|
|
};
|
|
const resourceResponse = await request(`/resources/${companyId}`, { values: resourceValues });
|
|
|
|
assert.equal(resourceResponse.status, 201);
|
|
const resource = ResourceResponseSchema.parse(await resourceResponse.json()).resource;
|
|
const path = `/remote-connections/${companyId}`;
|
|
const input: RemoteConnectionInput = {
|
|
personId: person.id,
|
|
sourceKind: "personal_computer",
|
|
sourceAssetId: null,
|
|
targetKind: "network",
|
|
targetId: null,
|
|
siteId: null,
|
|
method: "VPN",
|
|
requiresVpn: false,
|
|
state: "configured",
|
|
notes: null,
|
|
};
|
|
|
|
const list = async (scope = companyId) => {
|
|
const response = await request(`/remote-connections/${scope}`);
|
|
|
|
assert.equal(response.status, 200);
|
|
|
|
return RemoteConnectionsResponseSchema.parse(await response.json()).connections;
|
|
};
|
|
|
|
const save = async (values: RemoteConnectionInput, id?: string) => {
|
|
const response = await request(path + (id ? `/${id}/update` : ""), { values });
|
|
|
|
assert.equal(response.status, id ? 200 : 201);
|
|
|
|
return RemoteConnectionResponseSchema.parse(await response.json()).connection;
|
|
};
|
|
|
|
const error = async (values: unknown, code: string, id?: string, status = 409) => {
|
|
const response = await request(path + (id ? `/${id}/update` : ""), { values });
|
|
|
|
assert.equal(response.status, status);
|
|
assert.equal(z.object({ code: z.string() }).parse(await response.json()).code, code);
|
|
};
|
|
|
|
await error({ ...input, actorId: person.id }, "INVALID_REQUEST", undefined, 400);
|
|
await error({ ...input, sourceAssetId: host.id }, "INVALID_REQUEST", undefined, 400);
|
|
await error({ ...input, targetKind: "asset" }, "INVALID_REQUEST", undefined, 400);
|
|
await error(
|
|
{
|
|
...input,
|
|
sourceKind: "company_asset",
|
|
sourceAssetId: host.id,
|
|
targetKind: "asset",
|
|
targetId: host.id,
|
|
},
|
|
"INVALID_REQUEST",
|
|
undefined,
|
|
400
|
|
);
|
|
await error({ ...input, personId: randomUUID() }, "REMOTE_REFERENCE_NOT_FOUND", undefined, 404);
|
|
await error(
|
|
{ ...input, targetKind: "asset", targetId: randomUUID() },
|
|
"REMOTE_REFERENCE_NOT_FOUND",
|
|
undefined,
|
|
404
|
|
);
|
|
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 concurrent = await Promise.all([
|
|
request(path, { values: input }),
|
|
request(path, { values: { ...input, method: "vpn" } }),
|
|
]);
|
|
|
|
assert.deepEqual(concurrent.map((r) => r.status).sort(), [201, 409]);
|
|
const createdResponse = concurrent.find((r) => r.status === 201);
|
|
|
|
assert.ok(createdResponse);
|
|
const created = RemoteConnectionResponseSchema.parse(await createdResponse.json()).connection;
|
|
|
|
assert.equal(created.id[14], "7");
|
|
assert.equal(created.history[0]?.id[14], "7");
|
|
assert.ok(created.needsReview && !created.authorizationEffective);
|
|
assert.deepEqual(await save(created.values, created.id), created);
|
|
await error({ ...created.values, personId: randomUUID() }, "REMOTE_TARGET_IMMUTABLE", created.id);
|
|
await error(
|
|
{ ...created.values, sourceKind: "personal_mobile" },
|
|
"REMOTE_TARGET_IMMUTABLE",
|
|
created.id
|
|
);
|
|
await error(
|
|
{ ...created.values, targetKind: "asset", targetId: host.id },
|
|
"REMOTE_TARGET_IMMUTABLE",
|
|
created.id
|
|
);
|
|
await error({ ...created.values, state: "removed" }, "INVALID_REMOTE_TRANSITION", created.id);
|
|
assert.equal((await request(`/remote-connections/${otherId}`, { values: input })).status, 404);
|
|
assert.equal((await list(otherId)).length, 0);
|
|
assert.equal(
|
|
(
|
|
await request(`/remote-connections/${otherId}/${created.id}/update`, {
|
|
values: created.values,
|
|
})
|
|
).status,
|
|
404
|
|
);
|
|
const rdp = await save({
|
|
...input,
|
|
targetKind: "asset",
|
|
targetId: host.id,
|
|
method: "RDP",
|
|
requiresVpn: true,
|
|
});
|
|
const mobile = await save({
|
|
...input,
|
|
sourceKind: "personal_mobile",
|
|
targetKind: "resource",
|
|
targetId: resource.id,
|
|
method: "VPN móvil",
|
|
});
|
|
const corporate = await save({ ...input, sourceKind: "company_asset", sourceAssetId: host.id });
|
|
|
|
assert.ok(rdp.values.requiresVpn && corporate.values.sourceAssetId === host.id);
|
|
const alternative = await save({ ...input, method: "Herramienta propia" });
|
|
|
|
await error(
|
|
{ ...alternative.values, method: created.values.method },
|
|
"REMOTE_ALREADY_OPEN",
|
|
alternative.id
|
|
);
|
|
const teleworkValues = {
|
|
personId: person.id,
|
|
active: true,
|
|
startsOn: null,
|
|
expiresOn: null,
|
|
notes: null,
|
|
};
|
|
const authResponse = await request(`/telework-authorizations/${companyId}`, {
|
|
values: teleworkValues,
|
|
});
|
|
|
|
assert.equal(authResponse.status, 201);
|
|
const auth = TeleworkAuthorizationResponseSchema.parse(await authResponse.json()).authorization;
|
|
|
|
const current = async (id: string) => {
|
|
const item = (await list()).find((item) => item.id === id);
|
|
|
|
assert.ok(item);
|
|
|
|
return item;
|
|
};
|
|
|
|
assert.equal((await current(created.id)).needsReview, false);
|
|
assert.equal((await current(mobile.id)).needsReview, false);
|
|
assert.equal(
|
|
(
|
|
await request(`/resources/${companyId}/${resource.id}/update`, {
|
|
values: { ...resourceValues, active: false },
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal((await current(mobile.id)).needsReview, true);
|
|
assert.equal((await current(created.id)).needsReview, false);
|
|
await unlinkAssetFromWorkstations(request, companyId, host.id);
|
|
assert.equal(
|
|
(
|
|
await request(`/assets/${companyId}/${host.id}/update`, {
|
|
values: { ...host.values, status: "retired" },
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal((await current(corporate.id)).needsReview, true);
|
|
assert.equal((await current(rdp.id)).needsReview, true);
|
|
assert.equal(
|
|
(await request(`/assets/${companyId}/${host.id}/update`, { values: host.values })).status,
|
|
200
|
|
);
|
|
assert.equal((await current(corporate.id)).needsReview, false);
|
|
assert.equal(
|
|
(
|
|
await request(`/telework-authorizations/${companyId}/${auth.id}/update`, {
|
|
values: { ...teleworkValues, expiresOn: "2000-01-01" },
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal((await current(created.id)).needsReview, true);
|
|
assert.equal(
|
|
(
|
|
await request(`/telework-authorizations/${companyId}/${auth.id}/update`, {
|
|
values: teleworkValues,
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal(
|
|
(
|
|
await request(`/companies/${companyId}/people/${person.id}/update`, {
|
|
...personValues,
|
|
active: false,
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal((await current(created.id)).authorizationEffective, false);
|
|
assert.equal((await current(created.id)).history.length, 1);
|
|
assert.equal(
|
|
(
|
|
await request(`/telework-authorizations/${companyId}/${auth.id}/update`, {
|
|
values: { ...teleworkValues, active: false },
|
|
})
|
|
).status,
|
|
200
|
|
);
|
|
assert.equal(
|
|
(await request(`/companies/${companyId}/people/${person.id}/update`, personValues)).status,
|
|
200
|
|
);
|
|
assert.equal((await current(created.id)).needsReview, true);
|
|
const retiring = await save({ ...created.values, state: "removal_pending" }, created.id);
|
|
const removed = await save({ ...retiring.values, state: "removed" }, created.id);
|
|
|
|
assert.equal(removed.needsReview, false);
|
|
assert.equal(removed.history.length, 3);
|
|
assert.ok(removed.history.every((r) => r.actorId !== person.id));
|
|
assert.deepEqual(await save(removed.values, removed.id), removed);
|
|
await error(
|
|
{ ...removed.values, notes: "Cambiar cerrada" },
|
|
"INVALID_REMOTE_TRANSITION",
|
|
removed.id
|
|
);
|
|
const replacement = await save(created.values);
|
|
|
|
assert.notEqual(replacement.id, created.id);
|
|
await database.query("UPDATE remote_connections SET currentKey=:key WHERE id=:id", {
|
|
replacements: { id: replacement.id, key: "invalid" },
|
|
});
|
|
try {
|
|
const corrupt = await request(path);
|
|
|
|
assert.equal(corrupt.status, 500);
|
|
assert.ok(!(await corrupt.text()).includes("SELECT"));
|
|
} finally {
|
|
// El cierre conserva historial y libera la clave; este fixture se limpia al reiniciar la suite.
|
|
await database.query("UPDATE remote_connections SET currentKey=SHA2(:key,256) WHERE id=:id", {
|
|
replacements: {
|
|
id: replacement.id,
|
|
key: [
|
|
person.id,
|
|
input.sourceKind,
|
|
"",
|
|
input.targetKind,
|
|
"",
|
|
"",
|
|
replacement.values.method.toLowerCase(),
|
|
].join("|"),
|
|
},
|
|
});
|
|
}
|
|
assert.equal((await current(replacement.id)).history.length, 1);
|
|
}
|