Kyoo/api/tests/movies/get-all-movies.test.ts
2025-06-07 17:45:37 +02:00

329 lines
8.3 KiB
TypeScript

import { beforeAll, describe, expect, it } from "bun:test";
import { getJwtHeaders } from "tests/helpers/jwt";
import { expectStatus } from "tests/utils";
import { db } from "~/db";
import { shows } from "~/db/schema";
import { bubble } from "~/models/examples";
import { dune1984 } from "~/models/examples/dune-1984";
import { dune } from "~/models/examples/dune-2021";
import type { Movie } from "~/models/movie";
import { createMovie, getMovies, handlers } from "../helpers";
beforeAll(async () => {
await db.delete(shows);
for (const movie of [bubble, dune1984, dune]) {
const [ret, _] = await createMovie(movie);
expect(ret.status).toBe(201);
}
});
describe("Get all movies", () => {
it("Invalid filter params", async () => {
const [resp, body] = await getMovies({
filter: `slug eq ${bubble.slug}`,
langs: "en",
});
expectStatus(resp, body).toBe(422);
expect(body).toMatchObject({
status: 422,
message: expect.any(String),
details: {
in: "slug eq bubble",
},
});
});
it("Invalid filter syntax", async () => {
const [resp, body] = await getMovies({
filter: `slug eq gt ${bubble.slug}`,
langs: "en",
});
expectStatus(resp, body).toBe(422);
expect(body).toMatchObject({
details: expect.anything(),
message: "Invalid filter: slug eq gt bubble.",
status: 422,
});
});
it("Limit 2, default sort", async () => {
const [resp, body] = await getMovies({
limit: 2,
langs: "en",
});
expectStatus(resp, body).toBe(200);
expect(body).toMatchObject({
items: [
expect.objectContaining({ slug: bubble.slug }),
expect.objectContaining({ slug: dune.slug }),
],
this: "http://localhost/movies?limit=2",
// we can't have the exact after since it contains the pk that changes with every tests.
next: expect.stringContaining(
"http://localhost/movies?limit=2&after=WyJkdW5lIiw",
),
});
});
it("Limit 2, default sort, page 2", async () => {
let [resp, body] = await getMovies({
limit: 2,
langs: "en",
});
expectStatus(resp, body).toBe(200);
resp = await handlers.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
body = await resp.json();
expectStatus(resp, body).toBe(200);
expect(body).toMatchObject({
items: [expect.objectContaining({ slug: dune1984.slug })],
this: expect.stringContaining(
"http://localhost/movies?limit=2&after=WyJkdW5lIiw",
),
next: null,
});
});
it("Limit 2, sort by dates desc, page 2", async () => {
let [resp, body] = await getMovies({
limit: 2,
sort: "-airDate",
langs: "en",
});
expectStatus(resp, body).toBe(200);
// we copy this due to https://github.com/oven-sh/bun/issues/3521
const next = body.next;
expect(body).toMatchObject({
items: [
expect.objectContaining({ slug: bubble.slug, airDate: bubble.airDate }),
expect.objectContaining({ slug: dune.slug, airDate: dune.airDate }),
],
this: "http://localhost/movies?limit=2&sort=-airDate",
next: expect.stringContaining(
"http://localhost/movies?limit=2&sort=-airDate&after=WyIyMDIxLTEwLTIyIiw",
),
});
resp = await handlers.handle(
new Request(next, { headers: await getJwtHeaders() }),
);
body = await resp.json();
expectStatus(resp, body).toBe(200);
expect(body).toMatchObject({
items: [
expect.objectContaining({
slug: dune1984.slug,
airDate: dune1984.airDate,
}),
],
this: next,
next: null,
});
});
describe("Random sort", () => {
it("No limit, compare order with same seeds", async () => {
// First query
const [resp1, body1] = await getMovies({
sort: "random:100",
});
expectStatus(resp1, body1).toBe(200);
const items1: Movie[] = body1.items;
const items1Ids = items1.map(({ id }) => id);
// Second query
const [resp2, body2] = await getMovies({
sort: "random:100",
});
expectStatus(resp2, body2).toBe(200);
const items2: Movie[] = body2.items;
const items2Ids = items2.map(({ id }) => id);
expect(items1Ids).toEqual(items2Ids);
});
it("Limit 1, pages 1 and 2 ", async () => {
// First query fetches all
// use the result to know what is expected
let [resp, body] = await getMovies({
sort: "random:1234",
});
expectStatus(resp, body).toBe(200);
let items: Movie[] = body.items;
const expectedIds = items.map(({ id }) => id);
// Get First Page
[resp, body] = await getMovies({
sort: "random:1234",
limit: 1,
});
expectStatus(resp, body).toBe(200);
items = body.items;
expect(items.length).toBe(1);
expect(items[0].id).toBe(expectedIds[0]);
// Get Second Page
resp = await handlers.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
body = await resp.json();
expectStatus(resp, body).toBe(200);
items = body.items;
expect(items.length).toBe(1);
expect(items[0].id).toBe(expectedIds[1]);
});
it("Limit 1, pages 1 and 2, no seed ", async () => {
const [resp, body] = await getMovies({
sort: "random",
limit: 2,
});
expectStatus(resp, body).toBe(200);
const resp2 = await handlers.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
const body2 = await resp2.json();
expectStatus(resp2, body).toBe(200);
expect(body2.items.length).toBe(1);
expect(body.items.map((x: Movie) => x.slug)).not.toContain(
body2.items[0].slug,
);
});
it("Get /random", async () => {
const resp = await handlers.handle(
new Request("http://localhost/movies/random", {
headers: await getJwtHeaders(),
}),
);
expect(resp.status).toBe(302);
const location = resp.headers.get("location")!;
expect(location).toStartWith("/movies/");
});
});
it("Limit 2, fallback lang, prefer original", async () => {
const [resp, body] = await getMovies({
limit: 2,
langs: "en-au",
preferOriginal: true,
});
expectStatus(resp, body).toBe(200);
expect(body.items[0]).toMatchObject({
slug: bubble.slug,
name: bubble.translations.en.name,
poster: {
source: bubble.translations.ja.poster,
},
thumbnail: {
source: bubble.translations.ja.thumbnail,
},
banner: null,
// we fallback to the translated value when the original is null.
logo: { source: bubble.translations.en.logo },
});
expect(body.items[1]).toMatchObject({
slug: dune.slug,
name: dune.translations.en.name,
});
});
it("Limit 2, * lang, prefer original", async () => {
const [resp, body] = await getMovies({
limit: 2,
langs: "*",
preferOriginal: true,
});
expectStatus(resp, body).toBe(200);
expect(body.items[0]).toMatchObject({
slug: bubble.slug,
name: expect.any(String),
poster: {
source: expect.any(String),
},
thumbnail: {
source: expect.any(String),
},
banner: null,
});
expect(body.items[1]).toMatchObject({
slug: dune.slug,
name: dune.translations.en.name,
});
});
it("Limit 2, unknown lang, prefer original", async () => {
const [resp, body] = await getMovies({
limit: 2,
langs: "toto",
preferOriginal: true,
});
expectStatus(resp, body).toBe(200);
expect(body.items[0]).toMatchObject({
slug: bubble.slug,
name: expect.any(String),
poster: {
source: expect.any(String),
},
thumbnail: {
source: expect.any(String),
},
banner: null,
});
expect(body.items[1]).toMatchObject({
slug: dune.slug,
name: dune.translations.en.name,
});
});
it("Filter with tags", async () => {
const [resp, body] = await getMovies({
limit: 2,
filter: "tags eq gravity",
langs: "en",
});
expectStatus(resp, body).toBe(200);
expect(body.items).toBeArrayOfSize(1);
expect(body.items[0].slug).toBe(bubble.slug);
});
});
describe("search", () => {
it("Partial match", async () => {
const [resp, body] = await getMovies({
limit: 2,
query: "bub",
langs: "en",
});
expectStatus(resp, body).toBe(200);
expect(body.items).toBeArrayOfSize(1);
expect(body.items[0].slug).toBe(bubble.slug);
});
it("Invalid search don't match", async () => {
const [resp, body] = await getMovies({
limit: 2,
query: "buboeuoeunhoeu",
langs: "en",
});
expectStatus(resp, body).toBe(200);
expect(body.items).toBeArrayOfSize(0);
});
it("Typo match", async () => {
const [resp, body] = await getMovies({
limit: 2,
query: "bobble",
langs: "en",
});
expectStatus(resp, body).toBe(200);
expect(body.items).toBeArrayOfSize(1);
expect(body.items[0].slug).toBe(bubble.slug);
});
});