Forge jwt for tests

This commit is contained in:
Zoe Roux 2025-04-06 14:16:49 +02:00
parent 49f700ca6e
commit 0aab4cd84c
No known key found for this signature in database
12 changed files with 95 additions and 31 deletions

View File

@ -36,5 +36,4 @@ jobs:
working-directory: ./api working-directory: ./api
run: bun test run: bun test
env: env:
JWT_SECRET: "TODO"
POSTGRES_SERVER: localhost POSTGRES_SERVER: localhost

View File

@ -14,13 +14,18 @@ const jwks = createRemoteJWKSet(
const Jwt = t.Object({ const Jwt = t.Object({
sub: t.String({ description: "User id" }), sub: t.String({ description: "User id" }),
username: t.String(),
sid: t.String({ description: "Session id" }), sid: t.String({ description: "Session id" }),
username: t.String(),
permissions: t.Array(t.String()), permissions: t.Array(t.String()),
}); });
const validator = TypeCompiler.Compile(Jwt); const validator = TypeCompiler.Compile(Jwt);
export const auth = new Elysia({ name: "auth" }) export const auth = new Elysia({ name: "auth" })
.guard({
headers: t.Object({
authorization: t.TemplateLiteral("Bearer ${string}"),
}),
})
.macro({ .macro({
permissions(perms: string[]) { permissions(perms: string[]) {
return { return {

View File

@ -11,7 +11,7 @@ import { showsH } from "./controllers/shows/shows";
import { staffH } from "./controllers/staff"; import { staffH } from "./controllers/staff";
import { studiosH } from "./controllers/studios"; import { studiosH } from "./controllers/studios";
import { videosH } from "./controllers/videos"; import { videosH } from "./controllers/videos";
import { KError } from "./models/error"; import type { KError } from "./models/error";
export const base = new Elysia({ name: "base" }) export const base = new Elysia({ name: "base" })
.onError(({ code, error }) => { .onError(({ code, error }) => {
@ -61,11 +61,12 @@ export const app = new Elysia({ prefix })
detail: { detail: {
security: [{ bearer: ["core.read"] }, { api: ["core.read"] }], security: [{ bearer: ["core.read"] }, { api: ["core.read"] }],
}, },
response: { // See https://github.com/elysiajs/elysia/issues/1158
401: { ...KError, description: "" }, // response: {
403: { ...KError, description: "" }, // 401: { ...KError, description: "" },
}, // 403: { ...KError, description: "" },
perms: ["core.read"], // },
permissions: ["core.read"],
}, },
(app) => (app) =>
app app
@ -84,11 +85,12 @@ export const app = new Elysia({ prefix })
detail: { detail: {
security: [{ bearer: ["core.write"] }, { api: ["core.write"] }], security: [{ bearer: ["core.write"] }, { api: ["core.write"] }],
}, },
response: { // See https://github.com/elysiajs/elysia/issues/1158
401: { ...KError, description: "" }, // response: {
403: { ...KError, description: "" }, // 401: { ...KError, description: "" },
}, // 403: { ...KError, description: "" },
perms: ["core.read"], // },
permissions: ["core.write"],
}, },
(app) => app.use(videosH).use(seed), (app) => app.use(videosH).use(seed),
); );

17
api/tests/helpers/jwt.ts Normal file
View File

@ -0,0 +1,17 @@
import { SignJWT } from "jose";
export async function getJwtHeaders() {
const jwt = await new SignJWT({
sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884",
sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5",
username: "test-username",
permissions: ["core.read", "core.write"],
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setIssuer(process.env.JWT_ISSUER!)
.setExpirationTime("2h")
.sign(new TextEncoder().encode(process.env.JWT_SECRET));
return { Authorization: `Bearer ${jwt}` };
}

View File

@ -1,6 +1,7 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import type { SeedMovie } from "~/models/movie"; import type { SeedMovie } from "~/models/movie";
import { getJwtHeaders } from "./jwt";
export const getMovie = async ( export const getMovie = async (
id: string, id: string,
@ -15,8 +16,9 @@ export const getMovie = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -41,8 +43,9 @@ export const getMovies = async ({
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -56,6 +59,7 @@ export const createMovie = async (movie: SeedMovie) => {
body: JSON.stringify(movie), body: JSON.stringify(movie),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
...(await getJwtHeaders()),
}, },
}), }),
); );

View File

@ -1,6 +1,7 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import type { SeedSerie } from "~/models/serie"; import type { SeedSerie } from "~/models/serie";
import { getJwtHeaders } from "./jwt";
export const createSerie = async (serie: SeedSerie) => { export const createSerie = async (serie: SeedSerie) => {
const resp = await app.handle( const resp = await app.handle(
@ -9,6 +10,7 @@ export const createSerie = async (serie: SeedSerie) => {
body: JSON.stringify(serie), body: JSON.stringify(serie),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
...(await getJwtHeaders()),
}, },
}), }),
); );
@ -29,8 +31,9 @@ export const getSerie = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -58,8 +61,9 @@ export const getSeasons = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -87,8 +91,9 @@ export const getEntries = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -108,6 +113,7 @@ export const getExtras = async (
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`series/${serie}/extras`, opts), { new Request(buildUrl(`series/${serie}/extras`, opts), {
method: "GET", method: "GET",
headers: await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -124,6 +130,7 @@ export const getUnknowns = async (opts: {
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl("unknowns", opts), { new Request(buildUrl("unknowns", opts), {
method: "GET", method: "GET",
headers: await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -147,8 +154,9 @@ export const getNews = async ({
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();

View File

@ -1,10 +1,12 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import { getJwtHeaders } from "./jwt";
export const getStaff = async (id: string, query: {}) => { export const getStaff = async (id: string, query: {}) => {
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`staff/${id}`, query), { new Request(buildUrl(`staff/${id}`, query), {
method: "GET", method: "GET",
headers: await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -32,8 +34,9 @@ export const getStaffRoles = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...(await getJwtHeaders()),
} }
: {}, : await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -52,6 +55,7 @@ export const getSerieStaff = async (
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`series/${serie}/staff`, opts), { new Request(buildUrl(`series/${serie}/staff`, opts), {
method: "GET", method: "GET",
headers: await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -70,6 +74,7 @@ export const getMovieStaff = async (
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`movies/${movie}/staff`, opts), { new Request(buildUrl(`movies/${movie}/staff`, opts), {
method: "GET", method: "GET",
headers: await getJwtHeaders(),
}), }),
); );
const body = await resp.json(); const body = await resp.json();

View File

@ -1,5 +1,6 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import { getJwtHeaders } from "./jwt";
export const getStudio = async ( export const getStudio = async (
id: string, id: string,
@ -11,8 +12,9 @@ export const getStudio = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...await getJwtHeaders()
} }
: {}, : await getJwtHeaders()
}), }),
); );
const body = await resp.json(); const body = await resp.json();
@ -40,8 +42,9 @@ export const getShowsByStudio = async (
headers: langs headers: langs
? { ? {
"Accept-Language": langs, "Accept-Language": langs,
...await getJwtHeaders()
} }
: {}, : await getJwtHeaders()
}), }),
); );
const body = await resp.json(); const body = await resp.json();

View File

@ -1,6 +1,7 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import type { SeedVideo } from "~/models/video"; import type { SeedVideo } from "~/models/video";
import { getJwtHeaders } from "./jwt";
export const createVideo = async (video: SeedVideo | SeedVideo[]) => { export const createVideo = async (video: SeedVideo | SeedVideo[]) => {
const resp = await app.handle( const resp = await app.handle(
@ -9,6 +10,7 @@ export const createVideo = async (video: SeedVideo | SeedVideo[]) => {
body: JSON.stringify(Array.isArray(video) ? video : [video]), body: JSON.stringify(Array.isArray(video) ? video : [video]),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
...(await getJwtHeaders()),
}, },
}), }),
); );

View File

@ -1,4 +1,5 @@
import { beforeAll, describe, expect, it } from "bun:test"; import { beforeAll, describe, expect, it } from "bun:test";
import { getJwtHeaders } from "tests/helpers/jwt";
import { expectStatus } from "tests/utils"; import { expectStatus } from "tests/utils";
import { db } from "~/db"; import { db } from "~/db";
import { shows } from "~/db/schema"; import { shows } from "~/db/schema";
@ -10,8 +11,8 @@ import { app, createMovie, getMovies } from "../helpers";
beforeAll(async () => { beforeAll(async () => {
await db.delete(shows); await db.delete(shows);
for (const movie of [bubble, dune1984, dune]) { for (const movie of [bubble, dune1984, dune]) {
const [ret, _] = await createMovie(movie); const [ret, body] = await createMovie(movie);
expect(ret.status).toBe(201); expectStatus(ret, body).toBe(201);
} }
}); });
@ -73,7 +74,9 @@ describe("with a null value", () => {
), ),
}); });
resp = await app.handle(new Request(next)); resp = await app.handle(
new Request(next, { headers: await getJwtHeaders() }),
);
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
@ -120,7 +123,9 @@ describe("with a null value", () => {
), ),
}); });
resp = await app.handle(new Request(next)); resp = await app.handle(
new Request(next, { headers: await getJwtHeaders() }),
);
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);

