import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { CompanyResponseSchema, DashboardAttentionSchema, DashboardCompaniesSchema, DashboardEntriesSchema, DashboardSummarySchema, InterventionListSchema, RemoteConnectionResponseSchema, TicketListSchema, } from "@metis/companies/common"; import { CatalogResponseSchema, ResourceResponseSchema } from "@metis/resources/common"; type Request = (path: string, body?: unknown) => Promise; export async function verifyDashboard(request: Request, companyId: string, otherId: string) { async function get(path: string) { const response = await request(path); assert.equal(response.status, 200, await response.clone().text()); return response.json(); } const summary = DashboardSummarySchema.parse(await get("/dashboard/summary")); const companies = DashboardCompaniesSchema.parse(await get("/dashboard/companies")); assert.equal( summary.assets, companies.items.reduce((sum, item) => sum + item.assets, 0) ); assert.equal( summary.resources, companies.items.reduce((sum, item) => sum + item.resources, 0) ); const tickets = TicketListSchema.parse(await get("/tickets")); const interventions = InterventionListSchema.parse(await get("/interventions")); assert.equal( summary.tickets, tickets.tickets.filter((item) => !["resolved", "cancelled"].includes(item.values.state)).length ); assert.equal( summary.interventions, interventions.interventions.filter( (item) => !["finished", "cancelled"].includes(item.values.state) ).length ); for (const scope of [companyId, otherId]) { const scoped = DashboardSummarySchema.parse(await get(`/dashboard/summary?companyId=${scope}`)); const row = companies.items.find((item) => item.id === scope); assert.ok(row); assert.equal(scoped.assets, row.assets); assert.equal(scoped.resources, row.resources); for (const block of [ "work", "activity", "details?kind=asset", "details?kind=resource", "details?issue=certificate", "details?issue=license", "details?issue=profile", "details?issue=missing_site", "details?issue=inactive_person", ]) { const result = DashboardEntriesSchema.parse( await get(`/dashboard/${block}${block.includes("?") ? "&" : "?"}companyId=${scope}`) ); assert.ok(result.items.every((item) => item.companyId === scope)); assert.ok(result.items.length <= 10); } } const work = DashboardEntriesSchema.parse(await get("/dashboard/work")); assert.equal(work.total, summary.tickets + summary.interventions); assert.ok( work.items.every((item) => !["resolved", "cancelled", "finished"].includes(item.state ?? "")) ); const priorityRank = (value: string | null) => ["urgent", "high", "normal", "low"].indexOf(value ?? "normal"); for (let index = 1; index < work.items.length; index++) { const previous = work.items[index - 1]; const current = work.items[index]; assert.ok(previous && current); assert.ok(priorityRank(previous.priority) <= priorityRank(current.priority)); if (priorityRank(previous.priority) === priorityRank(current.priority)) { assert.ok((previous.recordedAt ?? "") <= (current.recordedAt ?? "")); } } const activity = DashboardEntriesSchema.parse(await get("/dashboard/activity")); assert.equal(new Set(activity.items.map((item) => item.eventId)).size, activity.items.length); assert.ok(activity.items.every((item) => item.eventId)); assert.equal(activity.total, activity.items.length); const attention = DashboardAttentionSchema.parse(await get("/dashboard/attention")); assert.equal(attention.items.length, 5); for (const alert of attention.items) { const details = DashboardEntriesSchema.parse( await get(`/dashboard/details?issue=${alert.kind}`) ); assert.equal(alert.count, details.total); } const beyond = DashboardEntriesSchema.parse( await get("/dashboard/details?kind=asset&page=100000") ); assert.equal(beyond.items.length, 0); assert.equal(beyond.total, summary.assets); for (const path of [ "/dashboard/summary?companyId=bad", "/dashboard/details?page=0", "/dashboard/details?issue=unknown", "/dashboard/summary?unknown=true", ]) { assert.equal((await request(path)).status, 400); } assert.equal((await request(`/dashboard/summary?companyId=${randomUUID()}`)).status, 404); } export async function verifyDashboardBoundaries(request: Request) { const created = await request("/companies", { name: "Dashboard vacĂ­o", active: true }); assert.equal(created.status, 201, await created.clone().text()); const companyId = CompanyResponseSchema.parse(await created.json()).company.id; const empty = await request(`/dashboard/summary?companyId=${companyId}`); const zero = DashboardSummarySchema.parse(await empty.json()); assert.deepEqual(zero, { companies: 1, tickets: 0, interventions: 0, assets: 0, resources: 0 }); const catalogResponse = await request("/resource-catalog/global"); const catalog = CatalogResponseSchema.parse(await catalogResponse.json()); const expiry = catalog.items.find((item) => item.builtinKey === "field:expires"); assert.ok(expiry); const date = (offset: number) => new Date(Date.now() + offset * 86400000).toISOString().slice(0, 10); for (const [issue, key] of [ ["certificate", "certificate"], ["license", "software_license"], ]) { const template = catalog.items.find((item) => item.builtinKey === `template:${key}`); assert.ok(template); const expected: string[] = []; for (const [offset, active] of [ [0, true], [30, true], [31, true], [-1, true], [5, false], [null, true], ] as const) { const response = await request(`/resources/${companyId}`, { values: { name: `Dashboard ${key} ${offset} ${active}`, templateId: template.id, active, notes: "", characteristics: offset === null ? {} : { [expiry.id]: date(offset) }, }, }); assert.equal(response.status, 201, await response.clone().text()); const resource = ResourceResponseSchema.parse(await response.json()).resource; if (active && (offset === 0 || offset === 30)) { expected.push(resource.id); } } const response = await request(`/dashboard/details?companyId=${companyId}&issue=${issue}`); assert.equal(response.status, 200, await response.clone().text()); const result = DashboardEntriesSchema.parse(await response.json()); assert.equal(result.total, 2); assert.deepEqual(new Set(result.items.map((item) => item.id)), new Set(expected)); } const first = DashboardEntriesSchema.parse( await (await request(`/dashboard/details?companyId=${companyId}&kind=resource`)).json() ); const second = DashboardEntriesSchema.parse( await (await request(`/dashboard/details?companyId=${companyId}&kind=resource&page=2`)).json() ); assert.equal(first.total, 12); assert.equal(first.items.length, 10); assert.equal(second.items.length, 2); assert.equal(second.total, 12); assert.equal(new Set([...first.items, ...second.items].map((item) => item.id)).size, 12); } export async function verifyDashboardInactiveAccess(request: Request, companyId: string) { const personValues = { name: "Dashboard acceso personal", position: null, email: null, phone: null, active: true, isContact: false, }; const created = await request(`/companies/${companyId}/people`, personValues); assert.equal(created.status, 201, await created.clone().text()); const company = CompanyResponseSchema.parse(await created.json()).company; const person = company.people.find((item) => item.name === personValues.name); assert.ok(person); const values = { personId: person.id, sourceKind: "personal_computer", sourceAssetId: null, targetKind: "network", targetId: null, siteId: null, method: "VPN", requiresVpn: false, state: "configured", notes: null, }; const response = await request(`/remote-connections/${companyId}`, { values }); assert.equal(response.status, 201, await response.clone().text()); const connection = RemoteConnectionResponseSchema.parse(await response.json()).connection; const deactivated = await request(`/companies/${companyId}/people/${person.id}/update`, { ...personValues, active: false, }); assert.equal(deactivated.status, 200, await deactivated.clone().text()); async function count() { const response = await request( `/dashboard/details?companyId=${companyId}&issue=inactive_person` ); assert.equal(response.status, 200, await response.clone().text()); return DashboardEntriesSchema.parse(await response.json()); } assert.ok((await count()).items.some((item) => item.id === person.id)); for (const state of ["removal_pending", "removed"]) { const updated = await request(`/remote-connections/${companyId}/${connection.id}/update`, { values: { ...values, state }, }); assert.equal(updated.status, 200, await updated.clone().text()); assert.equal( (await count()).items.some((item) => item.id === person.id), state !== "removed" ); } }