metis-desk/modules/resources/src/api/application/resource-lifecycle.test.ts
David 72c2a5101d feat: implement resource lifecycle management with delete functionality
- Added `verifyResourceLifecycle` integration to handle resource lifecycle operations.
- Introduced `remove` method in `IResourceRepository` and implemented it in `SequelizeResourceRepository`.
- Created `DeleteResourceDialog` component for confirming resource deletion.
- Updated `resource.use-cases` to include `remove` and `setActive` methods for managing resource states.
- Enhanced API routes to support resource status updates and deletions.
- Added tests for resource lifecycle operations, ensuring correct behavior for activation, deactivation, and deletion.
- Updated UI components to integrate new deletion functionality and provide feedback on resource status changes.
- Improved localization for resource lifecycle actions in both English and Spanish.
2026-10-17 13:58:29 +02:00

163 lines
5.3 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { Resource } from "../domain/resource.aggregate";
import { ResourceCatalog } from "../domain/resource-catalog.aggregate";
import { ResourceCatalogError } from "../domain/resource-definition";
import { ResourceSpecification, ResourceValues } from "../domain/value-objects/resource-values.vo";
import type { IResourceRepository, InventoryContext } from "./resource.repository";
import { ResourceUseCases } from "./resource.use-cases";
function fixture() {
const company = UniqueID.generateNewID();
const actor = UniqueID.generateNewID();
const templateId = UniqueID.generateNewID().toPrimitive();
const fieldId = UniqueID.generateNewID().toPrimitive();
const spec = ResourceSpecification.create({
templateId,
templateName: "Aplicación",
fields: [{ fieldId, name: "Fabricante", type: "text", unit: "", choices: [], required: false }],
});
assert.ok(spec.isSuccess);
const values = ResourceValues.create(
{
name: "Office 2024",
templateId,
active: true,
notes: "Notas conservadas",
characteristics: { [fieldId]: "Microsoft" },
},
spec.data
);
assert.ok(values.isSuccess);
const created = Resource.create(
company,
spec.data,
values.data,
actor,
"2026-01-01T12:00:00.000Z"
);
assert.ok(created.isSuccess);
let resources = [created.data];
let inUse = false;
const context = (): InventoryContext => ({
catalog: ResourceCatalog.create(),
resources,
references: [],
actorNames: new Map([[actor.toPrimitive(), "Técnico"]]),
});
const repository: IResourceRepository = {
get: async () => Result.ok(context()),
save: async (id, action) => {
const changed = action({ ...context(), resources: id.equals(company) ? resources : [] });
if (!changed.isSuccess) {
return Result.fail(changed.error);
}
resources = [changed.data];
return Result.ok({ resource: changed.data, actorNames: context().actorNames });
},
remove: async (id, identity) => {
if (!(id.equals(company) && resources.some((resource) => resource.id.equals(identity)))) {
return Result.fail(new ResourceCatalogError("RESOURCE_NOT_FOUND"));
}
if (inUse) {
return Result.fail(new ResourceCatalogError("RESOURCE_IN_USE"));
}
resources = [];
return Result.ok(undefined);
},
};
return {
service: new ResourceUseCases(repository),
created: created.data,
company: company.toPrimitive(),
actor: actor.toPrimitive(),
setInUse: () => {
inUse = true;
},
};
}
test("recurso: activar/desactivar conserva datos e historial y repetir estado no añade revisiones", async () => {
const { service, company, actor, created } = fixture();
for (const [active, revisions] of [
[false, 2],
[false, 2],
[true, 3],
] as const) {
const result = await service.setActive(company, created.id.toPrimitive(), active, actor);
assert.ok(result.isSuccess);
assert.equal(result.data.resource.values.toPrimitive().active, active);
assert.equal(result.data.resource.history.length, revisions);
assert.deepEqual(
result.data.resource.values.toPrimitive().characteristics,
created.values.toPrimitive().characteristics
);
assert.equal(
result.data.resource.values.toPrimitive().notes,
created.values.toPrimitive().notes
);
assert.equal(result.data.resource.history.at(-1)?.actorId.toPrimitive(), actor);
}
});
test("recurso: valida identidades y rechaza actuar sobre recursos ajenos", async () => {
const { service, company, actor, created } = fixture();
const id = created.id.toPrimitive();
const invalid = await service.setActive("invalid", id, false, actor);
assert.ok(!invalid.isSuccess);
assert.equal(invalid.error.code, "INVALID_REQUEST");
const invalidActor = await service.setActive(company, id, false, "invalid");
assert.ok(!invalidActor.isSuccess);
const invalidDelete = await service.remove(company, "invalid");
assert.ok(!invalidDelete.isSuccess);
const other = UniqueID.generateNewID().toPrimitive();
const foreign = await service.setActive(other, id, false, actor);
assert.ok(!foreign.isSuccess);
assert.equal(foreign.error.code, "RESOURCE_NOT_FOUND");
const foreignDelete = await service.remove(other, id);
assert.ok(!foreignDelete.isSuccess);
assert.equal(foreignDelete.error.code, "RESOURCE_NOT_FOUND");
});
test("recurso: propaga el bloqueo por asociaciones y permite desactivar", async () => {
const { service, company, actor, created, setInUse } = fixture();
setInUse();
const removed = await service.remove(company, created.id.toPrimitive());
assert.ok(!removed.isSuccess);
assert.equal(removed.error.code, "RESOURCE_IN_USE");
assert.ok((await service.setActive(company, created.id.toPrimitive(), false, actor)).isSuccess);
});
test("recurso: elimina y la repetición devuelve ausencia sin efectos adicionales", async () => {
const { service, company, created } = fixture();
assert.ok((await service.remove(company, created.id.toPrimitive())).isSuccess);
const missing = await service.remove(company, created.id.toPrimitive());
assert.ok(!missing.isSuccess);
assert.equal(missing.error.code, "RESOURCE_NOT_FOUND");
});