mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Fix & test GET /videos
This commit is contained in:
parent
45e769828b
commit
e26bc931f5
@ -14,6 +14,7 @@ import { KError } from "~/models/error";
|
|||||||
import { bubbleVideo } from "~/models/examples";
|
import { bubbleVideo } from "~/models/examples";
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
|
type Resource,
|
||||||
Sort,
|
Sort,
|
||||||
createPage,
|
createPage,
|
||||||
isUuid,
|
isUuid,
|
||||||
@ -54,8 +55,9 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
slug: shows.slug,
|
slug: shows.slug,
|
||||||
})
|
})
|
||||||
.from(videos)
|
.from(videos)
|
||||||
.crossJoin(
|
.leftJoin(
|
||||||
sql`jsonb_array_elements_text(${videos.guess}->'year') as year`,
|
sql`jsonb_array_elements_text(${videos.guess}->'year') as year`,
|
||||||
|
sql`true`,
|
||||||
)
|
)
|
||||||
.innerJoin(entryVideoJoin, eq(entryVideoJoin.videoPk, videos.pk))
|
.innerJoin(entryVideoJoin, eq(entryVideoJoin.videoPk, videos.pk))
|
||||||
.innerJoin(entries, eq(entries.pk, entryVideoJoin.entryPk))
|
.innerJoin(entries, eq(entries.pk, entryVideoJoin.entryPk))
|
||||||
@ -78,7 +80,10 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
const [{ guesses }] = await db
|
const [{ guesses }] = await db
|
||||||
.with(years, guess)
|
.with(years, guess)
|
||||||
.select({
|
.select({
|
||||||
guesses: jsonbObjectAgg<Guesses["guesses"]>(guess.guess, guess.years),
|
guesses: jsonbObjectAgg<Record<string, Resource>>(
|
||||||
|
guess.guess,
|
||||||
|
guess.years,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.from(guess);
|
.from(guess);
|
||||||
|
|
||||||
@ -98,7 +103,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
paths: paths.map((x) => x.path),
|
paths: paths.map((x) => x.path),
|
||||||
guesses,
|
guesses: guesses ?? {},
|
||||||
unmatched: unmatched.map((x) => x.path),
|
unmatched: unmatched.map((x) => x.path),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -177,8 +182,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
path: videos.path,
|
path: videos.path,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!isUniqueConstraint(e))
|
if (!isUniqueConstraint(e)) throw e;
|
||||||
throw e;
|
|
||||||
return error(409, {
|
return error(409, {
|
||||||
status: 409,
|
status: 409,
|
||||||
message: comment`
|
message: comment`
|
||||||
|
@ -144,5 +144,7 @@ export const jsonbBuildObject = <T>(select: JsonFields) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isUniqueConstraint = (e: unknown): boolean => {
|
export const isUniqueConstraint = (e: unknown): boolean => {
|
||||||
return typeof e === "object" && e != null && "code" in e && e.code === "23505";
|
return (
|
||||||
|
typeof e === "object" && e != null && "code" in e && e.code === "23505"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ export const Resource = () =>
|
|||||||
id: t.String({ format: "uuid" }),
|
id: t.String({ format: "uuid" }),
|
||||||
slug: t.String({ format: "slug" }),
|
slug: t.String({ format: "slug" }),
|
||||||
});
|
});
|
||||||
|
export type Resource = ReturnType<typeof Resource>["static"];
|
||||||
|
|
||||||
const checker = TypeCompiler.Compile(t.String({ format: "uuid" }));
|
const checker = TypeCompiler.Compile(t.String({ format: "uuid" }));
|
||||||
export const isUuid = (id: string) => checker.Check(id);
|
export const isUuid = (id: string) => checker.Check(id);
|
||||||
|
@ -167,10 +167,7 @@ export const Guesses = t.Object({
|
|||||||
paths: t.Array(t.String()),
|
paths: t.Array(t.String()),
|
||||||
guesses: t.Record(
|
guesses: t.Record(
|
||||||
t.String(),
|
t.String(),
|
||||||
t.Record(
|
t.Record(t.String({ pattern: "^([1-9][0-9]{3})|unknown$" }), Resource()),
|
||||||
t.Union([t.Literal("unknown"), t.String({ pattern: "[1-9][0-9]*" })]),
|
|
||||||
Resource(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
unmatched: t.Array(t.String()),
|
unmatched: t.Array(t.String()),
|
||||||
});
|
});
|
||||||
@ -188,7 +185,7 @@ registerExamples(Guesses, {
|
|||||||
id: "43b742f5-9ce6-467d-ad29-74460624020a",
|
id: "43b742f5-9ce6-467d-ad29-74460624020a",
|
||||||
slug: "evangelion",
|
slug: "evangelion",
|
||||||
},
|
},
|
||||||
1995: {
|
"1995": {
|
||||||
id: "43b742f5-9ce6-467d-ad29-74460624020a",
|
id: "43b742f5-9ce6-467d-ad29-74460624020a",
|
||||||
slug: "evangelion",
|
slug: "evangelion",
|
||||||
},
|
},
|
||||||
|
@ -17,3 +17,29 @@ export const createVideo = async (video: SeedVideo | SeedVideo[]) => {
|
|||||||
const body = await resp.json();
|
const body = await resp.json();
|
||||||
return [resp, body] as const;
|
return [resp, body] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getVideos = async () => {
|
||||||
|
const resp = await app.handle(
|
||||||
|
new Request(buildUrl("videos"), {
|
||||||
|
method: "GET",
|
||||||
|
headers: await getJwtHeaders(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const body = await resp.json();
|
||||||
|
return [resp, body] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteVideo = async (paths: string[]) => {
|
||||||
|
const resp = await app.handle(
|
||||||
|
new Request(buildUrl("videos"), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: JSON.stringify(paths),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(await getJwtHeaders()),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const body = await resp.json();
|
||||||
|
return [resp, body] as const;
|
||||||
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { db, migrate } from "~/db";
|
import { db, migrate } from "~/db";
|
||||||
import { profiles, shows } from "~/db/schema";
|
import { profiles, shows } from "~/db/schema";
|
||||||
import { madeInAbyss } from "~/models/examples";
|
import { bubble, madeInAbyss } from "~/models/examples";
|
||||||
import { createSerie, createVideo } from "./helpers";
|
import { createMovie, createSerie, createVideo, getVideos } from "./helpers";
|
||||||
|
|
||||||
// test file used to run manually using `bun tests/manual.ts`
|
// test file used to run manually using `bun tests/manual.ts`
|
||||||
// run those before running this script
|
// run those before running this script
|
||||||
@ -12,22 +12,42 @@ await migrate();
|
|||||||
await db.delete(shows);
|
await db.delete(shows);
|
||||||
await db.delete(profiles);
|
await db.delete(profiles);
|
||||||
|
|
||||||
const [__, ser] = await createSerie(madeInAbyss);
|
const [_, ser] = await createSerie(madeInAbyss);
|
||||||
console.log(ser);
|
const [__, mov] = await createMovie(bubble);
|
||||||
const [_, body] = await createVideo({
|
const [resp, body] = await createVideo([
|
||||||
guess: { title: "mia", season: [1], episode: [13], from: "test" },
|
{
|
||||||
part: null,
|
guess: { title: "mia", season: [1], episode: [13], from: "test" },
|
||||||
path: "/video/mia s1e13.mkv",
|
part: null,
|
||||||
rendering: "renderingsha",
|
path: "/video/mia s1e13.mkv",
|
||||||
version: 1,
|
rendering: "sha2",
|
||||||
for: [
|
version: 1,
|
||||||
{
|
for: [{ slug: `${madeInAbyss.slug}-s1e13` }],
|
||||||
serie: madeInAbyss.slug,
|
},
|
||||||
season: madeInAbyss.entries[0].seasonNumber!,
|
{
|
||||||
episode: madeInAbyss.entries[0].episodeNumber!,
|
guess: {
|
||||||
|
title: "mia",
|
||||||
|
season: [2],
|
||||||
|
episode: [1],
|
||||||
|
year: [2017],
|
||||||
|
from: "test",
|
||||||
},
|
},
|
||||||
],
|
part: null,
|
||||||
});
|
path: "/video/mia 2017 s2e1.mkv",
|
||||||
|
rendering: "sha8",
|
||||||
|
version: 1,
|
||||||
|
for: [{ slug: `${madeInAbyss.slug}-s2e1` }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
guess: { title: "bubble", from: "test" },
|
||||||
|
part: null,
|
||||||
|
path: "/video/bubble.mkv",
|
||||||
|
rendering: "sha5",
|
||||||
|
version: 1,
|
||||||
|
for: [{ movie: bubble.slug }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
console.log(body);
|
console.log(body);
|
||||||
|
const [___, ret] = await getVideos();
|
||||||
|
console.log(JSON.stringify(ret, undefined, 4));
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
154
api/tests/videos/getdel.test.ts
Normal file
154
api/tests/videos/getdel.test.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { beforeAll, describe, expect, it } from "bun:test";
|
||||||
|
import {
|
||||||
|
createMovie,
|
||||||
|
createSerie,
|
||||||
|
createVideo,
|
||||||
|
getVideos,
|
||||||
|
} from "tests/helpers";
|
||||||
|
import { expectStatus } from "tests/utils";
|
||||||
|
import { db } from "~/db";
|
||||||
|
import { shows, videos } from "~/db/schema";
|
||||||
|
import { bubble, madeInAbyss } from "~/models/examples";
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await db.delete(shows);
|
||||||
|
await db.delete(videos);
|
||||||
|
|
||||||
|
let [ret, body] = await createSerie(madeInAbyss);
|
||||||
|
expectStatus(ret, body).toBe(201);
|
||||||
|
[ret, body] = await createMovie(bubble);
|
||||||
|
expectStatus(ret, body).toBe(201);
|
||||||
|
|
||||||
|
[ret, body] = await createVideo([
|
||||||
|
{
|
||||||
|
guess: { title: "mia", season: [1], episode: [13], from: "test" },
|
||||||
|
part: null,
|
||||||
|
path: "/video/mia s1e13.mkv",
|
||||||
|
rendering: "sha2",
|
||||||
|
version: 1,
|
||||||
|
for: [{ slug: `${madeInAbyss.slug}-s1e13` }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
guess: {
|
||||||
|
title: "mia",
|
||||||
|
season: [2],
|
||||||
|
episode: [1],
|
||||||
|
year: [2017],
|
||||||
|
from: "test",
|
||||||
|
},
|
||||||
|
part: null,
|
||||||
|
path: "/video/mia 2017 s2e1.mkv",
|
||||||
|
rendering: "sha8",
|
||||||
|
version: 1,
|
||||||
|
for: [{ slug: `${madeInAbyss.slug}-s2e1` }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
guess: { title: "bubble", from: "test" },
|
||||||
|
part: null,
|
||||||
|
path: "/video/bubble.mkv",
|
||||||
|
rendering: "sha5",
|
||||||
|
version: 1,
|
||||||
|
for: [{ movie: bubble.slug }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expectStatus(ret, body).toBe(201);
|
||||||
|
expect(body).toBeArrayOfSize(3);
|
||||||
|
expect(body[0].entries).toBeArrayOfSize(1);
|
||||||
|
expect(body[1].entries).toBeArrayOfSize(1);
|
||||||
|
expect(body[2].entries).toBeArrayOfSize(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Video get/deletion", () => {
|
||||||
|
it("Get current state", async () => {
|
||||||
|
const [resp, body] = await getVideos();
|
||||||
|
expectStatus(resp, body).toBe(200);
|
||||||
|
expect(body.guesses).toMatchObject({
|
||||||
|
mia: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "made-in-abyss",
|
||||||
|
},
|
||||||
|
"2017": {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "made-in-abyss",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bubble: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "bubble",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("With unknown", async () => {
|
||||||
|
let [resp, body] = await createVideo({
|
||||||
|
guess: { title: "mia", season: [1], episode: [13], from: "test" },
|
||||||
|
part: null,
|
||||||
|
path: "/video/mia s1e13 unknown test.mkv",
|
||||||
|
rendering: "shanthnth",
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
expectStatus(resp, body).toBe(201);
|
||||||
|
|
||||||
|
[resp, body] = await getVideos();
|
||||||
|
expectStatus(resp, body).toBe(200);
|
||||||
|
expect(body.guesses).toMatchObject({
|
||||||
|
mia: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "made-in-abyss",
|
||||||
|
},
|
||||||
|
"2017": {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "made-in-abyss",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bubble: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "bubble",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(body.unmatched).toBeArrayOfSize(1);
|
||||||
|
expect(body.unmatched[0]).toBe("/video/mia s1e13 unknown test.mkv");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Mismatch title guess", async () => {
|
||||||
|
let [resp, body] = await createVideo({
|
||||||
|
guess: { title: "mia", season: [1], episode: [13], from: "test" },
|
||||||
|
part: null,
|
||||||
|
path: "/video/mia s1e13 mismatch.mkv",
|
||||||
|
rendering: "mismatch",
|
||||||
|
version: 1,
|
||||||
|
for: [{ movie: "bubble" }],
|
||||||
|
});
|
||||||
|
expectStatus(resp, body).toBe(201);
|
||||||
|
|
||||||
|
[resp, body] = await getVideos();
|
||||||
|
expectStatus(resp, body).toBe(200);
|
||||||
|
expect(body.guesses).toMatchObject({
|
||||||
|
mia: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
// take the latest slug
|
||||||
|
slug: "bubble",
|
||||||
|
},
|
||||||
|
"2017": {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "made-in-abyss",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bubble: {
|
||||||
|
unknown: {
|
||||||
|
id: expect.any(String),
|
||||||
|
slug: "bubble",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.todo("Delete video", async () => {});
|
||||||
|
});
|
@ -8,7 +8,6 @@ import { bubble, madeInAbyss } from "~/models/examples";
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await db.delete(shows);
|
await db.delete(shows);
|
||||||
await db.delete(entries);
|
|
||||||
await db.delete(videos);
|
await db.delete(videos);
|
||||||
let [ret, body] = await createSerie(madeInAbyss);
|
let [ret, body] = await createSerie(madeInAbyss);
|
||||||
expectStatus(ret, body).toBe(201);
|
expectStatus(ret, body).toBe(201);
|
||||||
@ -358,7 +357,6 @@ describe("Video seeding", () => {
|
|||||||
expect(body.message).toBeString();
|
expect(body.message).toBeString();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("Two for the same entry", async () => {
|
it("Two for the same entry", async () => {
|
||||||
const [resp, body] = await createVideo({
|
const [resp, body] = await createVideo({
|
||||||
guess: {
|
guess: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user