style: run eslint and prettier
All checks were successful
TrafficCue Server CI / check (push) Successful in 23s
All checks were successful
TrafficCue Server CI / check (push) Successful in 23s
This commit is contained in:
@ -13,4 +13,4 @@ jobs:
|
|||||||
- run: bunx prettier --check .
|
- run: bunx prettier --check .
|
||||||
- run: bun test
|
- run: bun test
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ export function getDb(forceSync = false): DataSource {
|
|||||||
type: "postgres",
|
type: "postgres",
|
||||||
url: process.env.DATABASE_URL,
|
url: process.env.DATABASE_URL,
|
||||||
synchronize: process.argv.includes("sync") || forceSync,
|
synchronize: process.argv.includes("sync") || forceSync,
|
||||||
entities: [User, Review, Saved]
|
entities: [User, Review, Saved],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from "typeorm";
|
||||||
import { type User } from "./User";
|
import { type User } from "./User";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@ -10,12 +16,12 @@ export class Review extends BaseEntity {
|
|||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "float"
|
type: "float",
|
||||||
})
|
})
|
||||||
latitude: number;
|
latitude: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "float"
|
type: "float",
|
||||||
})
|
})
|
||||||
longitude: number;
|
longitude: number;
|
||||||
|
|
||||||
@ -23,12 +29,12 @@ export class Review extends BaseEntity {
|
|||||||
rating: number;
|
rating: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: "text"
|
type: "text",
|
||||||
})
|
})
|
||||||
comment: string;
|
comment: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
default: () => "NOW()"
|
default: () => "NOW()",
|
||||||
})
|
})
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from "typeorm";
|
||||||
import { type User } from "./User";
|
import { type User } from "./User";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@ -18,7 +24,7 @@ export class Saved extends BaseEntity {
|
|||||||
data: string;
|
data: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
default: () => "NOW()"
|
default: () => "NOW()",
|
||||||
})
|
})
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { BaseEntity, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from "typeorm";
|
||||||
import { type Review } from "./Review";
|
import { type Review } from "./Review";
|
||||||
import { type Saved } from "./Saved";
|
import { type Saved } from "./Saved";
|
||||||
|
|
||||||
@ -17,12 +23,12 @@ export class User extends BaseEntity {
|
|||||||
saved: Saved[];
|
saved: Saved[];
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
default: () => "NOW()"
|
default: () => "NOW()",
|
||||||
})
|
})
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
default: () => "NOW()"
|
default: () => "NOW()",
|
||||||
})
|
})
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,4 +3,4 @@ import app from "./main";
|
|||||||
|
|
||||||
await initDb();
|
await initDb();
|
||||||
|
|
||||||
Bun.serve(app);
|
Bun.serve(app);
|
||||||
|
|||||||
63
src/main.ts
63
src/main.ts
@ -61,36 +61,43 @@ app.get("/api/config", (c) => {
|
|||||||
|
|
||||||
app.post("/api/user", async (c) => {
|
app.post("/api/user", async (c) => {
|
||||||
const { token } = await c.req.json();
|
const { token } = await c.req.json();
|
||||||
if(!token) {
|
if (!token) {
|
||||||
return c.json({ error: "Invalid request" }, 400);
|
return c.json({ error: "Invalid request" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!verifyToken(token)) {
|
if (!verifyToken(token)) {
|
||||||
return c.json({ error: "Invalid token" }, 400);
|
return c.json({ error: "Invalid token" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenData = getTokenData(token);
|
const tokenData = getTokenData(token);
|
||||||
if(!tokenData) return c.json({ error: "Invalid token" }, 400);
|
if (!tokenData) return c.json({ error: "Invalid token" }, 400);
|
||||||
if(!tokenData.sub || typeof tokenData.sub != "string") return c.json({ error: "Invalid token" }, 400);
|
if (!tokenData.sub || typeof tokenData.sub != "string")
|
||||||
if(!tokenData.preferred_username || typeof tokenData.preferred_username != "string") return c.json({ error: "Invalid token" }, 400);
|
return c.json({ error: "Invalid token" }, 400);
|
||||||
const user = await User.findOneBy({ id: tokenData.sub }) || new User();
|
if (
|
||||||
|
!tokenData.preferred_username ||
|
||||||
|
typeof tokenData.preferred_username != "string"
|
||||||
|
)
|
||||||
|
return c.json({ error: "Invalid token" }, 400);
|
||||||
|
const user = (await User.findOneBy({ id: tokenData.sub })) || new User();
|
||||||
user.id = tokenData.sub;
|
user.id = tokenData.sub;
|
||||||
user.username = tokenData.preferred_username;
|
user.username = tokenData.preferred_username;
|
||||||
user.updated_at = new Date();
|
user.updated_at = new Date();
|
||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
return c.json({ success: true });
|
return c.json({ success: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
app.get("/api/user", async (c) => {
|
app.get("/api/user", async (c) => {
|
||||||
const name = c.req.query("name");
|
const name = c.req.query("name");
|
||||||
if(!name) return c.json({ sucess: false }, 400);
|
if (!name) return c.json({ sucess: false }, 400);
|
||||||
const users = await User.findBy({
|
const users = await User.findBy({
|
||||||
username: ILike("%" + name + "%")
|
username: ILike("%" + name + "%"),
|
||||||
|
});
|
||||||
|
const mapped = users.map((u) => {
|
||||||
|
return { username: u.username };
|
||||||
});
|
});
|
||||||
const mapped = users.map(u => { return { username: u.username }; });
|
|
||||||
return c.json(mapped);
|
return c.json(mapped);
|
||||||
})
|
});
|
||||||
|
|
||||||
if (process.env.REVIEWS_ENABLED) {
|
if (process.env.REVIEWS_ENABLED) {
|
||||||
app.get("/api/reviews", async (c) => {
|
app.get("/api/reviews", async (c) => {
|
||||||
@ -104,7 +111,7 @@ if (process.env.REVIEWS_ENABLED) {
|
|||||||
console.log(`Fetching reviews for lat: ${lat}, lon: ${lon}`);
|
console.log(`Fetching reviews for lat: ${lat}, lon: ${lon}`);
|
||||||
const reviews = await Review.findBy({
|
const reviews = await Review.findBy({
|
||||||
latitude: nlat,
|
latitude: nlat,
|
||||||
longitude: nlon
|
longitude: nlon,
|
||||||
});
|
});
|
||||||
return c.json(reviews);
|
return c.json(reviews);
|
||||||
});
|
});
|
||||||
@ -137,7 +144,7 @@ if (process.env.REVIEWS_ENABLED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOneBy({ id: uid });
|
const user = await User.findOneBy({ id: uid });
|
||||||
if(!user) {
|
if (!user) {
|
||||||
return c.json({ error: "Invalid user ID" }, 400);
|
return c.json({ error: "Invalid user ID" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,19 +188,18 @@ app.get("/api/saved", async (c) => {
|
|||||||
// return c.json({ error: "Invalid user ID" }, 400);
|
// return c.json({ error: "Invalid user ID" }, 400);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const saved = await Saved.findBy({ user: {
|
const saved = await Saved.findBy({
|
||||||
id: uid
|
user: {
|
||||||
} });
|
id: uid,
|
||||||
|
},
|
||||||
|
});
|
||||||
return c.json(saved);
|
return c.json(saved);
|
||||||
})
|
});
|
||||||
|
|
||||||
app.put("/api/saved", async (c) => {
|
app.put("/api/saved", async (c) => {
|
||||||
const { name, data } = await c.req.json();
|
const { name, data } = await c.req.json();
|
||||||
if (!name || !data) {
|
if (!name || !data) {
|
||||||
return c.json(
|
return c.json({ error: "name and data are required" }, 400);
|
||||||
{ error: "name and data are required" },
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeader = c.req.header("Authorization");
|
const authHeader = c.req.header("Authorization");
|
||||||
@ -215,7 +221,7 @@ app.put("/api/saved", async (c) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOneBy({ id: uid });
|
const user = await User.findOneBy({ id: uid });
|
||||||
if(!user) {
|
if (!user) {
|
||||||
return c.json({ error: "Invalid user ID" }, 400);
|
return c.json({ error: "Invalid user ID" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,16 +231,13 @@ app.put("/api/saved", async (c) => {
|
|||||||
saved.data = data;
|
saved.data = data;
|
||||||
await saved.save();
|
await saved.save();
|
||||||
|
|
||||||
return c.json({ success: true })
|
return c.json({ success: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
app.delete("/api/saved", async (c) => {
|
app.delete("/api/saved", async (c) => {
|
||||||
const { name } = await c.req.json();
|
const { name } = await c.req.json();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return c.json(
|
return c.json({ error: "name is required" }, 400);
|
||||||
{ error: "name is required" },
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeader = c.req.header("Authorization");
|
const authHeader = c.req.header("Authorization");
|
||||||
@ -261,13 +264,13 @@ app.delete("/api/saved", async (c) => {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
const saved = await Saved.findOneBy({ user: { id: uid }, name });
|
const saved = await Saved.findOneBy({ user: { id: uid }, name });
|
||||||
if(!saved) {
|
if (!saved) {
|
||||||
return c.json({ error: "No such save found" }, 400);
|
return c.json({ error: "No such save found" }, 400);
|
||||||
}
|
}
|
||||||
await saved.remove();
|
await saved.remove();
|
||||||
|
|
||||||
return c.json({ success: true });
|
return c.json({ success: true });
|
||||||
})
|
});
|
||||||
|
|
||||||
if (process.env.TANKERKOENIG_API_KEY) {
|
if (process.env.TANKERKOENIG_API_KEY) {
|
||||||
app.get("/api/fuel/list", async (c) => {
|
app.get("/api/fuel/list", async (c) => {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { exportJWK, exportPKCS8, generateKeyPair } from "jose";
|
import { exportJWK, exportPKCS8, generateKeyPair } from "jose";
|
||||||
|
|
||||||
export async function createTestKey() {
|
export async function createTestKey() {
|
||||||
const { publicKey, privateKey } = await generateKeyPair("RS256", {
|
const { publicKey, privateKey } = await generateKeyPair("RS256", {
|
||||||
extractable: true
|
extractable: true,
|
||||||
});
|
});
|
||||||
const jwk = await exportJWK(publicKey);
|
const jwk = await exportJWK(publicKey);
|
||||||
jwk.kid = "test-key";
|
jwk.kid = "test-key";
|
||||||
@ -10,4 +10,4 @@ export async function createTestKey() {
|
|||||||
|
|
||||||
const pem = await exportPKCS8(privateKey);
|
const pem = await exportPKCS8(privateKey);
|
||||||
return { jwk, pem };
|
return { jwk, pem };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||||
import { getDb, initDb } from "../src/db";
|
import { getDb, initDb } from "../src/db";
|
||||||
import { GenericContainer, Wait, type StartedTestContainer } from "testcontainers";
|
import {
|
||||||
|
GenericContainer,
|
||||||
|
Wait,
|
||||||
|
type StartedTestContainer,
|
||||||
|
} from "testcontainers";
|
||||||
import { createTestKey } from "./keys";
|
import { createTestKey } from "./keys";
|
||||||
import { sign } from "jsonwebtoken";
|
import { sign } from "jsonwebtoken";
|
||||||
import { getTokenData, getTokenUID, verifyToken } from "../src/auth";
|
import { getTokenData, getTokenUID, verifyToken } from "../src/auth";
|
||||||
@ -13,7 +17,7 @@ process.env.OIDC_TOKEN_URL = "http://oidc.example.com/token";
|
|||||||
process.env.NODE_ENV = "test";
|
process.env.NODE_ENV = "test";
|
||||||
process.env.REVIEWS_ENABLED = "true";
|
process.env.REVIEWS_ENABLED = "true";
|
||||||
|
|
||||||
const app = await import("../src/main").then(m => m.default);
|
const app = await import("../src/main").then((m) => m.default);
|
||||||
|
|
||||||
const PGSQL_IMAGE = "postgres:latest";
|
const PGSQL_IMAGE = "postgres:latest";
|
||||||
let postgresContainer: StartedTestContainer | null = null;
|
let postgresContainer: StartedTestContainer | null = null;
|
||||||
@ -22,12 +26,12 @@ let privateKey: string;
|
|||||||
|
|
||||||
// Spin up a temporary Postgres container for testing
|
// Spin up a temporary Postgres container for testing
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
if(!process.env.DATABASE_URL) {
|
if (!process.env.DATABASE_URL) {
|
||||||
postgresContainer = await new GenericContainer(PGSQL_IMAGE)
|
postgresContainer = await new GenericContainer(PGSQL_IMAGE)
|
||||||
.withEnvironment({
|
.withEnvironment({
|
||||||
POSTGRES_USER: "tc",
|
POSTGRES_USER: "tc",
|
||||||
POSTGRES_PASSWORD: "tc",
|
POSTGRES_PASSWORD: "tc",
|
||||||
POSTGRES_DB: "tc"
|
POSTGRES_DB: "tc",
|
||||||
})
|
})
|
||||||
.withExposedPorts(5432)
|
.withExposedPorts(5432)
|
||||||
.withHealthCheck({
|
.withHealthCheck({
|
||||||
@ -38,10 +42,10 @@ beforeAll(async () => {
|
|||||||
})
|
})
|
||||||
.withWaitStrategy(Wait.forHealthCheck())
|
.withWaitStrategy(Wait.forHealthCheck())
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
process.env.DATABASE_URL = `postgres://tc:tc@localhost:${postgresContainer.getMappedPort(5432)}/tc`;
|
process.env.DATABASE_URL = `postgres://tc:tc@localhost:${postgresContainer.getMappedPort(5432)}/tc`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDb(true);
|
getDb(true);
|
||||||
await initDb();
|
await initDb();
|
||||||
});
|
});
|
||||||
@ -51,7 +55,7 @@ afterAll(async () => {
|
|||||||
const db = getDb();
|
const db = getDb();
|
||||||
await db.dropDatabase();
|
await db.dropDatabase();
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
if(postgresContainer) await postgresContainer.stop();
|
if (postgresContainer) await postgresContainer.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override JWKS URL to prevent external calls during tests
|
// Override JWKS URL to prevent external calls during tests
|
||||||
@ -63,7 +67,7 @@ beforeAll(async () => {
|
|||||||
// Mock the fetch function to return the test JWK
|
// Mock the fetch function to return the test JWK
|
||||||
// @ts-expect-error - we just need to override fetch for tests
|
// @ts-expect-error - we just need to override fetch for tests
|
||||||
global.fetch = async () => ({
|
global.fetch = async () => ({
|
||||||
json: async () => ({ keys: [jwk] })
|
json: async () => ({ keys: [jwk] }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,31 +90,35 @@ it("Serves a correct config", async () => {
|
|||||||
expect(config.capabilities).toBeArray();
|
expect(config.capabilities).toBeArray();
|
||||||
expect(config.capabilities).toContain("auth");
|
expect(config.capabilities).toContain("auth");
|
||||||
expect(config.capabilities).toContain("reviews");
|
expect(config.capabilities).toContain("reviews");
|
||||||
expect(config.oidc).toEqual({CLIENT_ID: "test-client", AUTH_URL: "http://oidc.example.com/auth", TOKEN_URL: "http://oidc.example.com/token"});
|
expect(config.oidc).toEqual({
|
||||||
})
|
CLIENT_ID: "test-client",
|
||||||
|
AUTH_URL: "http://oidc.example.com/auth",
|
||||||
|
TOKEN_URL: "http://oidc.example.com/token",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Authentication", () => {
|
describe("Authentication", () => {
|
||||||
it("verifies a valid token", async () => {
|
it("verifies a valid token", async () => {
|
||||||
const token = sign({ sub: "test" }, privateKey, {
|
const token = sign({ sub: "test" }, privateKey, {
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
keyid: jwk.kid,
|
keyid: jwk.kid,
|
||||||
expiresIn: "1h"
|
expiresIn: "1h",
|
||||||
});
|
});
|
||||||
expect(await verifyToken(token)).toBe(true);
|
expect(await verifyToken(token)).toBe(true);
|
||||||
expect(getTokenUID(token)).toBe("test");
|
expect(getTokenUID(token)).toBe("test");
|
||||||
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("fails with invalid KID", async () => {
|
it("fails with invalid KID", async () => {
|
||||||
const token = sign({ sub: "test" }, privateKey, {
|
const token = sign({ sub: "test" }, privateKey, {
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
keyid: "invalid-kid",
|
keyid: "invalid-kid",
|
||||||
expiresIn: "1h"
|
expiresIn: "1h",
|
||||||
});
|
});
|
||||||
expect(await verifyToken(token)).toBe(false);
|
expect(await verifyToken(token)).toBe(false);
|
||||||
expect(getTokenUID(token)).toBe("test");
|
expect(getTokenUID(token)).toBe("test");
|
||||||
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
||||||
})
|
});
|
||||||
|
|
||||||
it("fails on an invalid token", async () => {
|
it("fails on an invalid token", async () => {
|
||||||
const token = "invalid.token.here";
|
const token = "invalid.token.here";
|
||||||
@ -123,42 +131,52 @@ describe("Authentication", () => {
|
|||||||
const token = sign({ sub: "test" }, privateKey, {
|
const token = sign({ sub: "test" }, privateKey, {
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
keyid: jwk.kid,
|
keyid: jwk.kid,
|
||||||
expiresIn: "-1h"
|
expiresIn: "-1h",
|
||||||
});
|
});
|
||||||
expect(await verifyToken(token)).toBe(false);
|
expect(await verifyToken(token)).toBe(false);
|
||||||
expect(getTokenUID(token)).toBe("test");
|
expect(getTokenUID(token)).toBe("test");
|
||||||
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
expect(getTokenData(token)).toMatchObject({ sub: "test" });
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
let token: string;
|
let token: string;
|
||||||
const uid = crypto.randomUUID();
|
const uid = crypto.randomUUID();
|
||||||
|
|
||||||
describe("User Endpoints", () => {
|
describe("User Endpoints", () => {
|
||||||
it("fails with invalid token", async () => {
|
it("fails with invalid token", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/user", {
|
const res = await app.fetch(
|
||||||
method: "POST",
|
new Request("http://localhost/api/user", {
|
||||||
body: JSON.stringify({ token: "invalid.token.here" }),
|
method: "POST",
|
||||||
}));
|
body: JSON.stringify({ token: "invalid.token.here" }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
});
|
});
|
||||||
it("uploads a token", async () => {
|
it("uploads a token", async () => {
|
||||||
token = sign({
|
token = sign(
|
||||||
sub: uid,
|
{
|
||||||
preferred_username: "testuser",
|
sub: uid,
|
||||||
}, privateKey, {
|
preferred_username: "testuser",
|
||||||
algorithm: "RS256",
|
},
|
||||||
keyid: jwk.kid,
|
privateKey,
|
||||||
expiresIn: "1h"
|
{
|
||||||
});
|
algorithm: "RS256",
|
||||||
const res = await app.fetch(new Request("http://localhost/api/user", {
|
keyid: jwk.kid,
|
||||||
method: "POST",
|
expiresIn: "1h",
|
||||||
body: JSON.stringify({ token }),
|
},
|
||||||
}));
|
);
|
||||||
|
const res = await app.fetch(
|
||||||
|
new Request("http://localhost/api/user", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ token }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
it("finds the user in search", async () => {
|
it("finds the user in search", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/user?name=testuser"));
|
const res = await app.fetch(
|
||||||
|
new Request("http://localhost/api/user?name=testuser"),
|
||||||
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -166,34 +184,38 @@ describe("User Endpoints", () => {
|
|||||||
const users = json as { username: string }[];
|
const users = json as { username: string }[];
|
||||||
expect(users.length).toBeGreaterThan(0);
|
expect(users.length).toBeGreaterThan(0);
|
||||||
expect(users[0]!.username).toBe("testuser");
|
expect(users[0]!.username).toBe("testuser");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("Reviews", () => {
|
describe("Reviews", () => {
|
||||||
it("creates a review", async () => {
|
it("creates a review", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/review", {
|
const res = await app.fetch(
|
||||||
method: "POST",
|
new Request("http://localhost/api/review", {
|
||||||
headers: {
|
method: "POST",
|
||||||
"Authorization": `Bearer ${token}`,
|
headers: {
|
||||||
"Content-Type": "application/json"
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify({
|
},
|
||||||
rating: 4,
|
body: JSON.stringify({
|
||||||
comment: "This is a test review.",
|
rating: 4,
|
||||||
lat: 51.5074,
|
comment: "This is a test review.",
|
||||||
lon: 7.4653
|
lat: 51.5074,
|
||||||
|
lon: 7.4653,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}));
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
expect(json).toBeObject();
|
expect(json).toBeObject();
|
||||||
const review = json as { success: boolean };
|
const review = json as { success: boolean };
|
||||||
expect(review.success).toBe(true);
|
expect(review.success).toBe(true);
|
||||||
})
|
});
|
||||||
|
|
||||||
it("lists reviews", async () => {
|
it("lists reviews", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/reviews?lat=51.5074&lon=7.4653"));
|
const res = await app.fetch(
|
||||||
|
new Request("http://localhost/api/reviews?lat=51.5074&lon=7.4653"),
|
||||||
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -202,22 +224,24 @@ describe("Reviews", () => {
|
|||||||
expect(reviews.length).toBeGreaterThan(0);
|
expect(reviews.length).toBeGreaterThan(0);
|
||||||
expect(reviews[0]!.rating).toBe(4);
|
expect(reviews[0]!.rating).toBe(4);
|
||||||
expect(reviews[0]!.comment).toBe("This is a test review.");
|
expect(reviews[0]!.comment).toBe("This is a test review.");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("Saved Routes", () => {
|
describe("Saved Routes", () => {
|
||||||
it("saves a route", async () => {
|
it("saves a route", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/saved", {
|
const res = await app.fetch(
|
||||||
method: "PUT",
|
new Request("http://localhost/api/saved", {
|
||||||
headers: {
|
method: "PUT",
|
||||||
"Authorization": `Bearer ${token}`,
|
headers: {
|
||||||
"Content-Type": "application/json"
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify({
|
},
|
||||||
name: "Home to Work",
|
body: JSON.stringify({
|
||||||
data: "This is some test route data."
|
name: "Home to Work",
|
||||||
|
data: "This is some test route data.",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}));
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -226,12 +250,14 @@ describe("Saved Routes", () => {
|
|||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
it("lists saved routes", async () => {
|
it("lists saved routes", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/saved", {
|
const res = await app.fetch(
|
||||||
method: "GET",
|
new Request("http://localhost/api/saved", {
|
||||||
headers: {
|
method: "GET",
|
||||||
"Authorization": `Bearer ${token}`,
|
headers: {
|
||||||
},
|
Authorization: `Bearer ${token}`,
|
||||||
}));
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -242,16 +268,18 @@ describe("Saved Routes", () => {
|
|||||||
expect(routes[0]!.data).toBe("This is some test route data.");
|
expect(routes[0]!.data).toBe("This is some test route data.");
|
||||||
});
|
});
|
||||||
it("deletes a saved route", async () => {
|
it("deletes a saved route", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/saved", {
|
const res = await app.fetch(
|
||||||
method: "DELETE",
|
new Request("http://localhost/api/saved", {
|
||||||
headers: {
|
method: "DELETE",
|
||||||
"Authorization": `Bearer ${token}`,
|
headers: {
|
||||||
"Content-Type": "application/json"
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
"Content-Type": "application/json",
|
||||||
body: JSON.stringify({
|
},
|
||||||
name: "Home to Work",
|
body: JSON.stringify({
|
||||||
|
name: "Home to Work",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}));
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -260,12 +288,14 @@ describe("Saved Routes", () => {
|
|||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
it("no longer lists deleted routes", async () => {
|
it("no longer lists deleted routes", async () => {
|
||||||
const res = await app.fetch(new Request("http://localhost/api/saved", {
|
const res = await app.fetch(
|
||||||
method: "GET",
|
new Request("http://localhost/api/saved", {
|
||||||
headers: {
|
method: "GET",
|
||||||
"Authorization": `Bearer ${token}`,
|
headers: {
|
||||||
},
|
Authorization: `Bearer ${token}`,
|
||||||
}));
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
expect(json).toBeDefined();
|
expect(json).toBeDefined();
|
||||||
@ -273,4 +303,4 @@ describe("Saved Routes", () => {
|
|||||||
const routes = json as { name: string; data: string }[];
|
const routes = json as { name: string; data: string }[];
|
||||||
expect(routes.length).toBe(0);
|
expect(routes.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -27,6 +27,6 @@
|
|||||||
"strictPropertyInitialization": false,
|
"strictPropertyInitialization": false,
|
||||||
|
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user