OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge / test / favela-source-contract.test.js

58 lines · javascript ref:
// 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)
})