This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / api / identity.test.ts
3.9 kB 100 lines
1import { describe, expect, it, vi } from 'vitest'; 2import { createBobbinClient, type BobbinContext } from './client'; 3import { IdentityCache, type MiniDoc } from './identity'; 4 5const DOC: MiniDoc = { did: 'did:plc:x', handle: 'alice.test' }; 6 7const docResponse = (doc: MiniDoc): Response => 8 new Response(JSON.stringify(doc), { 9 status: 200, 10 headers: { 'content-type': 'application/json' } 11 }); 12 13const makeCtx = (fetchMock: typeof globalThis.fetch): BobbinContext => 14 createBobbinClient({ serviceUrl: 'https://bobbin.test', fetch: fetchMock }); 15 16const deferred = <T>(): { promise: Promise<T>; resolve: (value: T) => void } => { 17 let resolve!: (value: T) => void; 18 const promise = new Promise<T>((r) => { 19 resolve = r; 20 }); 21 return { promise, resolve }; 22}; 23 24describe('IdentityCache.resolve', () => { 25 it('fetches on the first miss then serves the cached document', async () => { 26 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); 27 const cache = new IdentityCache(makeCtx(fetchMock)); 28 29 const first = await cache.resolve('alice.test'); 30 const second = await cache.resolve('alice.test'); 31 32 expect(first).toEqual(DOC); 33 expect(second).toEqual(DOC); 34 expect(fetchMock).toHaveBeenCalledTimes(1); 35 expect((fetchMock.mock.calls[0][0] as URL).searchParams.get('identifier')).toBe('alice.test'); 36 }); 37 38 it('coalesces concurrent misses for the same key into one in-flight fetch', async () => { 39 const gate = deferred<Response>(); 40 const fetchMock = vi.fn<typeof globalThis.fetch>().mockReturnValue(gate.promise); 41 const cache = new IdentityCache(makeCtx(fetchMock)); 42 43 const a = cache.resolve('alice.test'); 44 const b = cache.resolve('alice.test'); 45 expect(fetchMock).toHaveBeenCalledTimes(1); 46 47 gate.resolve(docResponse(DOC)); 48 const [ra, rb] = await Promise.all([a, b]); 49 expect(ra).toEqual(DOC); 50 expect(rb).toEqual(DOC); 51 expect(fetchMock).toHaveBeenCalledTimes(1); 52 }); 53 54 it('re-fetches after the in-flight promise settles (no permanent stampede lock)', async () => { 55 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); 56 const cache = new IdentityCache(makeCtx(fetchMock)); 57 58 await cache.resolve('bob.test'); 59 fetchMock.mockResolvedValueOnce(docResponse({ did: 'did:plc:y', handle: 'carol.test' })); 60 await cache.resolve('carol.test'); 61 expect(fetchMock).toHaveBeenCalledTimes(2); 62 }); 63 64 it('populates both directions of the index from a resolved document', async () => { 65 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); 66 const cache = new IdentityCache(makeCtx(fetchMock)); 67 68 await cache.resolve('alice.test'); 69 expect(cache.didFor('alice.test')).toBe('did:plc:x'); 70 expect(cache.handleFor('did:plc:x')).toBe('alice.test'); 71 }); 72}); 73 74describe('IdentityCache.prime & map selection', () => { 75 it('seeds both directions without any fetch', () => { 76 const fetchMock = vi.fn<typeof globalThis.fetch>(); 77 const cache = new IdentityCache(makeCtx(fetchMock)); 78 79 cache.prime(DOC); 80 expect(cache.didFor('alice.test')).toBe('did:plc:x'); 81 expect(cache.handleFor('did:plc:x')).toBe('alice.test'); 82 expect(fetchMock).not.toHaveBeenCalled(); 83 }); 84 85 it('resolves a DID against the did map and a handle against the handle map', async () => { 86 const fetchMock = vi.fn<typeof globalThis.fetch>(); 87 const cache = new IdentityCache(makeCtx(fetchMock)); 88 cache.prime(DOC); 89 90 await expect(cache.resolve('did:plc:x')).resolves.toEqual(DOC); 91 await expect(cache.resolve('alice.test')).resolves.toEqual(DOC); 92 expect(fetchMock).not.toHaveBeenCalled(); 93 94 // unknown dids miss the handle map and fetch. 95 fetchMock.mockResolvedValueOnce(docResponse({ did: 'did:plc:z', handle: 'dan.test' })); 96 await cache.resolve('did:plc:z'); 97 expect(fetchMock).toHaveBeenCalledTimes(1); 98 expect((fetchMock.mock.calls[0][0] as URL).searchParams.get('identifier')).toBe('did:plc:z'); 99 }); 100});