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
run: bun test
env:
JWT_SECRET: "TODO"
POSTGRES_SERVER: localhost

View File

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

View File

@ -11,7 +11,7 @@ import { showsH } from "./controllers/shows/shows";
import { staffH } from "./controllers/staff";
import { studiosH } from "./controllers/studios";
import { videosH } from "./controllers/videos";
import { KError } from "./models/error";
import type { KError } from "./models/error";
export const base = new Elysia({ name: "base" })
.onError(({ code, error }) => {
@ -61,11 +61,12 @@ export const app = new Elysia({ prefix })
detail: {
security: [{ bearer: ["core.read"] }, { api: ["core.read"] }],
},
response: {
401: { ...KError, description: "" },
403: { ...KError, description: "" },
},
perms: ["core.read"],
// See https://github.com/elysiajs/elysia/issues/1158
// response: {
// 401: { ...KError, description: "" },
// 403: { ...KError, description: "" },
// },
permissions: ["core.read"],
},
(app) =>
app
@ -84,11 +85,12 @@ export const app = new Elysia({ prefix })
detail: {
security: [{ bearer: ["core.write"] }, { api: ["core.write"] }],
},
response: {
401: { ...KError, description: "" },
403: { ...KError, description: "" },
},
perms: ["core.read"],
// See https://github.com/elysiajs/elysia/issues/1158
// response: {
// 401: { ...KError, description: "" },
// 403: { ...KError, description: "" },
// },
permissions: ["core.write"],
},
(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 { app } from "~/base";
import type { SeedMovie } from "~/models/movie";
import { getJwtHeaders } from "./jwt";
export const getMovie = async (
id: string,
@ -15,8 +16,9 @@ export const getMovie = async (
headers: langs
? {
"Accept-Language": langs,
...(await getJwtHeaders()),
}
: {},
: await getJwtHeaders(),
}),
);
const body = await resp.json();
@ -41,8 +43,9 @@ export const getMovies = async ({
headers: langs
? {
"Accept-Language": langs,
...(await getJwtHeaders()),
}
: {},
: await getJwtHeaders(),
}),
);
const body = await resp.json();
@ -56,6 +59,7 @@ export const createMovie = async (movie: SeedMovie) => {
body: JSON.stringify(movie),
headers: {
"Content-Type": "application/json",
...(await getJwtHeaders()),
},
}),
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
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";
@ -10,8 +11,8 @@ import { app, createMovie, getMovies } 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);
const [ret, body] = await createMovie(movie);
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();
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();
expectStatus(resp, body).toBe(200);

View File

@ -1,4 +1,5 @@
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";
@ -71,7 +72,9 @@ describe("Get all movies", () => {
});
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();
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();
expectStatus(resp, body).toBe(200);
@ -160,7 +165,9 @@ describe("Get all movies", () => {
expect(items.length).toBe(1);
expect(items[0].id).toBe(expectedIds[0]);
// 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();
expectStatus(resp, body).toBe(200);
@ -175,7 +182,9 @@ describe("Get all movies", () => {
});
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();
expectStatus(resp2, body).toBe(200);
@ -187,7 +196,9 @@ describe("Get all movies", () => {
it("Get /random", async () => {
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);
const location = resp.headers.get("location")!;

View File

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