// FavelaSource must satisfy the same contract RecordSource does — see record-source-contract.js.
import test from 'brittle'
import { recordSourceContract } from './record-source-contract.js'
import { FavelaSource } from '../lib/favela-source.js'
// A held favela feed with one post and a profile, as FavelaFeed presents one.
const peer = () => ({
async reach () { return { networks: ['fediverse'], credential: null } },
async ownPosts () {
return {
feed: 'f'.repeat(64),
rows: [{ name: '01k2test', collection: 'post', body: 'hello world', createdAt: '2026-09-08T00:00:00Z' }]
}
},
async profile () {
return { frontmatter: { name: 'ashes', avatar: 'a'.repeat(64) }, body: 'the downstairs device' }
},
async call () { return null }
})
recordSourceContract(test, 'FavelaSource', async () => new FavelaSource({
peer: peer(),
host: 'ap.7rnx.net',
actorId: 'https://ap.7rnx.net/ap/users/ashes/actor',
followersId: 'https://ap.7rnx.net/ap/users/ashes/followers'
}))
// ⛔ A REGISTERED USER'S URLS MUST BE UNDER THEIR OWN PATH. blobUrl and objectIdFor derive from the
// HOST alone, so every user's avatar and every user's object landed on /ap/blobs/… and
// /ap/objects/… — the FLAT paths, which belong to the primary. Two consequences, both live:
//
// the avatar 404d, because the flat blob route asks the PRIMARY's PDS for a favela oid
// two users could mint the SAME object id (hyper-social #59 §6 flagged exactly this)
//
// It had not collided only because the incumbent's rkeys are TIDs and favela's are record names.
test('★★ a registered user\'s blob and object urls are under their own path', async (t) => {
const src = new FavelaSource({
peer: {
async reach () { return { networks: ['fediverse'], credential: null } },
async ownPosts () { return { feed: 'f'.repeat(64), rows: [{ name: '01k2', body: 'hi', createdAt: '2026-09-08T00:00:00Z' }] } },
async profile () { return { frontmatter: { name: 'ashes', avatar: 'b'.repeat(64) }, body: '' } },
async call () { return null }
},
host: 'ap.7rnx.net',
base: 'https://ap.7rnx.net/ap/users/ashatron',
actorId: 'https://ap.7rnx.net/ap/users/ashatron/actor',
followersId: 'https://ap.7rnx.net/ap/users/ashatron/followers'
})
const p = await src.getProfile()
t.ok(p.icon.url.startsWith('https://ap.7rnx.net/ap/users/ashatron/blobs/'),
'avatar under their path, not the primary\'s: ' + p.icon.url)
const { notes } = await src.listNotes({ limit: 1 })
t.ok(notes[0].id.startsWith('https://ap.7rnx.net/ap/users/ashatron/objects/'),
'and so is the object id: ' + notes[0].id)
})