diff --git a/api/src/controllers/profiles/history.ts b/api/src/controllers/profiles/history.ts index 441a131f..68379fde 100644 --- a/api/src/controllers/profiles/history.ts +++ b/api/src/controllers/profiles/history.ts @@ -206,6 +206,7 @@ export const historyH = new Elysia({ tags: ["profiles"] }) .where( and( eq(nextEntry.showPk, entries.showPk), + ne(entries.kind, "extra"), gt(nextEntry.order, entries.order), ), ) diff --git a/api/tests/helpers/shows-helper.ts b/api/tests/helpers/shows-helper.ts index f1a138dd..80073a54 100644 --- a/api/tests/helpers/shows-helper.ts +++ b/api/tests/helpers/shows-helper.ts @@ -58,3 +58,33 @@ export const getWatchlist = async ( const body = await resp.json(); return [resp, body] as const; }; + +export const getNextup = async ( + id: string, + { + langs, + ...query + }: { + filter?: string; + limit?: number; + after?: string; + sort?: string | string[]; + query?: string; + langs?: string; + preferOriginal?: boolean; + }, +) => { + const resp = await app.handle( + new Request(buildUrl(`profiles/${id}/nextup`, query), { + method: "GET", + headers: langs + ? { + "Accept-Language": langs, + ...(await getJwtHeaders()), + } + : await getJwtHeaders(), + }), + ); + const body = await resp.json(); + return [resp, body] as const; +}; diff --git a/api/tests/series/nextup.test.ts b/api/tests/series/nextup.test.ts new file mode 100644 index 00000000..01613775 --- /dev/null +++ b/api/tests/series/nextup.test.ts @@ -0,0 +1,173 @@ +import { beforeAll, describe, expect, it } from "bun:test"; +import { + addToHistory, + createMovie, + createSerie, + getEntries, + getHistory, + getMovie, + getNews, + getNextup, + getSerie, + getWatchlist, + setMovieStatus, + setSerieStatus, +} from "tests/helpers"; +import { expectStatus } from "tests/utils"; +import { db } from "~/db"; +import { entries, shows, videos } from "~/db/schema"; +import { bubble, madeInAbyss, madeInAbyssVideo } from "~/models/examples"; + +beforeAll(async () => { + await db.delete(shows); + await db.delete(entries); + await db.delete(videos); + // create video beforehand to test linking + await db.insert(videos).values(madeInAbyssVideo); + let [ret, body] = await createSerie(madeInAbyss); + expectStatus(ret, body).toBe(201); + [ret, body] = await createMovie(bubble); + expectStatus(ret, body).toBe(201); +}); + +const miaEntrySlug = `${madeInAbyss.slug}-s1e13`; +const miaNextEntrySlug = `${madeInAbyss.slug}-sp3`; + +describe("nextup", () => { + it("Watchlist populates nextup", async () => { + let [r, b] = await setMovieStatus(bubble.slug, { + status: "watching", + completedAt: null, + score: null, + }); + expectStatus(r, b).toBe(200); + [r, b] = await setSerieStatus(madeInAbyss.slug, { + status: "watching", + startedAt: "2024-12-22", + completedAt: null, + score: null, + }); + expectStatus(r, b).toBe(200); + + // only edit score, shouldn't change order + [r, b] = await setMovieStatus(bubble.slug, { + status: "watching", + completedAt: null, + score: 90, + }); + expectStatus(r, b).toBe(200); + + [r, b] = await getWatchlist("me", {}); + expectStatus(r, b).toBe(200); + expect(b.items).toBeArrayOfSize(2); + + const [resp, body] = await getNextup("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(2); + expect(body.items[0].slug).toBe(miaEntrySlug); + expect(body.items[0].progress).toMatchObject({ + percent: 0, + }); + expect(body.items[1].slug).toBe(bubble.slug); + expect(body.items[1].progress).toMatchObject({ + percent: 0, + }); + }); + + it("/series/:id?with=nextEntry", async () => { + const [resp, body] = await getSerie(madeInAbyss.slug, { + with: ["nextEntry"], + }); + expectStatus(resp, body).toBe(200); + expect(body.nextEntry).toBeObject(); + expect(body.nextEntry.slug).toBe(miaEntrySlug); + expect(body.nextEntry.progress).toMatchObject({ + percent: 0, + }); + }); + + it("history watching doesn't update", async () => { + let [resp, body] = await addToHistory("me", [ + { + entry: miaEntrySlug, + videoId: madeInAbyssVideo.id, + percent: 58, + time: 28 * 60 + 12, + playedDate: "2025-02-01", + }, + { + entry: bubble.slug, + videoId: null, + percent: 100, + time: 2 * 60, + playedDate: "2025-02-02", + }, + ]); + expectStatus(resp, body).toBe(201); + expect(body.inserted).toBe(2); + + [resp, body] = await getSerie(madeInAbyss.slug, { + with: ["nextEntry"], + }); + expectStatus(resp, body).toBe(200); + expect(body.nextEntry).toBeObject(); + expect(body.nextEntry.slug).toBe(miaEntrySlug); + expect(body.nextEntry.progress).toMatchObject({ + percent: 58, + time: 28 * 60 + 12, + videoId: madeInAbyssVideo.id, + playedDate: "2025-02-01T00:00:00+00:00", + }); + + [resp, body] = await getMovie(bubble.slug, {}); + expectStatus(resp, body).toBe(200); + expect(body.watchStatus).toMatchObject({ + percent: 100, + status: "completed", + completedAt: "2025-02-02 00:00:00+00", + }); + + [resp, body] = await getNextup("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(1); + expect(body.items[0].slug).toBe(miaEntrySlug); + expect(body.items[0].progress).toMatchObject({ + percent: 58, + time: 28 * 60 + 12, + videoId: madeInAbyssVideo.id, + playedDate: "2025-02-01 00:00:00+00", + }); + }); + + it("history completed picks next", async () => { + let [resp, body] = await addToHistory("me", [ + { + entry: miaEntrySlug, + videoId: madeInAbyssVideo.id, + percent: 98, + time: 28 * 60 + 12, + playedDate: "2025-02-05", + }, + ]); + expectStatus(resp, body).toBe(201); + expect(body.inserted).toBe(1); + + [resp, body] = await getSerie(madeInAbyss.slug, { + with: ["nextEntry"], + }); + expectStatus(resp, body).toBe(200); + expect(body.nextEntry).toBeObject(); + expect(body.nextEntry.slug).toBe(miaNextEntrySlug); + expect(body.nextEntry.progress).toMatchObject({ + percent: 0, + time: 0, + videoId: null, + playedDate: null, + }); + + [resp, body] = await getNextup("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(1); + expect(body.items[0].slug).toBe(miaNextEntrySlug); + }); +});