View File

@ -1,4 +1,5 @@
import { beforeAll, describe, expect, it } from "bun:test"; import { beforeAll, describe, expect, it } from "bun:test";
import { getJwtHeaders } from "tests/helpers/jwt";
import { expectStatus } from "tests/utils"; import { expectStatus } from "tests/utils";
import { db } from "~/db"; import { db } from "~/db";
import { shows } from "~/db/schema"; import { shows } from "~/db/schema";
@ -71,7 +72,9 @@ describe("Get all movies", () => {
}); });
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
resp = await app.handle(new Request(body.next)); resp = await app.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
@ -104,7 +107,9 @@ describe("Get all movies", () => {
), ),
}); });
resp = await app.handle(new Request(next)); resp = await app.handle(
new Request(next, { headers: await getJwtHeaders() }),
);
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
@ -160,7 +165,9 @@ describe("Get all movies", () => {
expect(items.length).toBe(1); expect(items.length).toBe(1);
expect(items[0].id).toBe(expectedIds[0]); expect(items[0].id).toBe(expectedIds[0]);
// Get Second Page // Get Second Page
resp = await app.handle(new Request(body.next)); resp = await app.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
body = await resp.json(); body = await resp.json();
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
@ -175,7 +182,9 @@ describe("Get all movies", () => {
}); });
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
const resp2 = await app.handle(new Request(body.next)); const resp2 = await app.handle(
new Request(body.next, { headers: await getJwtHeaders() }),
);
const body2 = await resp2.json(); const body2 = await resp2.json();
expectStatus(resp2, body).toBe(200); expectStatus(resp2, body).toBe(200);
@ -187,7 +196,9 @@ describe("Get all movies", () => {
it("Get /random", async () => { it("Get /random", async () => {
const resp = await app.handle( const resp = await app.handle(
new Request("http://localhost/movies/random"), new Request("http://localhost/movies/random", {
headers: await getJwtHeaders(),
}),
); );
expect(resp.status).toBe(302); expect(resp.status).toBe(302);
const location = resp.headers.get("location")!; const location = resp.headers.get("location")!;

View File

@ -1,6 +1,9 @@
import { beforeAll } from "bun:test"; import { beforeAll } from "bun:test";
import { migrate } from "~/db"; import { migrate } from "~/db";
process.env.JWT_SECRET = "this is a secret";
process.env.JWT_ISSUER = "https://kyoo.zoriya.dev";
beforeAll(async () => { beforeAll(async () => {
await migrate(); await migrate();
}); });