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' }) }) test('saveBrowserHeaders: bygger browser.json av råa headers', () => { const { dataDir, store, config } = setup() const flow = ytmusicFlow({ config, store }) const out = flow.saveBrowserHeaders( 'POST /youtubei/v1/browse HTTP/2\nUser-Agent: TestUA/1.0\nAccept-Language: sv\nX-Goog-AuthUser: 0\nCookie: VISITOR=1; __Secure-3PAPISID=hemlis; PREF=x\n', ) assert.deepEqual(out, { ok: true }) const b = JSON.parse(readFileSync(path.join(dataDir, 'browser.json'), 'utf8')) assert.equal(b['User-Agent'], 'TestUA/1.0') assert.match(b.Cookie, /__Secure-3PAPISID/) assert.equal(b['x-origin'], 'https://music.youtube.com') }) test('saveBrowserHeaders: avvisar headers utan giltig cookie', () => { const { store, config } = setup() const flow = ytmusicFlow({ config, store }) assert.match(flow.saveBrowserHeaders('User-Agent: x').error, /Cookie/i) assert.match(flow.saveBrowserHeaders('Cookie: bara=skräp').error, /__Secure-3PAPISID/) })