2026-10-06 15:15:42 +00:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { test } from "node:test";
|
|
|
|
|
|
|
|
|
|
import { UniqueID } from "@repo/rdx-ddd";
|
|
|
|
|
|
|
|
|
|
import type { ApplicationContext } from "../application/application-configuration.repository";
|
|
|
|
|
import { mapApplicationInput } from "../application/application-configuration-input.mapper";
|
|
|
|
|
import { buildApplicationResponse } from "../application/application-configuration-response.builder";
|
|
|
|
|
import { ApplicationConfigurationPersistenceMapper } from "../infrastructure/persistence/application-configuration-persistence.mapper";
|
|
|
|
|
|
|
|
|
|
import { ApplicationConfiguration } from "./application-configuration.aggregate";
|
2026-10-08 14:43:34 +00:00
|
|
|
import type { ApplicationConfigurationInput } from "./application-configuration-values";
|
|
|
|
|
import { ApplicationConfigurationInputSchema } from "./application-configuration-values";
|
2026-10-09 08:38:19 +00:00
|
|
|
import { ApplicationConfigurationValues } from "./value-objects/application-configuration-values.vo";
|
|
|
|
|
import { WorkProfileValues } from "./value-objects/work-profile-values.vo";
|
2026-10-06 15:15:42 +00:00
|
|
|
import { WorkProfile } from "./work-profile.aggregate";
|
|
|
|
|
|
|
|
|
|
const company = UniqueID.generateNewID();
|
|
|
|
|
const actor = UniqueID.generateNewID();
|
|
|
|
|
const person = UniqueID.generateNewID();
|
|
|
|
|
const host = UniqueID.generateNewID();
|
|
|
|
|
const resource = UniqueID.generateNewID();
|
|
|
|
|
const date = "2026-10-06T12:00:00.000Z";
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
function fixture() {
|
|
|
|
|
const value = WorkProfileValues.create({
|
|
|
|
|
name: "Ana",
|
|
|
|
|
personId: person.toPrimitive(),
|
|
|
|
|
assetId: host.toPrimitive(),
|
|
|
|
|
accountId: null,
|
|
|
|
|
active: true,
|
|
|
|
|
notes: null,
|
|
|
|
|
});
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(value.isSuccess);
|
|
|
|
|
const profile = WorkProfile.create(company, value.data, actor, date);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(profile.isSuccess);
|
|
|
|
|
const input: ApplicationConfigurationInput = {
|
|
|
|
|
profileId: profile.data.id.toPrimitive(),
|
|
|
|
|
resourceId: resource.toPrimitive(),
|
|
|
|
|
delivery: "installed",
|
|
|
|
|
version: null,
|
|
|
|
|
state: "pending",
|
|
|
|
|
notes: null,
|
|
|
|
|
};
|
|
|
|
|
const context: ApplicationContext = {
|
|
|
|
|
profiles: [profile.data],
|
|
|
|
|
configurations: [],
|
|
|
|
|
people: [{ id: person, active: true }],
|
|
|
|
|
assets: [{ id: host, available: true }],
|
|
|
|
|
resources: [{ id: resource, active: true, usage: "application" }],
|
|
|
|
|
actors: new Map([[actor.toPrimitive(), "Técnico"]]),
|
|
|
|
|
};
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
return { input, context };
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
function values(input: ApplicationConfigurationInput) {
|
|
|
|
|
const value = ApplicationConfigurationValues.create(input);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(value.isSuccess);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
return value.data;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
function create(input: ApplicationConfigurationInput) {
|
|
|
|
|
const root = ApplicationConfiguration.create(company, values(input), actor, date);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(root.isSuccess);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
return root.data;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
test("aplicaciones: contrato estricto, versión opcional y solo instalada", () => {
|
|
|
|
|
const { input } = fixture();
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.equal(
|
|
|
|
|
ApplicationConfigurationInputSchema.safeParse({ ...input, actorId: actor.toPrimitive() })
|
|
|
|
|
.success,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
ApplicationConfigurationValues.create({ ...input, delivery: "web", version: "1" }).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(values({ ...input, version: " ", notes: " " }).toPrimitive().version, null);
|
|
|
|
|
assert.equal(values({ ...input, delivery: "web" }).toPrimitive().version, null);
|
|
|
|
|
assert.equal(
|
|
|
|
|
ApplicationConfigurationValues.create({ ...input, version: "a".repeat(101) }).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
ApplicationConfigurationValues.create({ ...input, notes: "a".repeat(4001) }).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
test("aplicaciones: UUID, idempotencia, versión auditada, destino fijo y retirada", () => {
|
|
|
|
|
const { input } = fixture();
|
|
|
|
|
const root = create(input);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.equal(root.id.toPrimitive()[14], "7");
|
|
|
|
|
assert.equal(root.history[0]?.id.toPrimitive()[14], "7");
|
|
|
|
|
const same = root.update(values({ ...input, notes: " " }), actor, date);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(same.isSuccess);
|
|
|
|
|
assert.equal(same.data, root);
|
|
|
|
|
const configured = root.update(
|
|
|
|
|
values({ ...input, state: "configured", version: "128" }),
|
|
|
|
|
actor,
|
|
|
|
|
date
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(configured.isSuccess);
|
|
|
|
|
assert.equal(configured.data.history.length, 2);
|
|
|
|
|
assert.equal(configured.data.history[0]?.values.version.getOrUndefined(), undefined);
|
|
|
|
|
assert.equal(
|
|
|
|
|
configured.data.update(
|
|
|
|
|
values({ ...input, resourceId: UniqueID.generateNewID().toPrimitive() }),
|
|
|
|
|
actor,
|
|
|
|
|
date
|
|
|
|
|
).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
const removal = configured.data.update(
|
|
|
|
|
values({ ...configured.data.values.toPrimitive(), state: "removal_pending" }),
|
|
|
|
|
actor,
|
|
|
|
|
date
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(removal.isSuccess);
|
|
|
|
|
const removed = removal.data.update(
|
|
|
|
|
values({ ...removal.data.values.toPrimitive(), state: "removed" }),
|
|
|
|
|
actor,
|
|
|
|
|
date
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(removed.isSuccess);
|
|
|
|
|
assert.equal(
|
|
|
|
|
removed.data.update(
|
|
|
|
|
values({ ...removed.data.values.toPrimitive(), notes: "Otra" }),
|
|
|
|
|
actor,
|
|
|
|
|
date
|
|
|
|
|
).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
root.update(values({ ...input, state: "configured" }), actor, "2026-10-05T12:00:00.000Z")
|
|
|
|
|
.isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
ApplicationConfiguration.create(company, values({ ...input, state: "removed" }), actor, date)
|
|
|
|
|
.isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
test("aplicaciones: ámbito y clasificación explícita, duplicados abiertos y reapertura nueva", () => {
|
|
|
|
|
const { input, context } = fixture();
|
|
|
|
|
const root = create(input);
|
|
|
|
|
const duplicate = mapApplicationInput(input, { ...context, configurations: [root] });
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(!duplicate.isSuccess);
|
|
|
|
|
assert.equal(duplicate.error.code, "APPLICATION_ALREADY_OPEN");
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput({ ...input, resourceId: host.toPrimitive() }, context).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput(input, {
|
|
|
|
|
...context,
|
|
|
|
|
resources: [{ id: resource, active: true, usage: "certificate" }],
|
|
|
|
|
}).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput(input, {
|
|
|
|
|
...context,
|
|
|
|
|
resources: [{ id: resource, active: true, usage: null }],
|
|
|
|
|
}).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
const closed = root.update(values({ ...input, state: "never_configured" }), actor, date);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(closed.isSuccess);
|
|
|
|
|
assert.ok(mapApplicationInput(input, { ...context, configurations: [closed.data] }).isSuccess);
|
|
|
|
|
});
|
|
|
|
|
test("aplicaciones: inactividad conserva notas y permite retirada, impide reconfigurar", () => {
|
|
|
|
|
const { input, context } = fixture();
|
|
|
|
|
const root = create({ ...input, state: "configured" });
|
|
|
|
|
const inactive = {
|
|
|
|
|
...context,
|
|
|
|
|
configurations: [root],
|
|
|
|
|
resources: [{ id: resource, active: false, usage: "application" as const }],
|
|
|
|
|
};
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.equal(buildApplicationResponse(root, inactive).needsReview, true);
|
|
|
|
|
assert.ok(
|
|
|
|
|
mapApplicationInput({ ...root.values.toPrimitive(), notes: "Retirar" }, inactive, root)
|
|
|
|
|
.isSuccess
|
|
|
|
|
);
|
|
|
|
|
assert.ok(
|
|
|
|
|
mapApplicationInput({ ...root.values.toPrimitive(), state: "removal_pending" }, inactive, root)
|
|
|
|
|
.isSuccess
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput({ ...root.values.toPrimitive(), version: "2" }, inactive, root).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput({ ...root.values.toPrimitive(), delivery: "web" }, inactive, root)
|
|
|
|
|
.isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput(input, { ...context, people: [{ id: person, active: false }] }).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
mapApplicationInput(input, { ...context, assets: [{ id: host, available: false }] }).isSuccess,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
test("aplicaciones: persistencia preserva historial y rechaza claves y orden incoherentes", () => {
|
|
|
|
|
const { input } = fixture();
|
|
|
|
|
const root = create(input);
|
|
|
|
|
const mapper = new ApplicationConfigurationPersistenceMapper();
|
|
|
|
|
const record = mapper.toPersistence(root);
|
|
|
|
|
const rehydrated = mapper.toDomain(record.root, record.revisions);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-06 15:15:42 +00:00
|
|
|
assert.ok(rehydrated.id.equals(root.id));
|
|
|
|
|
assert.ok(rehydrated.history[0]?.id.equals(root.history[0]?.id));
|
|
|
|
|
assert.throws(() => mapper.toDomain({ ...record.root, currentKey: null }, record.revisions));
|
|
|
|
|
assert.throws(() =>
|
|
|
|
|
mapper.toDomain(
|
|
|
|
|
record.root,
|
|
|
|
|
record.revisions.map((r) => ({ ...r, position: 1 }))
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
assert.throws(() =>
|
|
|
|
|
mapper.toDomain(
|
|
|
|
|
record.root,
|
|
|
|
|
record.revisions.map((r) => ({ ...r, configurationId: company.toPrimitive() }))
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
});
|