import test from 'node:test'; import assert from 'node:assert/strict'; import { buildNtfyPublishRequest, buildNtfyPublishUrl, createNtfyNotificationClient, } from '../src/core/notification-client.mjs'; import { loadConfig } from '../src/lib/config.mjs'; test('ntfy publish request uses repo configured base url and topic', () => { const request = buildNtfyPublishRequest({ baseUrl: 'http://ntfy.utility.svc.cluster.local/', topic: 'unrip alerts', title: 'Quote lifecycle', message: 'quote abc submitted, awaiting settlement', priority: 'high', tags: ['warning', 'unrip'], click: 'https://dashboard.example/strategy', token: 'secret-token', }); assert.equal(request.ok, true); assert.equal(request.url, 'http://ntfy.utility.svc.cluster.local/unrip%20alerts'); assert.equal(request.init.method, 'POST'); assert.equal(request.init.body, 'quote abc submitted, awaiting settlement'); assert.equal(request.init.headers.Title, 'Quote lifecycle'); assert.equal(request.init.headers.Priority, 'high'); assert.equal(request.init.headers.Tags, 'warning,unrip'); assert.equal(request.init.headers.Click, 'https://dashboard.example/strategy'); assert.equal(request.init.headers.Authorization, 'Bearer secret-token'); }); test('ntfy notification client skips when endpoint or message is not configured', async () => { assert.equal(buildNtfyPublishUrl({ baseUrl: '', topic: 'unrip' }), null); assert.deepEqual( buildNtfyPublishRequest({ baseUrl: 'http://ntfy.utility.svc.cluster.local', topic: 'unrip', message: '' }), { ok: false, skipped: true, reason: 'message_empty' }, ); const client = createNtfyNotificationClient({ baseUrl: '', topic: 'unrip' }); assert.equal(client.isConfigured(), false); assert.deepEqual(await client.publish({ message: 'hello' }), { ok: false, skipped: true, reason: 'ntfy_not_configured', }); }); test('ntfy notification client posts text and returns response status', async () => { const requests = []; const client = createNtfyNotificationClient({ baseUrl: 'http://ntfy.utility.svc.cluster.local', topic: 'unrip', timeoutMs: 1234, fetchImpl: async (url, init) => { requests.push({ url, init }); return { ok: true, status: 200, async text() { return '{"id":"msg-1"}'; }, }; }, }); const result = await client.publish({ message: 'request completed', title: 'unrip' }); assert.equal(result.ok, true); assert.equal(result.status, 200); assert.equal(requests[0].url, 'http://ntfy.utility.svc.cluster.local/unrip'); assert.equal(requests[0].init.body, 'request completed'); assert.equal(requests[0].init.headers.Title, 'unrip'); assert.ok(requests[0].init.signal); }); test('config exposes ntfy notification defaults and environment overrides', () => { const previous = { baseUrl: process.env.NOTIFICATION_NTFY_BASE_URL, topic: process.env.NOTIFICATION_NTFY_TOPIC, timeout: process.env.NOTIFICATION_NTFY_TIMEOUT_MS, token: process.env.NOTIFICATION_NTFY_TOKEN, }; process.env.NOTIFICATION_NTFY_BASE_URL = 'http://ntfy.utility.svc.cluster.local'; process.env.NOTIFICATION_NTFY_TOPIC = 'unrip'; process.env.NOTIFICATION_NTFY_TIMEOUT_MS = '4321'; process.env.NOTIFICATION_NTFY_TOKEN = 'secret-token'; try { const config = loadConfig({ envPath: '/tmp/unrip-missing-env-for-notification-test' }); assert.equal(config.notificationNtfyBaseUrl, 'http://ntfy.utility.svc.cluster.local'); assert.equal(config.notificationNtfyTopic, 'unrip'); assert.equal(config.notificationNtfyTimeoutMs, 4321); assert.equal(config.notificationNtfyToken, 'secret-token'); } finally { restoreEnv('NOTIFICATION_NTFY_BASE_URL', previous.baseUrl); restoreEnv('NOTIFICATION_NTFY_TOPIC', previous.topic); restoreEnv('NOTIFICATION_NTFY_TIMEOUT_MS', previous.timeout); restoreEnv('NOTIFICATION_NTFY_TOKEN', previous.token); } }); function restoreEnv(key, value) { if (value == null) delete process.env[key]; else process.env[key] = value; }