// The favela-web HTTP client. Pure — a fake fetch records what would go on the wire.
//
// The claim under test is narrow and load-bearing: every request carries the session cookie when one
// exists. A client that quietly omits it still gets 200s back, with viewerDid null — so the bug shows
// up as posts rendering un-liked, never as an error. That is exactly why it is asserted here rather
// than left to a device.
//
// Run: bare test/favela-api.test.mjs
import { FavelaApi, apiFromStatus } from '../lib/favela-api.js'
const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log(' ok -', m) }
const calls = []
const fakeFetch = async (url, init) => {
calls.push({ url, init })
return { ok: true, status: 200, json: async () => ({ posts: [], url }) }
}
console.log('url building')
const api = new FavelaApi({ baseUrl: 'http://127.0.0.1:4041/', session: 'tok-1', fetchImpl: fakeFetch })
assert(api.baseUrl === 'http://127.0.0.1:4041', 'trailing slash trimmed (else //api/…)')
assert(api.url('/api/timeline', { limit: 30 }) === 'http://127.0.0.1:4041/api/timeline?limit=30', 'query string built')
assert(api.url('/api/thread', { uri: 'at://x/y/z' }).includes('uri=at%3A%2F%2Fx%2Fy%2Fz'), 'params are URL-ENCODED (an at:// uri must survive)')
assert(api.url('/api/search', { q: 'a', kind: null, limit: undefined }) === 'http://127.0.0.1:4041/api/search?q=a', 'null/undefined params dropped, not sent as "null"')
console.log('session')
assert(api.authenticated === true, 'reports authenticated when it holds a token')
await api.timeline({ limit: 5 })
assert(calls[0].init.headers.cookie === 'favela_session=tok-1', 'sends the session cookie')
const anon = new FavelaApi({ baseUrl: 'http://x', fetchImpl: fakeFetch })
assert(anon.authenticated === false, 'reports anonymous without a token')
await anon.timeline()
assert(!calls[1].init.headers.cookie, 'sends NO cookie when anonymous (rather than an empty one)')
console.log('every typed wrapper carries the session')
// The regression that matters: a wrapper added later that forgets the cookie. Assert across ALL of them
// rather than trusting the one that happened to be tested.
calls.length = 0
await api.timeline(); await api.author({ did: 'd' }); await api.profile({ did: 'd' })
await api.thread({ uri: 'u' }); await api.replies({ uri: 'u' }); await api.search({ q: 'x' })
await api.follows({ did: 'd' }); await api.followers({ did: 'd' }); await api.tagFeed({ tag: 't' })
assert(calls.length === 9, 'nine wrappers exercised')
assert(calls.every((c) => c.init.headers.cookie === 'favela_session=tok-1'), 'ALL nine send the cookie')
assert(calls.map((c) => c.url).every((u) => u.startsWith('http://127.0.0.1:4041/api/')), 'all hit /api/')
console.log('errors name the route')
const failing = new FavelaApi({
baseUrl: 'http://x',
fetchImpl: async () => ({ ok: false, status: 400, json: async () => ({ error: 'uri required' }) })
})
let e = null
try { await failing.thread({ uri: '' }) } catch (err) { e = err }
assert(e && /\/api\/thread/.test(e.message) && /uri required/.test(e.message), 'error names the route AND the reason')
console.log('apiFromStatus')
assert(apiFromStatus(null) === null, 'null when there is no status')
assert(apiFromStatus({ favelaUrl: null }) === null, 'null when favela-web did not start')
const built = apiFromStatus({ favelaUrl: 'http://127.0.0.1:4041', favelaSession: 'tok-2' }, fakeFetch)
assert(built.authenticated && built.session === 'tok-2', 'carries the session through from status')
const anonBuilt = apiFromStatus({ favelaUrl: 'http://127.0.0.1:4041', favelaSession: null }, fakeFetch)
assert(anonBuilt.authenticated === false, 'anonymous when sign-in failed — the screen can SAY so')
console.log('\nall favela-api tests passed')