You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

147 lines
5.0 KiB

import { describe, expect, it } from 'vitest'
import { ExtendedKind } from '@/constants'
import {
buildIndexByAddress,
buildPublicationIndexMap,
collectReachableAddresses,
collectReachableAddressesCached,
eventTagAddress,
filterValidIndexEvents,
getTopLevelIndexEvents,
pickNewerPublicationIndexEvent,
publicationIndexMapValues
} from '@/lib/publication-index'
import type { Event } from 'nostr-tools'
import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools'
const sk = generateSecretKey()
const PK = getPublicKey(sk)
function indexEvent(d: string, aTags: string[]): Event {
return finalizeEvent(
{
kind: ExtendedKind.PUBLICATION,
created_at: 100,
content: '',
tags: [['d', d], ['title', `Title ${d}`], ...aTags.map((a) => ['a', a] as [string, string])]
},
sk
)
}
function contentEvent(d: string, id = d.padEnd(64, '1').slice(0, 64)): Event {
return {
id,
kind: ExtendedKind.PUBLICATION_CONTENT,
pubkey: PK,
created_at: 100,
content: 'section body',
tags: [['d', d], ['title', `Section ${d}`]],
sig: 'd'.repeat(128)
}
}
describe('publication-index', () => {
it('filterValidIndexEvents rejects non-NKBIP-01 indexes', () => {
const valid = indexEvent('book', [`30041:${PK}:chapter-1`])
const withContent = { ...valid, content: 'not empty' }
const noTitle = { ...valid, tags: [['d', 'book'], ['a', `30041:${PK}:chapter-1`]] }
const nullContent = { ...valid, content: null as unknown as string }
expect(filterValidIndexEvents([valid])).toHaveLength(1)
expect(filterValidIndexEvents([nullContent])).toHaveLength(1)
expect(filterValidIndexEvents([withContent, noTitle])).toHaveLength(0)
})
it('getTopLevelIndexEvents excludes nested 30040 children', () => {
const childAddr = `30040:${PK}:part-1`
const root = indexEvent('book', [childAddr, `30041:${PK}:intro`])
const child = indexEvent('part-1', [`30041:${PK}:chapter-1`])
const top = getTopLevelIndexEvents([root, child])
expect(top).toHaveLength(1)
expect(eventTagAddress(top[0])).toBe(`30040:${PK}:book`)
})
it('collectReachableAddressesCached walks nested 30040 and 30041 refs', () => {
const childAddr = `30040:${PK}:part-1`
const leafAddr = `30041:${PK}:chapter-1`
const root = indexEvent('book', [childAddr, `30041:${PK}:intro`])
const child = indexEvent('part-1', [leafAddr])
const indexByAddress = buildIndexByAddress([root, child])
const reachable = collectReachableAddressesCached(root, indexByAddress)
expect(reachable.has(`30040:${PK}:book`)).toBe(true)
expect(reachable.has(childAddr)).toBe(true)
expect(reachable.has(`30041:${PK}:intro`)).toBe(true)
expect(reachable.has(leafAddr)).toBe(true)
})
it('fetchMissingIndex resolves nested index not in initial cache', async () => {
const childAddr = `30040:${PK}:part-1`
const root = indexEvent('book', [childAddr])
const child = indexEvent('part-1', [`30041:${PK}:chapter-1`])
const indexByAddress = buildIndexByAddress([root])
const reachable = await collectReachableAddresses(root, indexByAddress, async (addr) => {
if (addr === childAddr) return child
return null
})
expect(reachable.has(childAddr)).toBe(true)
expect(reachable.has(`30041:${PK}:chapter-1`)).toBe(true)
expect(indexByAddress.get(childAddr)?.id).toBe(child.id)
})
it('eventTagAddress uses lowercase pubkey', () => {
const ev = contentEvent('section-a')
expect(eventTagAddress(ev)).toBe(`30041:${PK}:section-a`)
})
it('buildPublicationIndexMap keeps newest valid row per kind:pubkey:d', () => {
const older = indexEvent('same-book', [`30041:${PK}:intro`])
older.created_at = 10
const newer = finalizeEvent(
{
kind: ExtendedKind.PUBLICATION,
created_at: 20,
content: '',
tags: [
['d', 'same-book'],
['title', 'Revised title'],
['a', `30041:${PK}:intro`]
]
},
sk
)
const invalid = { ...older, content: 'not empty' }
const map = buildPublicationIndexMap([older, newer, invalid])
expect(publicationIndexMapValues(map)).toHaveLength(1)
expect(map.get(`30040:${PK}:same-book`)?.id).toBe(newer.id)
expect(getTopLevelIndexEvents([older, newer, invalid])).toHaveLength(1)
expect(getTopLevelIndexEvents([older, newer, invalid])[0].id).toBe(newer.id)
})
it('pickNewerPublicationIndexEvent breaks created_at ties by event id', () => {
const first = finalizeEvent(
{
kind: ExtendedKind.PUBLICATION,
created_at: 50,
content: '',
tags: [['d', 'tie-book'], ['title', 'First'], ['a', `30041:${PK}:a`]]
},
sk
)
const second = finalizeEvent(
{
kind: ExtendedKind.PUBLICATION,
created_at: 50,
content: '',
tags: [['d', 'tie-book'], ['title', 'Second'], ['a', `30041:${PK}:b`]]
},
sk
)
const chosen = pickNewerPublicationIndexEvent(first, second)
expect(chosen.id).toBe(first.id > second.id ? first.id : second.id)
})
})