All checks were successful
build-and-push / build (push) Successful in 17s
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>
184 lines
6.6 KiB
JavaScript
184 lines
6.6 KiB
JavaScript
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')
|
||
})
|