Files
lyssnarr/backend/test/ntfy.test.js
claude d8a5089eaf
All checks were successful
build-and-push / build (push) Successful in 17s
ntfy-notiser: skicka kort notis när något läggs till för nedladdning
Konfigureras på inställningssidan (server-URL, topic, på/av-toggle,
testknapp) med NTFY_URL/NTFY_TOPIC som env-fallback. Notisen är
fire-and-forget och kan aldrig fälla själva tillägget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 18:33:37 +02:00

184 lines
6.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { buildApp } from '../src/app.js'
import { settingsStore } from '../src/settings.js'
import { ntfySettings, ntfyNotifier } from '../src/ntfy.js'
import { makeTesters } from '../src/testers.js'
function tmpDataDir() {
return mkdtempSync(path.join(tmpdir(), 'lyssnarr-test-'))
}
const baseConfig = {
appPassword: 'testlösen',
sessionSecret: 'x'.repeat(32),
dataDir: '/tmp',
lidarr: { url: 'http://stub', apiKey: 'k', qualityProfileId: 3, metadataProfileId: 1, rootFolder: '/music' },
jellyfin: { url: 'http://jellyfin:8096', apiKey: '' },
gemini: { apiKey: '', model: 'gemini-2.5-flash' },
ntfy: { url: 'https://ntfy.env-fallback', topic: '' },
}
async function loggedIn(app) {
const login = await app.inject({ method: 'POST', url: '/api/login', payload: { password: 'testlösen' } })
return login.headers['set-cookie']
}
test('ntfySettings: sparat har företräde, env är fallback, enabled kräver explicit true', () => {
const def = ntfySettings(baseConfig, {})
assert.equal(def.enabled, false)
assert.equal(def.url, 'https://ntfy.env-fallback')
const s = ntfySettings(baseConfig, { ntfy: { url: 'https://ntfy.ui/', topic: 'media-hamtningar', enabled: true } })
assert.equal(s.enabled, true)
assert.equal(s.url, 'https://ntfy.ui', 'avslutande / ska trimmas')
assert.equal(s.topic, 'media-hamtningar')
const utanConfig = ntfySettings({}, {})
assert.equal(utanConfig.url, '', 'config utan ntfy-sektion får inte krascha')
})
test('publish skickar bara när påslagen och komplett', async () => {
const store = settingsStore(tmpDataDir())
const sent = []
const fetchImpl = async (url, opts) => {
sent.push({ url, body: JSON.parse(opts.body) })
return { ok: true }
}
const notifier = ntfyNotifier({ config: baseConfig, store, fetchImpl })
assert.equal(await notifier.publish('t', 'm'), false, 'avstängd => inget skickas')
assert.equal(sent.length, 0)
store.save({ ntfy: { url: 'https://ntfy.test', topic: 'media-hamtningar', enabled: true } })
assert.equal(await notifier.publish('Tillagt', 'Powerwolf Interludium'), true)
assert.equal(sent.length, 1)
assert.deepEqual(sent[0].body, {
topic: 'media-hamtningar',
title: 'Tillagt',
message: 'Powerwolf Interludium',
tags: ['musical_note'],
})
})
test('POST /api/add notifierar vid nytt tillägg men inte vid dubblett', async () => {
const hit = {
album: {
foreignAlbumId: 'mbid-album-1',
title: 'Interludium',
artist: { artistName: 'Powerwolf', foreignArtistId: 'mbid-artist-1' },
},
}
const lidarr = {
search: async () => [hit],
addAlbum: async () => ({}),
}
const published = []
const ntfy = {
settings: () => ({ enabled: true, url: 'x', topic: 'y' }),
publish: async (title, message) => {
published.push({ title, message })
return true
},
}
const app = buildApp(baseConfig, { lidarr, musicbrainz: {}, store: settingsStore(tmpDataDir()), ntfy })
const cookie = await loggedIn(app)
const res = await app.inject({
method: 'POST',
url: '/api/add',
headers: { cookie },
payload: { kind: 'album', mbid: 'mbid-album-1' },
})
assert.equal(res.statusCode, 200)
assert.deepEqual(res.json(), { ok: true, already: false })
// fire-and-forget: ge event-loopen ett varv
await new Promise((r) => setImmediate(r))
assert.equal(published.length, 1)
assert.equal(published[0].title, 'Tillagt för nedladdning')
assert.equal(published[0].message, 'Powerwolf Interludium')
hit.album.id = 42 // finns redan i Lidarr
const dup = await app.inject({
method: 'POST',
url: '/api/add',
headers: { cookie },
payload: { kind: 'album', mbid: 'mbid-album-1' },
})
assert.deepEqual(dup.json(), { ok: true, already: true })
await new Promise((r) => setImmediate(r))
assert.equal(published.length, 1, 'dubblett ska inte notifiera')
await app.close()
})
test('misslyckad notis fäller inte add-svaret', async () => {
const hit = {
artist: { foreignArtistId: 'mbid-artist-1', artistName: 'Powerwolf' },
}
const lidarr = { search: async () => [hit], addArtist: async () => ({}) }
const ntfy = {
settings: () => ({ enabled: true, url: 'x', topic: 'y' }),
publish: async () => {
throw new Error('ntfy nere')
},
}
const app = buildApp(baseConfig, { lidarr, musicbrainz: {}, store: settingsStore(tmpDataDir()), ntfy })
const cookie = await loggedIn(app)
const res = await app.inject({
method: 'POST',
url: '/api/add',
headers: { cookie },
payload: { kind: 'artist', mbid: 'mbid-artist-1' },
})
assert.equal(res.statusCode, 200)
assert.deepEqual(res.json(), { ok: true, already: false })
await app.close()
})
test('PUT + GET /api/settings hanterar ntfy-sektionen, enabled=false består', async () => {
const store = settingsStore(tmpDataDir())
const app = buildApp(baseConfig, { lidarr: {}, musicbrainz: {}, store })
const cookie = await loggedIn(app)
await app.inject({
method: 'PUT',
url: '/api/settings',
headers: { cookie },
payload: { ntfy: { url: 'https://ntfy.test', topic: 'media-hamtningar', enabled: true } },
})
let body = (await app.inject({ method: 'GET', url: '/api/settings', headers: { cookie } })).json()
assert.deepEqual(body.ntfy, { enabled: true, url: 'https://ntfy.test', topic: 'media-hamtningar' })
await app.inject({
method: 'PUT',
url: '/api/settings',
headers: { cookie },
payload: { ntfy: { enabled: false } },
})
body = (await app.inject({ method: 'GET', url: '/api/settings', headers: { cookie } })).json()
assert.equal(body.ntfy.enabled, false, 'false ska sparas, inte ignoreras')
assert.equal(body.ntfy.topic, 'media-hamtningar', 'övriga fält ska behållas')
await app.close()
})
test('ntfy-testern skickar testnotis och skiljer på saknad konfig', async () => {
const store = settingsStore(tmpDataDir())
const sent = []
const fetchImpl = async (url, opts) => {
sent.push(JSON.parse(opts.body))
return { ok: true }
}
const testers = makeTesters({ config: { ...baseConfig, ntfy: { url: '', topic: '' } }, store, fetchImpl })
assert.equal((await testers.ntfy()).ok, false, 'utan konfig => fel')
store.save({ ntfy: { url: 'https://ntfy.test', topic: 'media-hamtningar' } })
const out = await testers.ntfy()
assert.equal(out.ok, true)
assert.match(out.message, /avstängda/, 'ska påminna om att notiser inte är påslagna')
assert.equal(sent.length, 1)
assert.equal(sent[0].topic, 'media-hamtningar')
})