72 lines
3.9 KiB
JavaScript
72 lines
3.9 KiB
JavaScript
import { db, adminUserId, makeUser, load } from './setup.mjs';
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
const {
|
|
createChangeRequest, findApprovedRequest, findPendingRequest, decideChangeRequest, markRequestExecuted, listChangeRequests
|
|
} = await load('../models/changeRequestModel.js');
|
|
const { intuneReportingOverview, recordGraphAudit } = await load('../models/graphModel.js');
|
|
const { createIntuneDeployment } = await load('../models/psadtModel.js');
|
|
|
|
const owner = adminUserId();
|
|
const other = makeUser({ email: 'gov-other@test.local' });
|
|
|
|
const deployment = createIntuneDeployment({ name: 'Gov Test App', visibility: 'shared', status: 'ready' }, owner);
|
|
|
|
test('approval lifecycle: pending -> approved -> executed', () => {
|
|
const request = createChangeRequest({ deploymentId: deployment.id, connectionId: null, action: 'publish', requestedBy: owner, requesterEmail: 'a@b.c' });
|
|
assert.equal(request.status, 'pending');
|
|
assert.equal(findPendingRequest(deployment.id, 'publish').id, request.id);
|
|
assert.equal(findApprovedRequest(deployment.id, 'publish'), null);
|
|
|
|
const approved = decideChangeRequest(request.id, { status: 'approved', approverUserId: owner, approverEmail: 'admin@b.c' });
|
|
assert.equal(approved.status, 'approved');
|
|
assert.equal(findApprovedRequest(deployment.id, 'publish').id, request.id);
|
|
|
|
markRequestExecuted(request.id);
|
|
assert.equal(findApprovedRequest(deployment.id, 'publish'), null, 'executed request is no longer approved');
|
|
});
|
|
|
|
test('deciding an already-decided request returns null', () => {
|
|
const request = createChangeRequest({ deploymentId: deployment.id, action: 'assign', requestedBy: owner });
|
|
decideChangeRequest(request.id, { status: 'rejected', approverUserId: owner });
|
|
assert.equal(decideChangeRequest(request.id, { status: 'approved', approverUserId: owner }), null);
|
|
});
|
|
|
|
test('change requests are scoped to visible deployments for non-admins', () => {
|
|
createChangeRequest({ deploymentId: deployment.id, action: 'relationships', requestedBy: owner });
|
|
// deployment is shared, so a non-admin still sees its requests
|
|
assert.ok(listChangeRequests(other, { isAdmin: false }).some((r) => r.deploymentId === deployment.id));
|
|
});
|
|
|
|
test('reporting overview aggregates deployment status and install states', () => {
|
|
const ts = new Date().toISOString();
|
|
const insert = db.prepare(`
|
|
INSERT INTO intune_deployment_statuses (id, deployment_id, graph_app_id, scope, install_state, error_code, created_at, updated_at)
|
|
VALUES (?, ?, 'app-1', 'device', ?, ?, ?, ?)
|
|
`);
|
|
insert.run('ids_1', deployment.id, 'failed', '0x87D1041C', ts, ts);
|
|
insert.run('ids_2', deployment.id, 'installed', '', ts, ts);
|
|
insert.run('ids_3', deployment.id, 'failed', '0x87D1041C', ts, ts);
|
|
|
|
const overview = intuneReportingOverview(owner, false);
|
|
const failed = overview.installStateCounts.find((row) => row.state === 'failed');
|
|
assert.equal(failed.count, 2);
|
|
assert.ok(overview.deploymentsByStatus.some((row) => row.status === 'ready'));
|
|
assert.equal(overview.topErrors[0].code, '0x87D1041C');
|
|
assert.equal(overview.topErrors[0].count, 2);
|
|
});
|
|
|
|
test('reporting overview includes soak timers and a write-action trend', () => {
|
|
const soaking = createIntuneDeployment({ name: 'Soak Report', visibility: 'shared', autoPromoteAfterHours: 24, autoPromoteRing: 'Broad' }, owner);
|
|
db.prepare('UPDATE intune_deployments SET soak_started_at = ? WHERE id = ?')
|
|
.run(new Date(Date.now() - 3_600_000).toISOString(), soaking.id);
|
|
recordGraphAudit({ deploymentId: soaking.id, actorUserId: owner, action: 'win32app.publish', status: 'success', message: 'ok' });
|
|
|
|
const overview = intuneReportingOverview(owner, false);
|
|
const timer = overview.soakTimers.find((row) => row.id === soaking.id);
|
|
assert.ok(timer, 'soak timer present');
|
|
assert.ok(timer.hoursRemaining > 22 && timer.hoursRemaining <= 24, `~23h remaining, got ${timer.hoursRemaining}`);
|
|
assert.ok(overview.auditTrend.some((row) => row.status === 'success' && row.count >= 1));
|
|
});
|