diff --git a/api/src/controllers/profiles/history.ts b/api/src/controllers/profiles/history.ts index 36dfe225..4247d7fb 100644 --- a/api/src/controllers/profiles/history.ts +++ b/api/src/controllers/profiles/history.ts @@ -145,7 +145,7 @@ export const historyH = new Elysia({ tags: ["profiles"] }) ), ) .post( - "/profile/me/history", + "/profiles/me/history", async ({ body, jwt: { sub } }) => { const profilePk = await getOrCreateProfile(sub); diff --git a/api/tests/helpers/series-helper.ts b/api/tests/helpers/series-helper.ts index ac856226..f367542b 100644 --- a/api/tests/helpers/series-helper.ts +++ b/api/tests/helpers/series-helper.ts @@ -1,5 +1,6 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; +import type { SeedHistory } from "~/models/history"; import type { SeedSerie } from "~/models/serie"; import type { SerieWatchStatus } from "~/models/watchlist"; import { getJwtHeaders } from "./jwt"; @@ -197,3 +198,47 @@ export const setSerieStatus = async (id: string, status: SerieWatchStatus) => { const body = await resp.json(); return [resp, body] as const; }; + +export const getHistory = async ( + profile: string, + { + langs, + ...opts + }: { + filter?: string; + limit?: number; + after?: string; + query?: string; + langs?: string; + preferOriginal?: boolean; + }, +) => { + const resp = await app.handle( + new Request(buildUrl(`profiles/${profile}/history`, opts), { + method: "GET", + headers: langs + ? { + "Accept-Language": langs, + ...(await getJwtHeaders()), + } + : await getJwtHeaders(), + }), + ); + const body = await resp.json(); + return [resp, body] as const; +}; + +export const addToHistory = async (profile: string, seed: SeedHistory[]) => { + const resp = await app.handle( + new Request(buildUrl(`profiles/${profile}/history`), { + method: "POST", + body: JSON.stringify(seed), + headers: { + "Content-Type": "application/json", + ...(await getJwtHeaders()), + }, + }), + ); + const body = await resp.json(); + return [resp, body] as const; +}; diff --git a/api/tests/series/history.test.ts b/api/tests/series/history.test.ts new file mode 100644 index 00000000..6d33d97c --- /dev/null +++ b/api/tests/series/history.test.ts @@ -0,0 +1,153 @@ +import { beforeAll, describe, expect, it } from "bun:test"; +import { + addToHistory, + createMovie, + createSerie, + getEntries, + getHistory, + getMovie, + getNews, + getShows, + getWatchlist, + setMovieStatus, +} 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`; + +describe("Set & get history", () => { + it("Add episodes & movie to history", async () => { + let [resp, body] = await getHistory("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(0); + + const [r, b] = 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(r, b).toBe(201); + expect(b.inserted).toBe(2); + + [resp, body] = await getHistory("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(2); + expect(body.items[0].slug).toBe(bubble.slug); + expect(body.items[0].watchStatus).toMatchObject({ + percent: 100, + time: 2 * 60, + }); + expect(body.items[1].slug).toBe(miaEntrySlug); + expect(body.items[1].watchStatus).toMatchObject({ + percent: 58, + videoId: madeInAbyssVideo.id, + }); + }); + + it("Create duplicated history entry", async () => { + const [r, b] = await addToHistory("me", [ + { + entry: miaEntrySlug!, + videoId: madeInAbyssVideo.id, + percent: 100, + time: 38 * 60, + playedDate: "2025-02-03", + }, + ]); + expectStatus(r, b).toBe(201); + expect(b.inserted).toBe(1); + + const [resp, body] = await getHistory("me", {}); + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(3); + expect(body.items[0].slug).toBe(miaEntrySlug); + expect(body.items[0].watchStatus).toMatchObject({ + percent: 100, + videoId: madeInAbyssVideo.id, + }); + expect(body.items[1].slug).toBe(bubble.slug); + expect(body.items[1].watchStatus).toMatchObject({ + percent: 100, + time: 2 * 60, + }); + expect(body.items[2].slug).toBe(miaEntrySlug); + expect(body.items[2].watchStatus).toMatchObject({ + percent: 58, + videoId: madeInAbyssVideo.id, + }); + }); + + it("Return progress in /shows/:id/entries", async () => { + const [resp, body] = await getEntries(madeInAbyss.slug, { langs: "en" }); + + expectStatus(resp, body).toBe(200); + expect(body.items).toBeArrayOfSize(madeInAbyss.entries.length); + expect(body.items[0].progress).toMatchObject({ + percent: 100, + time: 38 * 60, + videoId: madeInAbyssVideo.id, + playedDate: "2025-02-03 00:00:00", + }); + }); + + it("Return progress in /news", async () => { + const [resp, body] = await getNews({ langs: "en" }); + + expectStatus(resp, body).toBe(200); + const entry = body.items.findFirst((x: any) => x.slug === miaEntrySlug); + expect(entry.progress).toMatchObject({ + percent: 100, + time: 38 * 60, + videoId: madeInAbyssVideo.id, + playedDate: "2025-02-03 00:00:00", + }); + }); + + // extras, unknowns + + // it("Update watchlist", async () => { + // const [r, b] = await setMovieStatus(bubble.slug, { + // status: "rewatching", + // // we still need to specify all values + // completedAt: "2024-12-21", + // score: 85, + // }); + // expectStatus(r, b).toBe(200); + // + // const [resp, body] = await getMovie(bubble.slug, {}); + // expectStatus(resp, body).toBe(200); + // expect(body.slug).toBe(bubble.slug); + // expect(body.watchStatus).toMatchObject({ + // status: "rewatching", + // completedAt: "2024-12-21 00:00:00+00", + // score: 85, + // percent: 0, + // }); + // }); +});