mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-20 00:15:27 -07:00
feat(stats): add library entry merge and episode move (#190)
This commit is contained in:
@@ -44,6 +44,45 @@ test('getAnimeCoverUrl appends retry tokens for late cover refreshes', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getAnimeMergeRecommendations loads pending duplicate pairs', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let seenUrl = '';
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
seenUrl = String(input);
|
||||
return new Response(
|
||||
JSON.stringify({ recommendations: [{ recommendationId: 4, animeIds: [7, 8] }] }),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
}) as typeof globalThis.fetch;
|
||||
|
||||
try {
|
||||
const result = await apiClient.getAnimeMergeRecommendations();
|
||||
assert.equal(seenUrl, `${BASE_URL}/api/stats/anime/merge-recommendations`);
|
||||
assert.deepEqual(result.recommendations, [{ recommendationId: 4, animeIds: [7, 8] }]);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('dismissAnimeMergeRecommendation sends DELETE for the selected suggestion', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let seenUrl = '';
|
||||
let seenMethod = '';
|
||||
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
seenUrl = String(input);
|
||||
seenMethod = init?.method ?? 'GET';
|
||||
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
||||
}) as typeof globalThis.fetch;
|
||||
|
||||
try {
|
||||
await apiClient.dismissAnimeMergeRecommendation(4);
|
||||
assert.equal(seenUrl, `${BASE_URL}/api/stats/anime/merge-recommendations/4`);
|
||||
assert.equal(seenMethod, 'DELETE');
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('getCoverImages batches anime and media cover requests', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let seenUrl = '';
|
||||
|
||||
@@ -11,6 +11,10 @@ import type {
|
||||
StatsExcludedWordsRequest,
|
||||
StatsHttpClient,
|
||||
StatsJsonResponseMap,
|
||||
StatsMergeAnimeRequest,
|
||||
StatsMergeAnimeResponse,
|
||||
StatsMoveVideoRequest,
|
||||
StatsMoveVideoResponse,
|
||||
StatsTrendGroupBy,
|
||||
StatsTrendRange,
|
||||
StatsVideoWatchedRequest,
|
||||
@@ -140,6 +144,8 @@ export const apiClient = {
|
||||
getMediaLibrary: () => fetchJson('mediaLibrary', '/api/stats/media'),
|
||||
getMediaDetail: (videoId: number) => fetchJson('mediaDetail', `/api/stats/media/${videoId}`),
|
||||
getAnimeLibrary: () => fetchJson('animeLibrary', '/api/stats/anime'),
|
||||
getAnimeMergeRecommendations: () =>
|
||||
fetchJson('animeMergeRecommendations', '/api/stats/anime/merge-recommendations'),
|
||||
getAnimeDetail: (animeId: number) => fetchJson('animeDetail', `/api/stats/anime/${animeId}`),
|
||||
getAnimeWords: (animeId: number, limit = 50) =>
|
||||
fetchJson('animeWords', `/api/stats/anime/${animeId}/words?limit=${limit}`),
|
||||
@@ -209,6 +215,30 @@ export const apiClient = {
|
||||
fetchResponse(`/api/stats/anime/${animeId}`, { method: 'DELETE' }),
|
||||
);
|
||||
},
|
||||
mergeAnime: async (
|
||||
targetAnimeId: number,
|
||||
sourceAnimeIds: number[],
|
||||
): Promise<StatsMergeAnimeResponse> => {
|
||||
const res = await fetchResponse(`/api/stats/anime/${targetAnimeId}/merge`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sourceAnimeIds } satisfies StatsMergeAnimeRequest),
|
||||
});
|
||||
return res.json() as Promise<StatsMergeAnimeResponse>;
|
||||
},
|
||||
moveVideoToAnime: async (videoId: number, animeId: number): Promise<StatsMoveVideoResponse> => {
|
||||
const res = await fetchResponse(`/api/stats/media/${videoId}/anime`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ animeId } satisfies StatsMoveVideoRequest),
|
||||
});
|
||||
return res.json() as Promise<StatsMoveVideoResponse>;
|
||||
},
|
||||
dismissAnimeMergeRecommendation: async (recommendationId: number): Promise<void> => {
|
||||
await fetchResponse(`/api/stats/anime/merge-recommendations/${recommendationId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
},
|
||||
getKnownWords: () => fetchJson('knownWords', '/api/stats/known-words'),
|
||||
getKnownWordsSummary: () => fetchJson('knownWordsSummary', '/api/stats/known-words-summary'),
|
||||
getAnimeKnownWordsSummary: (animeId: number) =>
|
||||
|
||||
Reference in New Issue
Block a user