Skip to content

Commit 40362e6

Browse files
test: 96% line coverage, 305 tests — reduce branches, tree root, LRU, migration verification
1 parent f4ce33d commit 40362e6

5 files changed

Lines changed: 84 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
[![CI](https://github.com/forgesworn/bray/actions/workflows/ci.yml/badge.svg)](https://github.com/forgesworn/bray/actions/workflows/ci.yml)
66
[![npm](https://img.shields.io/npm/v/nostr-bray)](https://www.npmjs.com/package/nostr-bray)
7-
[![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)](./package.json)
7+
[![coverage](https://img.shields.io/badge/coverage-96%25-brightgreen)](./package.json)
88
[![licence](https://img.shields.io/npm/l/nostr-bray)](./LICENSE)
99
[![TypeScript](https://img.shields.io/badge/TypeScript-ESM-blue)](./tsconfig.json)
1010

test/context.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,35 @@ describe('IdentityContext', () => {
191191
expect(refB!.every(b => b === 0)).toBe(true)
192192
})
193193
})
194+
195+
describe('treeRootPubkey', () => {
196+
it('returns the nsec-tree root master pubkey', () => {
197+
const ctx = new IdentityContext(TEST_NSEC, 'nsec')
198+
expect(ctx.treeRootPubkey).toBeDefined()
199+
// Tree root pubkey is different from the raw key's npub
200+
expect(ctx.treeRootPubkey).not.toBe(ctx.activeNpub)
201+
ctx.destroy()
202+
})
203+
})
204+
205+
describe('LRU cache duplicate', () => {
206+
it('re-deriving the same identity updates cache without duplicating', () => {
207+
const ctx = new IdentityContext(TEST_NSEC, 'nsec')
208+
const first = ctx.derive('same', 0)
209+
const second = ctx.derive('same', 0)
210+
expect(first.npub).toBe(second.npub)
211+
const list = ctx.listIdentities()
212+
const sameCount = list.filter(i => i.purpose === 'same').length
213+
expect(sameCount).toBe(1)
214+
ctx.destroy()
215+
})
216+
})
217+
218+
describe('activePublicKeyHex', () => {
219+
it('returns 64-char hex public key', () => {
220+
const ctx = new IdentityContext(TEST_NSEC, 'nsec')
221+
expect(ctx.activePublicKeyHex).toMatch(/^[0-9a-f]{64}$/)
222+
ctx.destroy()
223+
})
224+
})
194225
})

test/identity/migration.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ describe('identity migration', () => {
104104
}
105105
})
106106

107+
it('skips events that fail signature verification', async () => {
108+
const pool = mockPool()
109+
const badEvent = { ...mockProfile, pubkey: 'wrong-pubkey' } // author mismatch
110+
const backup = {
111+
pubkeyHex: 'oldpub123',
112+
events: [badEvent, mockContacts],
113+
}
114+
const result = await handleIdentityRestore(ctx, pool as any, backup)
115+
expect(result.skipped.length).toBe(1)
116+
expect(result.skipped[0].reason).toMatch(/verification|mismatch/i)
117+
})
118+
107119
it('does NOT re-sign kind 31000 attestations (trust chain protection)', async () => {
108120
const pool = mockPool()
109121
const backup = {

test/social/groups.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ describe('NIP-29 group handlers', () => {
3636
expect(result.isOpen).toBe(true)
3737
})
3838

39+
it('takes highest created_at from multiple metadata events', async () => {
40+
const events = [
41+
{ kind: 39000, pubkey: 'r', created_at: 500, tags: [['d', 'g'], ['name', 'Old']], content: '', id: '1', sig: 's' },
42+
{ kind: 39000, pubkey: 'r', created_at: 1000, tags: [['d', 'g'], ['name', 'New']], content: '', id: '2', sig: 's' },
43+
]
44+
const pool = mockPool(events)
45+
const result = await handleGroupInfo(pool as any, 'npub1test', { relay: 'wss://test', groupId: 'g' })
46+
expect(result.name).toBe('New')
47+
})
48+
3949
it('returns minimal info when no metadata found', async () => {
4050
const pool = mockPool([])
4151
const result = await handleGroupInfo(pool as any, 'npub1test', { relay: 'wss://test', groupId: 'unknown' })
@@ -100,6 +110,17 @@ describe('NIP-29 group handlers', () => {
100110
expect(result[1].role).toBe('admin')
101111
})
102112

113+
it('takes highest created_at from multiple member events', async () => {
114+
const events = [
115+
{ kind: 39002, pubkey: 'r', created_at: 500, tags: [['d', 'g'], ['p', 'old']], content: '', id: '1', sig: 's' },
116+
{ kind: 39002, pubkey: 'r', created_at: 1000, tags: [['d', 'g'], ['p', 'new']], content: '', id: '2', sig: 's' },
117+
]
118+
const pool = mockPool(events)
119+
const result = await handleGroupMembers(pool as any, 'npub1test', { groupId: 'g' })
120+
expect(result.length).toBe(1)
121+
expect(result[0].pubkey).toBe('new')
122+
})
123+
103124
it('returns empty when no member list found', async () => {
104125
const pool = mockPool([])
105126
const result = await handleGroupMembers(pool as any, 'npub1test', { groupId: 'empty' })

test/social/handlers.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ describe('social handlers', () => {
9494
expect(result.about).toBe('A test user')
9595
})
9696

97+
it('takes highest created_at when multiple profiles returned', async () => {
98+
const pool = mockPool([
99+
{ kind: 0, pubkey: 'p', created_at: 500, tags: [], content: JSON.stringify({ name: 'Old' }), id: '1', sig: 's' },
100+
{ kind: 0, pubkey: 'p', created_at: 1000, tags: [], content: JSON.stringify({ name: 'New' }), id: '2', sig: 's' },
101+
])
102+
const result = await handleSocialProfileGet(pool as any, 'npub', 'p')
103+
expect(result.name).toBe('New')
104+
})
105+
97106
it('returns empty profile when none found', async () => {
98107
const pool = mockPool([])
99108
const result = await handleSocialProfileGet(pool as any, 'somenpub', 'somepub')
@@ -203,6 +212,16 @@ describe('social handlers', () => {
203212
expect(result[2].petname).toBe('alice')
204213
})
205214

215+
it('takes highest created_at when multiple kind 3 events', async () => {
216+
const pool = mockPool([
217+
{ kind: 3, pubkey: 'p', created_at: 500, tags: [['p', 'old']], content: '', id: '1', sig: 's' },
218+
{ kind: 3, pubkey: 'p', created_at: 1000, tags: [['p', 'new']], content: '', id: '2', sig: 's' },
219+
])
220+
const result = await handleContactsGet(pool as any, 'npub', 'p')
221+
expect(result.length).toBe(1)
222+
expect(result[0].pubkey).toBe('new')
223+
})
224+
206225
it('returns empty when no kind 3 found', async () => {
207226
const pool = mockPool([])
208227
const result = await handleContactsGet(pool as any, 'somenpub', 'mypub')

0 commit comments

Comments
 (0)