metis-desk/apps/server/src/windows-domains-ui.integration.test.ts
2026-10-20 13:20:22 +02:00

414 lines
14 KiB
TypeScript

import assert from "node:assert/strict";
import { once } from "node:events";
import { test } from "node:test";
import {
DIRECTORY_MEMBERSHIP_TYPE_ID,
DirectoryEligibilitySchema,
DirectoryRelationshipResponseSchema,
} from "@metis/assets/common";
import { DirectoryDomainOptionsResponseSchema } from "@metis/resources/common";
import { UniqueID } from "../../../packages/rdx-ddd/src/index";
import {
cleanupMembershipFixture,
prepareMembershipFixture,
} from "./asset-directory-memberships.fixture";
import { createDatabase, readDatabaseEnvironment } from "./config/database";
test("HTTP UI de dominios: sesión, ámbito, roles, cursores y comandos existentes", async (t) => {
const environment = readDatabaseEnvironment(process.env);
assert.equal(environment.NODE_ENV, "test");
assert.equal(environment.DB_NAME, "metis_desk_test");
const database = createDatabase(environment);
const actorId = UniqueID.generateNewID().toPrimitive();
const companyId = UniqueID.generateNewID().toPrimitive();
const foreignCompany = UniqueID.generateNewID().toPrimitive();
let prepared = false;
try {
const fixture = await prepareMembershipFixture(database, environment.APP_ORIGIN, actorId, [
companyId,
foreignCompany,
]);
prepared = true;
const domain1 = await fixture.domain(companyId, "ui-one.local");
const domain2 = await fixture.domain(companyId, "ui-two.local");
const foreignDomain = await fixture.domain(foreignCompany, "ui-one.local");
const asset1 = await fixture.asset(companyId);
const asset2 = await fixture.asset(companyId);
const laptop = await fixture.asset(companyId, "laptop");
const printer = await fixture.asset(companyId, "printer");
const listener = fixture.app.listen(0, "127.0.0.1");
try {
await once(listener, "listening");
const address = listener.address();
assert.ok(address && typeof address !== "string");
const base = `http://127.0.0.1:${address.port}`;
const path = `/api/asset-directory-memberships/${companyId}`;
const parameters = new URLSearchParams({
typeId: DIRECTORY_MEMBERSHIP_TYPE_ID,
anchorType: "resources:resource",
anchorId: domain1.id.toPrimitive(),
direction: "incoming",
includeEnded: "false",
pageSize: "1",
});
assert.equal((await fetch(`${base}${path}/relationships?${parameters}`)).status, 401);
const session = await fetch(`${base}/fixture-session`);
const cookie = session.headers.get("set-cookie")?.split(";")[0];
assert.ok(cookie);
const headers = {
cookie,
origin: environment.APP_ORIGIN,
"content-type": "application/json",
"x-csrf-token": "a".repeat(64),
};
async function read(params = parameters, prefix = path) {
return fetch(`${base}${prefix}/relationships?${params}`, {
headers: { cookie: cookie ?? "" },
});
}
async function command(assetId: string, body: unknown) {
return fetch(`${base}${path}/${assetId}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
}
await t.test("vacío real, dominios disponibles y compatibilidad por proyección", async () => {
const emptyResponse = await read();
assert.equal(emptyResponse.status, 200);
assert.deepEqual(DirectoryRelationshipResponseSchema.parse(await emptyResponse.json()), {
page: { items: [], nextCursor: null },
summaries: [],
});
const optionResponse = await fetch(`${base}${path}/domains`, { headers: { cookie } });
assert.equal(optionResponse.status, 200, "domain options response");
const options = DirectoryDomainOptionsResponseSchema.parse(await optionResponse.json());
assert.equal(options.items.length, 2);
assert.ok(options.items.every((item) => item.id !== foreignDomain.id.toPrimitive()));
const serverResponse = await fetch(
`${base}${path}/${asset1.id.toPrimitive()}/eligibility`,
{ headers: { cookie } }
);
const printerResponse = await fetch(
`${base}${path}/${printer.id.toPrimitive()}/eligibility`,
{ headers: { cookie } }
);
assert.equal(serverResponse.status, 200, "server eligibility response");
assert.equal(printerResponse.status, 200, "printer eligibility response");
const serverEligibility = DirectoryEligibilitySchema.parse(await serverResponse.json());
const printerEligibility = DirectoryEligibilitySchema.parse(await printerResponse.json());
for (let index = 0; index < 19; index += 1) {
await fixture.domain(companyId, `ui-extra-${index}.local`);
}
const firstOptionsResponse = await fetch(`${base}${path}/domains`, { headers: { cookie } });
const firstOptions = DirectoryDomainOptionsResponseSchema.parse(
await firstOptionsResponse.json()
);
assert.equal(firstOptions.items.length, 20);
assert.ok(firstOptions.nextCursor);
const lastOptions = DirectoryDomainOptionsResponseSchema.parse(
await (
await fetch(`${base}${path}/domains?cursor=${firstOptions.nextCursor}`, {
headers: { cookie },
})
).json()
);
assert.equal(lastOptions.items.length, 1);
assert.equal(lastOptions.nextCursor, null);
assert.equal(
new Set([...firstOptions.items, ...lastOptions.items].map((item) => item.id)).size,
21
);
const matching = DirectoryDomainOptionsResponseSchema.parse(
await (
await fetch(`${base}${path}/domains?search=ui-two.local`, { headers: { cookie } })
).json()
);
assert.equal(matching.items[0]?.id, domain2.id.toPrimitive());
await database.query(
"UPDATE resources SET active=0 WHERE id=:id AND companyId=:companyId",
{ replacements: { id: domain2.id.toPrimitive(), companyId } }
);
const inactiveOptions = DirectoryDomainOptionsResponseSchema.parse(
await (
await fetch(`${base}${path}/domains?search=ui-two.local`, { headers: { cookie } })
).json()
);
assert.equal(inactiveOptions.items.length, 0);
await database.query(
"UPDATE resources SET active=1 WHERE id=:id AND companyId=:companyId",
{ replacements: { id: domain2.id.toPrimitive(), companyId } }
);
assert.equal(serverEligibility.server, true);
assert.equal(
(
await fetch(`${base}${path}/${asset1.id.toPrimitive().toUpperCase()}/eligibility`, {
headers: { cookie },
})
).status,
200
);
assert.equal(printerEligibility.computer || printerEligibility.server, false);
assert.equal(
(await fetch(`${base}${path}/domains?operatorId=${actorId}`, { headers: { cookie } }))
.status,
400
);
});
await t.test(
"asociación, controladores, lote de resúmenes y cursor ligado al dominio",
async () => {
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "domain-controller",
})
).status,
200
);
assert.equal(
(
await command(asset2.id.toPrimitive(), {
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "domain-controller",
})
).status,
200
);
assert.equal(
(
await command(laptop.id.toPrimitive(), {
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "member",
})
).status,
200
);
const first = DirectoryRelationshipResponseSchema.parse(await (await read()).json());
assert.equal(first.page.items.length, 1);
assert.equal(first.summaries[0]?.status, "resolved");
assert.ok(first.page.nextCursor);
const secondParams = new URLSearchParams(parameters);
secondParams.set("cursor", first.page.nextCursor);
const second = DirectoryRelationshipResponseSchema.parse(
await (await read(secondParams)).json()
);
assert.notEqual(first.page.items[0]?.origin.id, second.page.items[0]?.origin.id);
secondParams.set("anchorId", domain2.id.toPrimitive());
assert.equal((await read(secondParams)).status, 400);
const allParams = new URLSearchParams(parameters);
allParams.set("pageSize", "20");
const all = DirectoryRelationshipResponseSchema.parse(
await (await read(allParams)).json()
);
assert.equal(all.page.items.length, 3);
assert.equal(
all.page.items.filter((item) => item.roleCode === "domain-controller").length,
2
);
assert.equal(all.page.items.filter((item) => item.roleCode === "member").length, 1);
assert.ok(all.summaries.every((item) => item.status === "resolved"));
}
);
await t.test(
"directa, cambio de rol, traslado atómico, finalización e historial",
async () => {
const direct = new URLSearchParams(parameters);
direct.set("anchorType", "assets:asset");
direct.set("anchorId", asset1.id.toPrimitive());
direct.set("direction", "outgoing");
const joined = DirectoryRelationshipResponseSchema.parse(
await (await read(direct)).json()
);
const original = joined.page.items[0];
assert.ok(original);
assert.equal(original.roleCode, "domain-controller");
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "role",
membershipId: original.origin.id,
role: "member",
})
).status,
200
);
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "transfer",
membershipId: original.origin.id,
domainResourceId: domain2.id.toPrimitive(),
role: "member",
})
).status,
200
);
const transferred = DirectoryRelationshipResponseSchema.parse(
await (await read(direct)).json()
);
const current = transferred.page.items[0];
assert.ok(current);
assert.equal(current.target.id, domain2.id.toPrimitive());
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "role",
membershipId: original.origin.id,
role: "domain-controller",
})
).status,
409
);
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "end",
membershipId: current.origin.id,
})
).status,
200
);
assert.equal(
(
await command(asset1.id.toPrimitive(), {
action: "end",
membershipId: current.origin.id,
})
).status,
200
);
const empty = DirectoryRelationshipResponseSchema.parse(
await (await read(direct)).json()
);
assert.equal(empty.page.items.length, 0);
direct.set("includeEnded", "true");
direct.set("pageSize", "20");
const history = DirectoryRelationshipResponseSchema.parse(
await (await read(direct)).json()
);
assert.equal(history.page.items.length, 2);
assert.ok(history.page.items.every((item) => item.stateCode === "ended"));
}
);
await t.test(
"límites, tipo desconocido, suplantación, compañía inexistente y referencia ajena",
async () => {
for (const [key, value] of [
["pageSize", "101"],
["typeId", "assets:unknown"],
["cursor", "invalid"],
["operatorId", actorId],
["anchorType", "unknown:entity"],
]) {
const invalid = new URLSearchParams(parameters);
assert.ok(key && value);
invalid.set(key, value);
assert.equal((await read(invalid)).status, 400);
}
const foreign = new URLSearchParams(parameters);
foreign.set("anchorId", foreignDomain.id.toPrimitive());
assert.equal((await read(foreign)).status, 404);
assert.equal(
(
await read(
parameters,
`/api/asset-directory-memberships/${UniqueID.generateNewID().toPrimitive()}`
)
).status,
403
);
assert.equal(
(
await command(printer.id.toPrimitive(), {
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "member",
})
).status,
409
);
assert.equal(
(
await command(laptop.id.toPrimitive(), {
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "domain-controller",
})
).status,
409
);
assert.equal(
(
await fetch(`${base}${path}/${asset1.id.toPrimitive()}`, {
method: "POST",
headers: {
cookie,
origin: environment.APP_ORIGIN,
"content-type": "application/json",
},
body: JSON.stringify({
action: "join",
domainResourceId: domain1.id.toPrimitive(),
role: "member",
}),
})
).status,
403
);
}
);
} finally {
await new Promise<void>((resolve, reject) =>
listener.close((error) => (error ? reject(error) : resolve()))
);
}
} finally {
if (prepared) {
await cleanupMembershipFixture(database, [companyId, foreignCompany], actorId);
}
await database.close();
}
});