✨ jest automatic tests
This commit is contained in:
		
							parent
							
								
									1e331b7c9d
								
							
						
					
					
						commit
						a7bf295591
					
				
							
								
								
									
										56
									
								
								api/jest/getRouteDescriptions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								api/jest/getRouteDescriptions.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| import { traverseDirectory } from "lambert-server"; | ||||
| import path from "path"; | ||||
| import express from "express"; | ||||
| import * as RouteUtility from "../dist/util/route"; | ||||
| const Router = express.Router; | ||||
| 
 | ||||
| const routes = new Map<string, RouteUtility.RouteOptions>(); | ||||
| let currentPath = ""; | ||||
| let currentFile = ""; | ||||
| 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"); | ||||
| 	if (opts) { | ||||
| 		routes.set(urlPath + "|" + method, opts); | ||||
| 		// console.log(method, urlPath, opts);
 | ||||
| 	} else { | ||||
| 		console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function routeOptions(opts) { | ||||
| 	return opts; | ||||
| } | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
| RouteUtility.route = routeOptions; | ||||
| 
 | ||||
| express.Router = (opts) => { | ||||
| 	const path = currentPath; | ||||
| 	const file = currentFile; | ||||
| 	const router = Router(opts); | ||||
| 
 | ||||
| 	for (const method of methods) { | ||||
| 		router[method] = registerPath.bind(null, file, method, path); | ||||
| 	} | ||||
| 
 | ||||
| 	return router; | ||||
| }; | ||||
| 
 | ||||
| export default function getRouteDescriptions() { | ||||
| 	const root = path.join(__dirname, "..", "dist", "routes", "/"); | ||||
| 	traverseDirectory({ dirname: root, recursive: true }, (file) => { | ||||
| 		currentFile = file; | ||||
| 		let path = file.replace(root.slice(0, -1), ""); | ||||
| 		path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
 | ||||
| 		path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
 | ||||
| 		if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
 | ||||
| 		currentPath = path; | ||||
| 
 | ||||
| 		require(file); | ||||
| 	}); | ||||
| 	return routes; | ||||
| } | ||||
							
								
								
									
										15
									
								
								api/jest/globalSetup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								api/jest/globalSetup.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| const fs = require("fs"); | ||||
| const path = require("path"); | ||||
| const { FosscordServer } = require("../dist/Server"); | ||||
| const Server = new FosscordServer({ port: 3001 }); | ||||
| global.server = Server; | ||||
| module.exports = async () => { | ||||
| 	try { | ||||
| 		fs.unlinkSync(path.join(__dirname, "..", "database.db")); | ||||
| 	} catch {} | ||||
| 	return await Server.start(); | ||||
| }; | ||||
| 
 | ||||
| // afterAll(async () => {
 | ||||
| // 	return await Server.stop();
 | ||||
| // });
 | ||||
| @ -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());
 | ||||
|  | ||||
| @ -28,12 +28,11 @@ declare global { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export type RouteSchema = string; // typescript interface name
 | ||||
| export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record<string, string> }; | ||||
| export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record<string, string> }; | ||||
| 
 | ||||
| export interface RouteOptions { | ||||
| 	permission?: PermissionResolvable; | ||||
| 	body?: RouteSchema; | ||||
| 	body?: `${string}Schema`; // typescript interface name
 | ||||
| 	response?: RouteResponse; | ||||
| 	example?: { | ||||
| 		body?: any; | ||||
|  | ||||
| @ -1,2 +0,0 @@ | ||||
| // TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308
 | ||||
| // TODO: check every route with different database engine
 | ||||
							
								
								
									
										54
									
								
								api/tests/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								api/tests/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| // TODO: check every route based on route() parameters: https://github.com/fosscord/fosscord-server/issues/308
 | ||||
| // TODO: check every route with different database engine
 | ||||
| 
 | ||||
| import getRouteDescriptions from "../jest/getRouteDescriptions"; | ||||
| import supertest, { Response } from "supertest"; | ||||
| import path 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 schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); | ||||
| export const ajv = new Ajv({ | ||||
| 	allErrors: true, | ||||
| 	parseDate: true, | ||||
| 	allowDate: true, | ||||
| 	schemas, | ||||
| 	messages: true, | ||||
| 	strict: true, | ||||
| 	strictRequired: true | ||||
| }); | ||||
| addFormats(ajv); | ||||
| 
 | ||||
| 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) { | ||||
| 				console.log(`Route ${path} is missing the example property`); | ||||
| 				return done(); | ||||
| 			} | ||||
| 			if (!route.response) { | ||||
| 				console.log(`Route ${path} is missing the response 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); | ||||
| 
 | ||||
| 					return done(); | ||||
| 				}); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Flam3rboy
						Flam3rboy