// uri.test.js — the `favela://` scheme, and what goes in the authority position.
//
// ⛔ THE LIVE BUG. `isMention` is `/^(favela:\/\/|did:)/` — it matches ANY favela URI. The blob form
// was noticed and worked around by ordering ("every caller must test for a blob FIRST",
// dialect.js:43). The RECORD form was not, because it was never specified — so
// `favela://<x>/<collection>/<name>`, which favela-surface already emits for a quote
// (router.js:291), is classified as a mention of a user. A quote renders as a mention of nobody.
//
// ⛔ AND THE AUTHORITY MEANT TWO THINGS. Mentions carry an IDENTITY ROOT (dialect.js:37,
// mentions.js:116). The quote path carries a FEED KEY. Both are 64 hex characters, so nothing can
// tell them apart. hyper-social #61 §12 #1 is exactly this question.
//
// ★ THE DECISION: the authority is the IDENTITY ROOT, one space, everywhere.
// - the mention form already uses it, and those records are written
// - NAMING.md §3 — a name resolves to an identity root, and only then to a room key
// - hyper-social #64 §1 — handle claims anchor to the ROOT; pgh paid for the device-key version
// with `pgh names reanchor --all --force`
// - #61 §2a — an address derived from a rotatable locator is the failure `favela://` exists to
// prevent. A feed key is a LOCATOR; the root is the identity. Same split as name vs oplog.
//
// ⚠ Parsing stays PERMISSIVE about the authority's shape, because it must read bytes already
// written — including a vector that mentions `favela://abc123`. Strictness belongs where URIs are
// MINTED, not where old records are read.
import test from 'brittle'
import { parseUri, userUri, recordUri, blobUri, isRecordUri } from '../lib/uri.js'
import { isMention, isBlob } from '../lib/dialect.js'
const ROOT = 'a'.repeat(64)
test('★★ a record URI is NOT a mention', async (t) => {
const uri = recordUri({ root: ROOT, collection: 'post', name: '01k1v297vo008i4cq4alj7f24p' })
t.absent(isMention(uri), 'the whole defect: a quote used to render as a mention of nobody')
t.ok(isRecordUri(uri))
})
test('★★ three forms, told apart by SHAPE rather than by call order', async (t) => {
t.is(parseUri('favela://' + ROOT).kind, 'user')
t.is(parseUri('favela://' + ROOT + '/post/abc').kind, 'record')
t.is(parseUri('favela://blob/deadbeef').kind, 'blob')
// dialect.js:43 says a caller must test for a blob FIRST or an image silently disappears. With a
// real parse there is no ordering to get wrong.
t.absent(isMention('favela://blob/deadbeef'), 'a blob is not a mention either, now')
t.ok(isBlob('favela://blob/deadbeef'), 'and the blob predicate is unchanged')
})
test('★ the parts come back', async (t) => {
const u = parseUri('favela://' + ROOT + '/post/01k1v297vo008i4cq4alj7f24p')
t.is(u.root, ROOT)
t.is(u.collection, 'post')
t.is(u.name, '01k1v297vo008i4cq4alj7f24p')
t.is(parseUri('favela://blob/deadbeef').oid, 'deadbeef')
t.is(parseUri('favela://' + ROOT).root, ROOT)
})
test('★ minting round-trips', async (t) => {
const r = recordUri({ root: ROOT, collection: 'post', name: 'abc' })
t.alike(parseUri(r), { kind: 'record', root: ROOT, collection: 'post', name: 'abc', oid: null })
t.is(parseUri(userUri(ROOT)).kind, 'user')
t.is(parseUri(blobUri('deadbeef')).kind, 'blob')
})
// A vector in render.test.js mentions `favela://abc123`. Records are written; the reader must not
// start refusing them because a later spec said roots are 64 hex.
test('⛔ a legacy non-hex authority still reads as a user', async (t) => {
t.is(parseUri('favela://abc123').kind, 'user')
t.ok(isMention('favela://abc123'), 'and is still a mention')
})
test('⛔ minting a record URI REQUIRES an identity root — strict where we write', async (t) => {
t.exception(() => recordUri({ root: 'abc123', collection: 'post', name: 'x' }), /identity root/i,
'a feed key or a short string must not reach the authority position')
t.exception(() => recordUri({ root: ROOT, collection: 'a/b', name: 'x' }), /segment/i)
t.exception(() => recordUri({ root: ROOT, collection: 'post', name: '' }), /segment/i)
})
test('⛔ anything else fails closed', async (t) => {
for (const bad of ['favela://', 'favela://a/b', 'favela://a/b/c/d', 'https://example.com', 'did:plc:x', '', null]) {
t.is(parseUri(bad), null, JSON.stringify(bad))
}
})
test('★ did: stays a mention — it is the legacy user form', async (t) => {
t.ok(isMention('did:plc:abc'), 'dialect.js:37 calls it the legacy form and records carry it')
})