import { adminUserId, makeUser, load } from './setup.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; const { compareVersions, evaluateUpdateState, mapGraphAppToCatalog } = await load('../services/appVersionService.js'); const { createApplication, listApplications, loadApplication, updateApplication, deleteApplication, recordVersionCheck } = await load('../models/applicationModel.js'); const { runCatalogChecks } = await load('../services/catalogWorker.js'); const owner = adminUserId(); const other = makeUser({ email: 'app-other@test.local' }); // --- pure version logic --------------------------------------------------- test('compareVersions orders numeric segments correctly', () => { assert.equal(compareVersions('1.2.0', '1.10.0'), -1); assert.equal(compareVersions('2.0', '1.9.9'), 1); assert.equal(compareVersions('1.0.0', '1.0.0'), 0); assert.equal(compareVersions('v3.1', '3.1.0'), 0); }); test('evaluateUpdateState flags newer upstream versions', () => { assert.equal(evaluateUpdateState({ currentVersion: '1.0.0', latestKnownVersion: '1.1.0' }).updateAvailable, true); assert.equal(evaluateUpdateState({ currentVersion: '2.0.0', latestKnownVersion: '1.1.0' }).updateAvailable, false); assert.equal(evaluateUpdateState({ currentVersion: '', latestKnownVersion: '1.0.0' }).updateAvailable, true); assert.equal(evaluateUpdateState({ currentVersion: '1.0.0', latestKnownVersion: '' }).updateAvailable, false); }); test('mapGraphAppToCatalog maps the key fields', () => { const mapped = mapGraphAppToCatalog({ id: 'app-1', displayName: 'Contoso VPN', publisher: 'Contoso', displayVersion: '3.1.0' }); assert.equal(mapped.name, 'Contoso VPN'); assert.equal(mapped.vendor, 'Contoso'); assert.equal(mapped.currentVersion, '3.1.0'); assert.equal(mapped.currentGraphAppId, 'app-1'); }); // --- catalog model -------------------------------------------------------- test('applications are visibility-scoped', () => { const app = createApplication({ name: 'Private App', currentVersion: '1.0.0', visibility: 'personal' }, owner); assert.ok(listApplications(owner).some((a) => a.id === app.id)); assert.ok(!listApplications(other).some((a) => a.id === app.id)); assert.equal(loadApplication(app.id, other), null); }); test('application read surfaces a computed updateState', () => { const app = createApplication({ name: 'Edge', currentVersion: '1.0.0', latestKnownVersion: '1.2.0', visibility: 'shared' }, owner); const loaded = loadApplication(app.id, owner); assert.equal(loaded.updateState.updateAvailable, true); assert.equal(loaded.updateState.latest, '1.2.0'); }); test('recordVersionCheck updates the latest known version', () => { const app = createApplication({ name: 'Firefox', currentVersion: '120.0', visibility: 'shared' }, owner); const updated = recordVersionCheck(app.id, owner, { latestKnownVersion: '121.0', message: '' }); assert.equal(updated.latestKnownVersion, '121.0'); assert.equal(updated.updateState.updateAvailable, true); assert.ok(updated.lastCheckedAt); }); test('auto-check worker only processes opted-in apps and records new versions', async () => { const optedIn = createApplication({ name: 'Worker Opt-In', currentVersion: '1.0.0', latestKnownVersion: '1.0.0', versionSource: 'url', versionSourceRef: 'https://example/v', autoCheck: true, visibility: 'shared' }, owner); const optedOut = createApplication({ name: 'Worker Opt-Out', currentVersion: '1.0.0', latestKnownVersion: '1.0.0', autoCheck: false, visibility: 'shared' }, owner); // Injected fetcher avoids network; returns a newer version. const summary = await runCatalogChecks({ fetcher: async () => ({ version: '2.0.0', message: '' }) }); assert.ok(summary.checked >= 1); assert.ok(summary.updated >= 1); assert.equal(loadApplication(optedIn.id, owner).latestKnownVersion, '2.0.0'); assert.equal(loadApplication(optedOut.id, owner).latestKnownVersion, '1.0.0'); }); test('a non-owner cannot update or delete a personal application', () => { const app = createApplication({ name: 'Locked App', visibility: 'personal' }, owner); assert.equal(updateApplication(app.id, { name: 'Hijacked' }, other), null); deleteApplication(app.id, other); assert.ok(loadApplication(app.id, owner), 'should survive foreign delete'); });