From d61013cebf9eb8a73318ea0bfcae6f643cd90a7b Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 01:50:29 +0200 Subject: :sparkles: jest automatic tests --- api/src/util/route.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'api/src/util/route.ts') diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 678ca64c..35ea43ba 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -28,12 +28,11 @@ declare global { } } -export type RouteSchema = string; // typescript interface name -export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record }; +export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record }; export interface RouteOptions { permission?: PermissionResolvable; - body?: RouteSchema; + body?: `${string}Schema`; // typescript interface name response?: RouteResponse; example?: { body?: any; -- cgit 1.5.1 From aae7e8d7770f6d5f7d46c2263880c6c4e7ef6788 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 18 Sep 2021 11:56:06 +0200 Subject: :sparkles: route middleware test option --- api/jest/getRouteDescriptions.ts | 6 ++++-- api/src/routes/users/#id/profile.ts | 2 +- api/src/util/route.ts | 6 +++--- api/tests/routes.test.ts | 11 ++++------- 4 files changed, 12 insertions(+), 13 deletions(-) (limited to 'api/src/util/route.ts') diff --git a/api/jest/getRouteDescriptions.ts b/api/jest/getRouteDescriptions.ts index d7d6e0c6..33922899 100644 --- a/api/jest/getRouteDescriptions.ts +++ b/api/jest/getRouteDescriptions.ts @@ -2,6 +2,7 @@ import { traverseDirectory } from "lambert-server"; import path from "path"; import express from "express"; import * as RouteUtility from "../dist/util/route"; +import { RouteOptions } from "../dist/util/route"; const Router = express.Router; const routes = new Map(); @@ -12,9 +13,10 @@ const methods = ["get", "post", "put", "delete", "patch"]; function registerPath(file, method, prefix, path, ...args) { const urlPath = prefix + path; const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); - const opts = args.find((x) => typeof x === "object"); + const opts: RouteOptions = args.find((x) => typeof x === "object"); if (opts) { - routes.set(urlPath + "|" + method, opts); + routes.set(urlPath + "|" + method, opts); // @ts-ignore + opts.file = sourceFile; // console.log(method, urlPath, opts); } else { console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index d60c4f86..d099bce7 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -11,7 +11,7 @@ export interface UserProfileResponse { premium_since?: Date; } -router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => { +router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { if (req.params.id === "@me") req.params.id = req.user_id; const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 35ea43ba..9ef92c3a 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -33,11 +33,11 @@ export type RouteResponse = { status?: number; body?: `${string}Response`; heade export interface RouteOptions { permission?: PermissionResolvable; body?: `${string}Schema`; // typescript interface name - response?: RouteResponse; - example?: { + test?: { + response?: RouteResponse; body?: any; path?: string; - event?: EventData; + event?: EventData | EventData[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index e913e0dc..4cb7e6bc 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -3,13 +3,13 @@ import getRouteDescriptions from "../jest/getRouteDescriptions"; import supertest, { Response } from "supertest"; -import path from "path"; +import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; const request = supertest("http://localhost:3001/api"); -const SchemaPath = path.join(__dirname, "..", "assets", "responses.json"); +const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ allErrors: true, @@ -27,13 +27,10 @@ describe("Automatic unit tests with route description middleware", () => { routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); + test(path, (done) => { if (!route.example) { - console.log(`Route ${path} is missing the example property`); - return done(); - } - if (!route.response) { - console.log(`Route ${path} is missing the response property`); + console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } const urlPath = path || route.example?.path; -- cgit 1.5.1 From ecc7683c02430cee57eac59bf2c9c64fe87b7091 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 20 Sep 2021 23:35:37 +0200 Subject: :bug: fix unittests --- api/jest/globalSetup.js | 7 +++- api/jest/setup.js | 4 +-- api/src/util/route.ts | 4 +-- api/tests/routes.test.ts | 88 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 82 insertions(+), 21 deletions(-) (limited to 'api/src/util/route.ts') diff --git a/api/jest/globalSetup.js b/api/jest/globalSetup.js index 98e70fb9..520aa0e2 100644 --- a/api/jest/globalSetup.js +++ b/api/jest/globalSetup.js @@ -1,3 +1,4 @@ +const { Config, initDatabase } = require("@fosscord/util"); const fs = require("fs"); const path = require("path"); const { FosscordServer } = require("../dist/Server"); @@ -5,8 +6,12 @@ const Server = new FosscordServer({ port: 3001 }); global.server = Server; module.exports = async () => { try { - fs.unlinkSync(path.join(__dirname, "..", "database.db")); + fs.unlinkSync(path.join(process.cwd(), "database.db")); } catch {} + + await initDatabase(); + await Config.init(); + Config.get().limits.rate.disabled = true; return await Server.start(); }; diff --git a/api/jest/setup.js b/api/jest/setup.js index bd535866..abc485ae 100644 --- a/api/jest/setup.js +++ b/api/jest/setup.js @@ -1,2 +1,2 @@ -// jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); -// jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "log").mockImplementation(() => jest.fn()); +jest.spyOn(global.console, "info").mockImplementation(() => jest.fn()); diff --git a/api/src/util/route.ts b/api/src/util/route.ts index 5b06a2b5..e7c7ed1c 100644 --- a/api/src/util/route.ts +++ b/api/src/util/route.ts @@ -1,4 +1,4 @@ -import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; +import { DiscordApiErrors, EVENT, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; @@ -38,7 +38,7 @@ export interface RouteOptions { response?: RouteResponse; body?: any; path?: string; - event?: EventData | EventData[]; + event?: EVENT | EVENT[]; headers?: Record; }; } diff --git a/api/tests/routes.test.ts b/api/tests/routes.test.ts index 4cb7e6bc..0473c579 100644 --- a/api/tests/routes.test.ts +++ b/api/tests/routes.test.ts @@ -2,12 +2,12 @@ // TODO: check every route with different database engine import getRouteDescriptions from "../jest/getRouteDescriptions"; -import supertest, { Response } from "supertest"; import { join } from "path"; import fs from "fs"; import Ajv from "ajv"; import addFormats from "ajv-formats"; -const request = supertest("http://localhost:3001/api"); +import fetch from "node-fetch"; +import { User } from "@fosscord/util"; const SchemaPath = join(__dirname, "..", "assets", "responses.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); @@ -18,34 +18,90 @@ export const ajv = new Ajv({ schemas, messages: true, strict: true, - strictRequired: true + strictRequired: true, + coerceTypes: true }); addFormats(ajv); +var token: string; +var user: User; +beforeAll(async (done) => { + try { + const response = await fetch("http://localhost:3001/api/auth/register", { + method: "POST", + body: JSON.stringify({ + fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw", + email: "test@example.com", + username: "tester", + password: "wtp9gep9gw", + invite: null, + consent: true, + date_of_birth: "2000-01-01", + gift_code_sku_id: null, + captcha_key: null + }), + headers: { + "content-type": "application/json" + } + }); + const json = await response.json(); + token = json.token; + user = await ( + await fetch(`http://localhost:3001/api/users/@me`, { + headers: { authorization: token } + }) + ).json(); + + done(); + } catch (error) { + done(error); + } +}); + describe("Automatic unit tests with route description middleware", () => { const routes = getRouteDescriptions(); routes.forEach((route, pathAndMethod) => { const [path, method] = pathAndMethod.split("|"); - test(path, (done) => { - if (!route.example) { + test(path, async (done) => { + if (!route.test) { console.log(`${(route as any).file}\nrouter.${method} is missing the test property`); return done(); } - const urlPath = path || route.example?.path; - const validate = ajv.getSchema(route.response.body); - if (!validate) return done(new Error(`Response schema ${route.response.body} not found`)); - - request[method](urlPath) - .expect(route.response.status) - .expect((err: any, res: Response) => { - if (err) return done(err); - const valid = validate(res.body); - if (!valid) return done(validate.errors); + const urlPath = path.replace(":id", user.id) || route.test?.path; + var validate: any; + if (route.test.body) { + validate = ajv.getSchema(route.test.body); + if (!validate) return done(new Error(`Response schema ${route.test.body} not found`)); + } + + var body = ""; - return done(); + try { + const response = await fetch(`http://localhost:3001/api${urlPath}`, { + method: method.toUpperCase(), + body: JSON.stringify(route.test.body), + headers: { ...route.test.headers, authorization: token } }); + + body = await response.text(); + + expect(response.status, body).toBe(route.test.response.status || 200); + + // TODO: check headers + // TODO: expect event + + if (validate) { + body = JSON.parse(body); + const valid = validate(body); + if (!valid) return done(validate.errors); + } + } catch (error) { + return done(error); + } + + return done(); }); }); }); -- cgit 1.5.1