metis-desk/modules/resources/src/api/application/resource-scope.test.ts

239 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-10-24 23:19:17 +00:00
import assert from "node:assert/strict";
import { once } from "node:events";
import { createServer } from "node:http";
import { test } from "node:test";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import express from "express";
import { InventoryResponseSchema, ResourceResponseSchema } from "../../common/inventory";
import { Resource } from "../domain/resource.aggregate";
import { ResourceCatalogError } from "../domain/resource-definition";
import { scopeFixture } from "../domain/resource-scope.fixture";
import { ResourceValues } from "../domain/value-objects/resource-values.vo";
import { registerResourceRoutes } from "../infrastructure/http/resource.routes";
import type { IResourceRepository, InventoryContext } from "./resource.repository";
import { ResourceUseCases } from "./resource.use-cases";
import { ResourceInventoryService } from "./resource-inventory.service";
function inventoryFixture() {
const fixture = scopeFixture();
let resources = [fixture.local, fixture.global, fixture.foreign];
const context = (): InventoryContext => ({
catalog: fixture.catalog,
resources,
references: [],
actorNames: new Map([[fixture.actor.toPrimitive(), "Técnico de pruebas"]]),
});
const repository: IResourceRepository = {
get: async () => Result.ok(context()),
save: async (_company, action) => {
const changed = action(context());
if (!changed.isSuccess) {
return changed;
}
resources = [...resources.filter((item) => !item.id.equals(changed.data.id)), changed.data];
return Result.ok({ resource: changed.data, actorNames: context().actorNames });
},
remove: async () => Result.fail(new ResourceCatalogError("RESOURCE_NOT_FOUND")),
};
return {
...fixture,
repository,
useCases: new ResourceUseCases(repository),
setResources: (next: Resource[]) => {
resources = next;
},
};
}
test("lectores locales: excluyen global y otra compañía incluso si el repositorio los entrega; stockKey no depende del nombre", async () => {
const { repository, useCases, company, local, global, foreign, actor } = inventoryFixture();
const inventory = await new ResourceInventoryService(repository).list(company);
assert.deepEqual(
inventory.map((item) => item.id.toPrimitive()),
[local.id.toPrimitive()]
);
assert.equal(inventory[0]?.usage, "application");
const read = await useCases.get(company.toPrimitive());
assert.ok(read.isSuccess);
assert.deepEqual(read.data.resources, [local]);
for (const resource of [global, foreign]) {
for (const operation of [
useCases.setActive(
company.toPrimitive(),
resource.id.toPrimitive(),
false,
actor.toPrimitive()
),
useCases.save(
company.toPrimitive(),
resource.id.toPrimitive(),
resource.values.toPrimitive(),
actor.toPrimitive()
),
]) {
const result = await operation;
assert.ok(!result.isSuccess);
assert.equal(result.error.code, "RESOURCE_NOT_FOUND");
}
}
});
test("HTTP actual sin MariaDB: aislamiento con 15 compañías, 50 recursos locales, alta, edición, estado e historial", async () => {
const { local, global, foreign, actor, company, useCases, setResources } = inventoryFixture();
const companies = [
company,
foreign.requireCompanyId(),
...Array.from({ length: 13 }, () => UniqueID.generateNewID()),
];
const resources = [local, foreign];
for (const owner of [company, ...companies.slice(2)]) {
const count = owner.equals(company) ? 49 : 1;
for (let index = 0; index < count; index += 1) {
const values = ResourceValues.create(
{
...local.values.toPrimitive(),
name: `Aplicación de pruebas con tildes ${index + 1}`,
active: index % 2 === 0,
},
local.specification
);
assert.ok(values.isSuccess);
const created = Resource.create(
owner,
local.specification,
values.data,
actor,
"2026-10-24T10:00:00.000Z"
);
assert.ok(created.isSuccess);
resources.push(created.data);
}
}
setResources([...resources, global]);
const app = express();
app.use(express.json());
registerResourceRoutes(app, useCases, (request, _response, next) => {
Object.assign(request, { session: { userId: actor.toPrimitive() } });
next();
});
const server = createServer(app);
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
assert.ok(address && typeof address !== "string");
const base = `http://127.0.0.1:${address.port}/api/resources`;
const request = (path: string, body?: unknown) =>
fetch(`${base}/${path}`, {
method: body === undefined ? "GET" : "POST",
...(body === undefined
? {}
: { headers: { "content-type": "application/json" }, body: JSON.stringify(body) }),
signal: AbortSignal.timeout(5000),
});
try {
const response = await request(company.toPrimitive());
const listed = InventoryResponseSchema.parse(await response.json());
assert.equal(response.status, 200);
assert.equal(listed.resources.length, 50);
assert.ok(
listed.resources.every(
(item) => item.companyId === company.toPrimitive() && !Object.hasOwn(item, "scope")
)
);
for (const owner of companies.slice(1)) {
const response = await request(owner.toPrimitive());
const listed = InventoryResponseSchema.parse(await response.json());
assert.equal(listed.resources.length, 1);
assert.equal(listed.resources[0]?.companyId, owner.toPrimitive());
}
for (const resource of [global, foreign]) {
assert.equal(
(
await request(`${company.toPrimitive()}/${resource.id.toPrimitive()}/status`, {
active: false,
})
).status,
404
);
assert.equal(
(
await request(`${company.toPrimitive()}/${resource.id.toPrimitive()}/update`, {
values: resource.values.toPrimitive(),
})
).status,
404
);
}
for (const extra of [
{ scope: "global", companyId: null },
{ scope: "company", companyId: company.toPrimitive() },
]) {
assert.equal(
(await request(company.toPrimitive(), { values: local.values.toPrimitive(), ...extra }))
.status,
400
);
assert.equal(
(
await request(company.toPrimitive(), {
values: { ...local.values.toPrimitive(), ...extra },
})
).status,
400
);
}
const create = await request(company.toPrimitive(), {
values: { ...local.values.toPrimitive(), name: "Aplicación nueva" },
});
const created = ResourceResponseSchema.parse(await create.json()).resource;
assert.equal(create.status, 201);
assert.equal(created.companyId, company.toPrimitive());
assert.equal(created.history.length, 1);
const edit = await request(`${company.toPrimitive()}/${created.id}/update`, {
values: { ...created.values, notes: "Edición local" },
});
const edited = ResourceResponseSchema.parse(await edit.json()).resource;
assert.equal(edit.status, 200);
assert.equal(edited.id, created.id);
assert.equal(edited.history.length, 2);
for (const active of [false, false, true]) {
const status = await request(`${company.toPrimitive()}/${created.id}/status`, { active });
const changed = ResourceResponseSchema.parse(await status.json()).resource;
assert.equal(status.status, 200);
assert.equal(changed.values.active, active);
assert.equal(changed.history.length, active ? 4 : 3);
assert.deepEqual(changed.history[0], created.history[0]);
}
} finally {
await new Promise<void>((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve()))
);
}
});