import { describe, it, expect } from 'vitest' import { buildTestApp, setupAdmin, auth } from './helpers.js' function subsonicStubFetch(ok: boolean): typeof fetch { return (async (url: any) => { if (String(url).includes('/rest/ping')) { const body = ok ? { 'subsonic-response': { status: 'ok' } } : { 'subsonic-response': { status: 'failed', error: { code: 40, message: 'Wrong username or password.' }, }, } return new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' }, }) } return new Response('not found', { status: 404 }) }) as typeof fetch } describe('settings routes', () => { it('starts empty and stores/clears tokens', async () => { const app = await buildTestApp() const cookie = await setupAdmin(app) const empty = await app.inject({ method: 'GET', url: '/api/settings', ...auth(cookie) }) expect(empty.json()).toEqual({ hasDiscogsToken: false, discogsTokenMasked: null, subsonicUrl: null, subsonicUsername: null, hasSubsonicPassword: false, }) const put = await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { discogsToken: 'abcdef0123456789' }, }) expect(put.statusCode).toBe(200) expect(put.json()).toMatchObject({ hasDiscogsToken: true, discogsTokenMasked: '****56789' }) await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { discogsToken: '' }, }) const cleared = await app.inject({ method: 'GET', url: '/api/settings', ...auth(cookie) }) expect(cleared.json().hasDiscogsToken).toBe(false) await app.close() }) it('stores valid subsonic config after successful ping', async () => { const app = await buildTestApp(subsonicStubFetch(true)) const cookie = await setupAdmin(app) const put = await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { subsonicUrl: 'http://navidrome.local', subsonicUsername: 'sam', subsonicPassword: 'pass', }, }) expect(put.statusCode).toBe(200) expect(put.json()).toMatchObject({ subsonicUrl: 'http://navidrome.local', hasSubsonicPassword: true, }) await app.close() }) it('rejects bad subsonic credentials with 400 subsonic_auth', async () => { const app = await buildTestApp(subsonicStubFetch(false)) const cookie = await setupAdmin(app) const put = await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { subsonicUrl: 'http://navidrome.local', subsonicUsername: 'sam', subsonicPassword: 'wrong', }, }) expect(put.statusCode).toBe(400) expect(put.json()).toMatchObject({ error: 'subsonic_auth' }) await app.close() }) it('rejects unreachable subsonic with 400 subsonic_unreachable', async () => { const failing = (async () => { throw new TypeError('fetch failed') }) as unknown as typeof fetch const app = await buildTestApp(failing) const cookie = await setupAdmin(app) const put = await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { subsonicUrl: 'http://nope.invalid', subsonicUsername: 'sam', subsonicPassword: 'pass', }, }) expect(put.statusCode).toBe(400) expect(put.json()).toMatchObject({ error: 'subsonic_unreachable' }) await app.close() }) it('requires auth', async () => { const app = await buildTestApp() const res = await app.inject({ method: 'GET', url: '/api/settings' }) expect(res.statusCode).toBe(401) await app.close() }) })