144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import { Sequelize, Transaction } from "sequelize";
|
|
|
|
import { buildResourceResponse } from "../../application/resource-response.builder";
|
|
import { scopeFixture } from "../../domain/resource-scope.fixture";
|
|
import { ResourceValues } from "../../domain/value-objects/resource-values.vo";
|
|
|
|
import { projectDirectoryDomain } from "./directory-domain-projection";
|
|
import { projectNetworkShare } from "./network-share-projection";
|
|
import { projectResource } from "./resource-compatibility";
|
|
import { ResourcePersistenceMapper } from "./resource-persistence.mapper";
|
|
|
|
test("lector legacy: roundtrip conserva UUID, revisiones, valores, actor, fecha y DTO sin columna scope", () => {
|
|
const { local, actor } = scopeFixture();
|
|
const values = ResourceValues.create(
|
|
{ ...local.values.toPrimitive(), active: false, notes: "Cambio auditado" },
|
|
local.specification
|
|
);
|
|
|
|
assert.ok(values.isSuccess);
|
|
const changed = local.update(values.data, actor, "2026-10-24T12:00:00.000Z");
|
|
|
|
assert.ok(changed.isSuccess);
|
|
const mapper = new ResourcePersistenceMapper();
|
|
const stored = mapper.toPersistence(changed.data);
|
|
const read = mapper.toDomain(stored.resource, stored.revisions);
|
|
|
|
assert.equal(read.scope, "company");
|
|
assert.deepEqual(mapper.toPersistence(read), stored);
|
|
assert.equal(Object.hasOwn(stored.resource, "scope"), false);
|
|
assert.deepEqual(
|
|
buildResourceResponse(read, new Map()),
|
|
buildResourceResponse(changed.data, new Map())
|
|
);
|
|
assert.deepEqual(
|
|
mapper.toPersistence(
|
|
mapper.toScopedDomain({ ...stored.resource, scope: "company" }, stored.revisions)
|
|
),
|
|
stored
|
|
);
|
|
});
|
|
|
|
test("lector: solo el adaptador legacy conocido interpreta ausencia; ningún ámbito inválido usa fallback", () => {
|
|
const { local } = scopeFixture();
|
|
const mapper = new ResourcePersistenceMapper();
|
|
const stored = mapper.toPersistence(local);
|
|
|
|
for (const patch of [
|
|
{ scope: "global", companyId: stored.resource.companyId },
|
|
{ scope: "company", companyId: null },
|
|
{ scope: "other" },
|
|
{ scope: undefined },
|
|
{ companyId: null },
|
|
{ companyId: undefined },
|
|
{ companyId: "invalid" },
|
|
]) {
|
|
assert.throws(
|
|
() =>
|
|
Reflect.apply(mapper.toDomain, mapper, [
|
|
{ ...stored.resource, ...patch },
|
|
stored.revisions,
|
|
]),
|
|
/INVALID_RESOURCE_SCOPE/
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
Reflect.apply(mapper.toScopedDomain, mapper, [
|
|
{ ...stored.resource, ...patch },
|
|
stored.revisions,
|
|
]),
|
|
/INVALID_RESOURCE_SCOPE/
|
|
);
|
|
}
|
|
assert.throws(
|
|
() => Reflect.apply(mapper.toScopedDomain, mapper, [stored.resource, stored.revisions]),
|
|
/INVALID_RESOURCE_SCOPE/
|
|
);
|
|
const global = mapper.toScopedDomain(
|
|
{ ...stored.resource, scope: "global", companyId: null },
|
|
stored.revisions
|
|
);
|
|
|
|
assert.equal(global.companyId, null);
|
|
assert.equal(global.scope, "global");
|
|
assert.deepEqual(global.history, local.history);
|
|
assert.throws(() => mapper.toPersistence(global), /RESOURCE_SCOPE_UNSUPPORTED/);
|
|
});
|
|
|
|
test("proyecciones operativas: global sintético se rechaza antes de SQL, el recurso local sin integración no crea proyección", async () => {
|
|
const { local, global, catalog } = scopeFixture();
|
|
const database = new Sequelize({
|
|
dialect: "mariadb",
|
|
database: "metis_desk_test",
|
|
logging: false,
|
|
});
|
|
const transaction = new Transaction(database, {});
|
|
|
|
// Una conexión cerrada hace fallar cualquier acceso accidental a persistencia.
|
|
await database.close();
|
|
await assert.rejects(
|
|
() => projectResource(database, global, transaction),
|
|
/RESOURCE_SCOPE_UNSUPPORTED/
|
|
);
|
|
await assert.rejects(
|
|
() => projectDirectoryDomain(database, global, catalog, undefined, undefined, transaction),
|
|
/RESOURCE_SCOPE_UNSUPPORTED/
|
|
);
|
|
await assert.rejects(
|
|
() => projectNetworkShare(database, global, catalog, undefined, false, transaction),
|
|
/RESOURCE_SCOPE_UNSUPPORTED/
|
|
);
|
|
assert.equal(await projectResource(database, local, transaction), undefined);
|
|
const directory = await projectDirectoryDomain(
|
|
database,
|
|
local,
|
|
catalog,
|
|
undefined,
|
|
undefined,
|
|
transaction
|
|
);
|
|
|
|
assert.ok(directory.isSuccess);
|
|
assert.equal(directory.data, undefined);
|
|
assert.equal(
|
|
await projectNetworkShare(database, local, catalog, undefined, false, transaction),
|
|
undefined
|
|
);
|
|
});
|
|
|
|
test("escritor scoped: envía company explícito, preserva la salida legacy y rechaza global", () => {
|
|
const { local, global } = scopeFixture();
|
|
const mapper = new ResourcePersistenceMapper();
|
|
const legacy = mapper.toPersistence(local);
|
|
const scoped = mapper.toScopedPersistence(local);
|
|
const { scope, ...row } = scoped.resource;
|
|
|
|
assert.equal(scope, "company");
|
|
assert.deepEqual(row, legacy.resource);
|
|
assert.deepEqual(scoped.revisions, legacy.revisions);
|
|
assert.throws(() => mapper.toScopedPersistence(global), /RESOURCE_SCOPE_UNSUPPORTED/);
|
|
});
|