239 lines
7.8 KiB
TypeScript
239 lines
7.8 KiB
TypeScript
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()))
|
|
);
|
|
}
|
|
});
|