All checks were successful
build-and-push / build (push) Successful in 16s
- POST /api/ytmusic/start + /poll: kör device-flödet mot Google och skriver ytmusicapi-kompatibel oauth.json till Youtubarrs datamapp (mounten byts till rw i stacken) - Settings-sektion: Client ID/secret, 'Spara & koppla' visar kod + länk och pollar tills godkänt; 'Testa kopplingen' verifierar med en token-förnyelse mot Google - 4 nya tester, totalt 21 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01824ZrvG2mDYYrmwqypLNup
90 lines
2.9 KiB
JavaScript
90 lines
2.9 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { existsSync, mkdtempSync, readFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import path from 'node:path'
|
|
import { settingsStore } from '../src/settings.js'
|
|
import { ytmusicFlow } from '../src/ytmusic.js'
|
|
|
|
function setup() {
|
|
const dataDir = mkdtempSync(path.join(tmpdir(), 'lyssnarr-yt-'))
|
|
const store = settingsStore(dataDir)
|
|
store.save({ ytmusic: { clientId: 'klient-id', clientSecret: 'klient-hemlis' } })
|
|
const config = { youtubarr: { dataDir } }
|
|
return { dataDir, store, config }
|
|
}
|
|
|
|
test('start returnerar kod och länk från Google', async () => {
|
|
const { store, config } = setup()
|
|
const flow = ytmusicFlow({
|
|
config,
|
|
store,
|
|
fetchImpl: async (url, opts) => {
|
|
assert.match(url, /device\/code/)
|
|
assert.match(opts.body, /client_id=klient-id/)
|
|
return {
|
|
ok: true,
|
|
json: async () => ({
|
|
device_code: 'dev-123',
|
|
user_code: 'ABCD-EFGH',
|
|
verification_url: 'https://www.google.com/device',
|
|
interval: 5,
|
|
expires_in: 1800,
|
|
}),
|
|
}
|
|
},
|
|
})
|
|
const out = await flow.start()
|
|
assert.equal(out.userCode, 'ABCD-EFGH')
|
|
assert.equal(out.deviceCode, 'dev-123')
|
|
})
|
|
|
|
test('start utan client id ger fel', async () => {
|
|
const dataDir = mkdtempSync(path.join(tmpdir(), 'lyssnarr-yt-'))
|
|
const flow = ytmusicFlow({ config: { youtubarr: { dataDir } }, store: settingsStore(dataDir) })
|
|
const out = await flow.start()
|
|
assert.match(out.error, /Client ID/i)
|
|
})
|
|
|
|
test('poll: pending -> klar skriver ytmusicapi-kompatibel oauth.json', async () => {
|
|
const { dataDir, store, config } = setup()
|
|
let calls = 0
|
|
const flow = ytmusicFlow({
|
|
config,
|
|
store,
|
|
fetchImpl: async () => {
|
|
calls++
|
|
if (calls === 1) return { ok: false, json: async () => ({ error: 'authorization_pending' }) }
|
|
return {
|
|
ok: true,
|
|
json: async () => ({
|
|
access_token: 'acc',
|
|
refresh_token: 'ref',
|
|
expires_in: 3600,
|
|
scope: 'https://www.googleapis.com/auth/youtube',
|
|
token_type: 'Bearer',
|
|
}),
|
|
}
|
|
},
|
|
})
|
|
assert.deepEqual(await flow.poll('dev-123'), { status: 'pending' })
|
|
assert.deepEqual(await flow.poll('dev-123'), { status: 'klar' })
|
|
const p = path.join(dataDir, 'oauth.json')
|
|
assert.ok(existsSync(p))
|
|
const tok = JSON.parse(readFileSync(p, 'utf8'))
|
|
assert.equal(tok.access_token, 'acc')
|
|
assert.equal(tok.refresh_token, 'ref')
|
|
assert.equal(tok.token_type, 'Bearer')
|
|
assert.ok(tok.expires_at > Math.floor(Date.now() / 1000))
|
|
})
|
|
|
|
test('poll: avslag ger felstatus', async () => {
|
|
const { store, config } = setup()
|
|
const flow = ytmusicFlow({
|
|
config,
|
|
store,
|
|
fetchImpl: async () => ({ ok: false, json: async () => ({ error: 'access_denied', error_description: 'nekad' }) }),
|
|
})
|
|
assert.deepEqual(await flow.poll('dev-123'), { status: 'error', message: 'nekad' })
|
|
})
|