Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101import { describe, expect, it, vi } from 'vitest';import { createBobbinClient, type BobbinContext } from './client';import { IdentityCache, type MiniDoc } from './identity';
const DOC: MiniDoc = { did: 'did:plc:x', handle: 'alice.test' };
const docResponse = (doc: MiniDoc): Response => new Response(JSON.stringify(doc), { status: 200, headers: { 'content-type': 'application/json' } });
const makeCtx = (fetchMock: typeof globalThis.fetch): BobbinContext => createBobbinClient({ serviceUrl: 'https://bobbin.test', fetch: fetchMock });
const deferred = <T>(): { promise: Promise<T>; resolve: (value: T) => void } => { let resolve!: (value: T) => void; const promise = new Promise<T>((r) => { resolve = r; }); return { promise, resolve };};
describe('IdentityCache.resolve', () => { it('fetches on the first miss then serves the cached document', async () => { const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); const cache = new IdentityCache(makeCtx(fetchMock));
const first = await cache.resolve('alice.test'); const second = await cache.resolve('alice.test');
expect(first).toEqual(DOC); expect(second).toEqual(DOC); expect(fetchMock).toHaveBeenCalledTimes(1); expect((fetchMock.mock.calls[0][0] as URL).searchParams.get('identifier')).toBe('alice.test'); });
it('coalesces concurrent misses for the same key into one in-flight fetch', async () => { const gate = deferred<Response>(); const fetchMock = vi.fn<typeof globalThis.fetch>().mockReturnValue(gate.promise); const cache = new IdentityCache(makeCtx(fetchMock));
const a = cache.resolve('alice.test'); const b = cache.resolve('alice.test'); expect(fetchMock).toHaveBeenCalledTimes(1);
gate.resolve(docResponse(DOC)); const [ra, rb] = await Promise.all([a, b]); expect(ra).toEqual(DOC); expect(rb).toEqual(DOC); expect(fetchMock).toHaveBeenCalledTimes(1); });
it('re-fetches after the in-flight promise settles (no permanent stampede lock)', async () => { const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); const cache = new IdentityCache(makeCtx(fetchMock));
await cache.resolve('bob.test'); fetchMock.mockResolvedValueOnce(docResponse({ did: 'did:plc:y', handle: 'carol.test' })); await cache.resolve('carol.test'); expect(fetchMock).toHaveBeenCalledTimes(2); });
it('populates both directions of the index from a resolved document', async () => { const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(docResponse(DOC)); const cache = new IdentityCache(makeCtx(fetchMock));
await cache.resolve('alice.test'); expect(cache.didFor('alice.test')).toBe('did:plc:x'); expect(cache.handleFor('did:plc:x')).toBe('alice.test'); });});
describe('IdentityCache.prime & map selection', () => { it('seeds both directions without any fetch', () => { const fetchMock = vi.fn<typeof globalThis.fetch>(); const cache = new IdentityCache(makeCtx(fetchMock));
cache.prime(DOC); expect(cache.didFor('alice.test')).toBe('did:plc:x'); expect(cache.handleFor('did:plc:x')).toBe('alice.test'); expect(fetchMock).not.toHaveBeenCalled(); });
it('resolves a DID against the did map and a handle against the handle map', async () => { const fetchMock = vi.fn<typeof globalThis.fetch>(); const cache = new IdentityCache(makeCtx(fetchMock)); cache.prime(DOC);
await expect(cache.resolve('did:plc:x')).resolves.toEqual(DOC); await expect(cache.resolve('alice.test')).resolves.toEqual(DOC); expect(fetchMock).not.toHaveBeenCalled();
// unknown dids miss the handle map and fetch. fetchMock.mockResolvedValueOnce(docResponse({ did: 'did:plc:z', handle: 'dan.test' })); await cache.resolve('did:plc:z'); expect(fetchMock).toHaveBeenCalledTimes(1); expect((fetchMock.mock.calls[0][0] as URL).searchParams.get('identifier')).toBe('did:plc:z'); });});