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((resolve, reject) => listener.close((error) => (error ? reject(error) : resolve())) ); } } finally { if (prepared) { await cleanupMembershipFixture(database, [companyId, foreignCompany], actorId); } await database.close(); } });