Fix search result limiting after ranking (#277)

This commit is contained in:
kikootwo
2026-08-18 17:26:27 -04:00
committed by GitHub
parent ae75c914c9
commit bc371860d0
7 changed files with 181 additions and 15 deletions
@@ -20,6 +20,10 @@ const rankTorrentsMock = vi.hoisted(() => vi.fn());
const groupIndexersMock = vi.hoisted(() => vi.fn());
const groupDescriptionMock = vi.hoisted(() => vi.fn(() => 'Group'));
vi.mock('@/lib/db', () => ({
prisma: {},
}));
vi.mock('@/lib/middleware/auth', () => ({
requireAuth: requireAuthMock,
}));
@@ -94,6 +98,45 @@ describe('Audiobooks search torrents route', () => {
expect(payload.results[0].rank).toBe(1);
expect(rankTorrentsMock).toHaveBeenCalled();
});
it('ranks all candidates before returning the top 100 with truncation metadata', async () => {
authRequest.json.mockResolvedValue({ title: 'Title', author: 'Author' });
configServiceMock.get
.mockResolvedValueOnce(JSON.stringify([{ id: 1, name: 'Indexer', protocol: 'torrent', priority: 10 }]))
.mockResolvedValueOnce(null);
groupIndexersMock.mockReturnValue({ groups: [{ categories: [3030], indexerIds: [1] }], skippedIndexers: [] });
const candidates = Array.from({ length: 101 }, (_, index) => ({
title: index === 100 ? 'Best result' : `Result ${index}`,
size: 100,
indexer: 'Indexer',
indexerId: 1,
}));
const rankedCandidates = [candidates[100], ...candidates.slice(0, 100)].map((result, index) => ({
...result,
score: 100 - index,
breakdown: { matchScore: 50, formatScore: 0, sizeScore: 0, seederScore: 0, notes: [] },
bonusPoints: 0,
bonusModifiers: [],
finalScore: 100 - index,
}));
prowlarrMock.searchWithVariations.mockResolvedValue(candidates);
rankTorrentsMock.mockReturnValue(rankedCandidates);
const { POST } = await import('@/app/api/audiobooks/search-torrents/route');
const response = await POST({} as any);
const payload = await response.json();
expect(rankTorrentsMock.mock.calls[0][0]).toHaveLength(101);
expect(prowlarrMock.searchWithVariations).toHaveBeenCalledWith('Title', 'Author', {
categories: [3030],
indexerIds: [1],
});
expect(payload.results).toHaveLength(100);
expect(payload.results[0]).toEqual(expect.objectContaining({ title: 'Best result', rank: 1 }));
expect(payload.truncated).toBe(true);
expect(payload.rawCount).toBe(101);
});
});
+45
View File
@@ -153,6 +153,51 @@ describe('Request action routes', () => {
);
});
it('ranks the complete interactive result set before returning the top 100', async () => {
authRequest.json.mockResolvedValue({});
prismaMock.request.findUnique.mockResolvedValueOnce({
id: 'req-complete-ranking',
userId: 'user-1',
audiobook: { title: 'Title', author: 'Author', audibleAsin: null },
});
prismaMock.user.findUnique.mockResolvedValueOnce({
role: 'user',
interactiveSearchAccess: null,
});
configServiceMock.get.mockResolvedValueOnce(JSON.stringify([{ id: 1, priority: 10, categories: [3030] }]));
configServiceMock.get.mockResolvedValueOnce(null);
groupIndexersMock.mockReturnValue({ groups: [{ categories: [3030], indexerIds: [1] }], skippedIndexers: [] });
const candidates = Array.from({ length: 101 }, (_, index) => ({
title: index === 100 ? 'Best result' : `Result ${index}`,
size: 100,
}));
const rankedCandidates = [candidates[100], ...candidates.slice(0, 100)].map((result, index) => ({
...result,
score: 100 - index,
breakdown: { matchScore: 50, formatScore: 0, sizeScore: 0, seederScore: 0, notes: [] },
bonusPoints: 0,
bonusModifiers: [],
finalScore: 100 - index,
}));
prowlarrMock.searchWithVariations.mockResolvedValueOnce(candidates);
rankTorrentsMock.mockReturnValueOnce(rankedCandidates);
const { POST } = await import('@/app/api/requests/[id]/interactive-search/route');
const response = await POST({} as any, { params: Promise.resolve({ id: 'req-complete-ranking' }) });
const payload = await response.json();
expect(rankTorrentsMock.mock.calls[0][0]).toHaveLength(101);
expect(prowlarrMock.searchWithVariations).toHaveBeenCalledWith('Title', 'Author', {
categories: [3030],
indexerIds: [1],
});
expect(payload.results).toHaveLength(100);
expect(payload.results[0]).toEqual(expect.objectContaining({ title: 'Best result', rank: 1 }));
expect(payload.truncated).toBe(true);
expect(payload.rawCount).toBe(101);
});
it('performs interactive search gracefully when runtime fetch fails', async () => {
authRequest.json.mockResolvedValue({});
prismaMock.request.findUnique.mockResolvedValueOnce({