import { describe, expect, it } from 'vitest'; import { reciprocalRankFusion } from '../fusion'; import type { SourceRef } from '../types'; function ref(itemId: string): SourceRef { return { product: 'mail', accountId: 'acct-1', collectionId: 'inbox', itemId, chunkIx: 0 }; } describe('reciprocalRankFusion', () => { it('ranks an item found by both legs above one found by only one', () => { const local = [{ ref: ref('a'), score: 1 }, { ref: ref('b'), score: 0.9 }]; const server = [{ ref: ref('a'), score: 0.8 }, { ref: ref('c'), score: 0.7 }]; const fused = reciprocalRankFusion([local, server], 10); expect(fused[0].ref.itemId).toBe('a'); // rank 1 in both legs expect(fused.map((f) => f.ref.itemId)).toEqual(['a', 'b', 'c']); }); it('degrades to a single retriever when one leg is empty, no special-casing needed', () => { const local = [{ ref: ref('a'), score: 1 }, { ref: ref('b'), score: 0.5 }]; const fused = reciprocalRankFusion([local, []], 10); expect(fused.map((f) => f.ref.itemId)).toEqual(['a', 'b']); }); it('returns nothing when both legs are empty', () => { expect(reciprocalRankFusion([[], []], 10)).toEqual([]); }); it('respects the limit', () => { const local = [ref('a'), ref('b'), ref('c')].map((r, i) => ({ ref: r, score: 1 - i * 0.1 })); const fused = reciprocalRankFusion([local, []], 2); expect(fused).toHaveLength(2); }); it('does not double-count the same item across legs when collectionId differs', () => { // Same email, but the two legs report a different mailbox for it - see // fusion.ts's refKey comment for why collectionId is deliberately not // part of the fusion identity. const local = [{ ref: { ...ref('a'), collectionId: 'inbox' }, score: 1 }]; const server = [{ ref: { ...ref('a'), collectionId: 'archive' }, score: 1 }]; const fused = reciprocalRankFusion([local, server], 10); expect(fused).toHaveLength(1); }); });