feat: per-user settings routes with subsonic validation
This commit is contained in:
124
server/test/settings.test.ts
Normal file
124
server/test/settings.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user