/* * The jest suite ships global manual mocks for `multiformats/cid` and * `multiformats/hashes/hasher` (in root `__mocks__/`) so unrelated tests don't * pull in real crypto. This test is precisely about the real CID hashing, so we * opt back into the actual implementations here. */ jest.unmock('multiformats/cid') jest.unmock('multiformats/hashes/hasher') import {CID} from 'multiformats/cid' import {computeCid} from '#/lib/api/computeCid' import {type app, type com} from '#/lexicons' /* * Golden-CID regression test for the composer post pipeline. * * `computeCid` hashes a post record in the client so a thread's later posts can * reference earlier posts by CID before the server assigns them. The hash is * byte-sensitive: any drift in how records (especially blobs) are serialized to * DAG-CBOR silently produces the wrong CID and breaks reply chains with NO type * error. These golden values MUST remain byte-identical - they gate the * structural lex-blob shape check. * * The blob CID below is a fixed, deterministic CIDv1/raw/sha256 used purely as a * stable fixture - it is not derived from any real upload. */ const BLOB_CID = 'bafkreieq5jui4j25lacwomsqgjeswwl3y5zcdrresptwgmfylxo2depppq' /** * Build a post record with an image embed whose blob is the given value. */ function postWithImageBlob(blob: unknown): app.bsky.feed.post.Main { return { $type: 'app.bsky.feed.post', createdAt: '2024-01-01T00:00:00.001Z', text: 'post with image', embed: { $type: 'app.bsky.embed.images', images: [ { image: blob, alt: 'alt text', aspectRatio: {width: 100, height: 200}, }, ], }, } as app.bsky.feed.post.Main } describe('computeCid', () => { it('case 1: plain post record with no blob', async () => { const record: app.bsky.feed.post.Main = { $type: 'app.bsky.feed.post', createdAt: '2024-01-01T00:00:00.000Z', text: 'hello world', } expect(await computeCid(record)).toBe( 'bafyreieawtmh7hwfrqpamqkodza5r62bbfhsepe2iyustgxhgbhi6b2lfi', ) }) it('case 2: record whose embed carries a plain-JSON lex blob', async () => { /* * The blob shape lex `uploadBlob` returns: a plain object * `{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID. The * structural lex-blob guard passes it through `prepareForHashing` * untouched and DAG-CBOR encodes its CID `ref` as a CID link. The golden * CID below is the byte-identical value the pre-migration legacy blob * class instance produced via `.ipld()`. */ const blob = { $type: 'blob' as const, ref: CID.parse(BLOB_CID), mimeType: 'image/jpeg', size: 12345, } expect(await computeCid(postWithImageBlob(blob))).toBe( 'bafyreiem7g6vja66nebr7he4fshfnlyndyldbvle2n265oixscmepjcbii', ) }) it('case 3: three-post thread chains reply StrongRef CIDs', async () => { const did = 'did:plc:abc123' const base = new Date('2024-01-01T00:00:00.000Z') const golden = [ 'bafyreig62rxs34h5rvznfrracwkjlfgad5b25qxglp2hcziqdfas2nw2ee', 'bafyreicxcj2tq5jrh5jcaczg3eli5cvxitgzu7kpu3fm5v3njq2byjxirq', 'bafyreigvaswuhlpd7dllja2xrqswhqbruyv2kar7mvbn7gdm5ldzu6vkti', ] let reply: app.bsky.feed.post.Main['reply'] | undefined for (let i = 0; i < 3; i++) { const now = new Date(base.getTime() + i) const uri = `at://${did}/app.bsky.feed.post/rkey${i}` const record = { $type: 'app.bsky.feed.post', createdAt: now.toISOString(), text: `post ${i}`, reply, } as app.bsky.feed.post.Main const cid = await computeCid(record) expect(cid).toBe(golden[i]) const ref = {cid, uri} as com.atproto.repo.strongRef.Main reply = {root: reply?.root ?? ref, parent: ref} } }) })