'use strict'; const assert = require('node:assert/strict'); const test = require('node:test'); const { createNotificationService } = require('../../src/services/notification-service'); test('generic notifications are HTTPS-only, redact bearer state and send bounded JSON', async () => { const values = new Map(); const secretsMap = new Map(); let request; const settings = { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) }; const secrets = { has: key => secretsMap.has(key), get: key => secretsMap.get(key) || '', set: (key, value) => value ? secretsMap.set(key, value) : secretsMap.delete(key) }; const service = createNotificationService({ settings, secrets, fetchImpl: async (url, options) => { request = { url, options }; return { ok: true }; } }); assert.throws(() => service.save({ enabled: true, endpoint: 'http://notify.local' }), /HTTPS/); const status = service.save({ enabled: true, endpoint: 'https://notify.example.test/chimera', bearerToken: 'secret-token' }); assert.equal(status.bearerConfigured, true); assert.equal(Object.prototype.hasOwnProperty.call(status, 'bearerToken'), false); await service.test(); assert.equal(request.options.headers.Authorization, 'Bearer secret-token'); assert.equal(JSON.parse(request.options.body).schema, 1); }); test('invalid notification changes are atomic and reject loopback variants', () => { const values = new Map(); const service = createNotificationService({ settings: { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) }, secrets: { has: () => false, get: () => '', set: () => {} }, }); assert.throws(() => service.save({ enabled: true }), /Configureer/); assert.equal(values.has('notifications.generic'), false); assert.throws(() => service.save({ endpoint: 'https://[::1]/hook' }), /loopbackhost/); }); test('event notification rules are opt-in, deduplicated and previewable', async () => { const values = new Map(); const secretsMap = new Map(); let calls = 0; const settings = { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) }; const secrets = { has: key => secretsMap.has(key), get: key => secretsMap.get(key) || '', set: (key, value) => value ? secretsMap.set(key, value) : secretsMap.delete(key) }; const service = createNotificationService({ settings, secrets, fetchImpl: async () => { calls += 1; return { ok: true }; }, }); service.save({ enabled: true, endpoint: 'https://notify.example.test/chimera', rules: ['game-update-available'], cooldownMinutes: 60, }); assert.equal((await service.emit('task-failure', { title: 'ignored' })).reason, 'rule-disabled'); assert.equal((await service.emit('game-update-available', { title: 'Update found', message: 'CUSA00001' })).sent, true); assert.equal((await service.emit('game-update-available', { title: 'Update found', message: 'CUSA00001' })).reason, 'cooldown'); assert.equal(calls, 1); assert.equal(service.preview('game-update-available').dryRun, true); assert.throws(() => service.preview('not-a-rule'), /Onbekende/); });