/* eslint-disable @typescript-eslint/no-explicit-any */ /* * Copyright (C) 2020 StApps * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import { SCInternalServerErrorResponse, SCMethodNotAllowedErrorResponse, SCRoute, SCRouteHttpVerbs, SCValidationErrorResponse, } from '@openstapps/core'; import * as bodyParser from 'body-parser'; import sinon from 'sinon'; import {expect} from 'chai'; import {Application} from 'express'; import {validator} from '../../src/common'; import {createRoute} from '../../src/routes/route'; import express, {Express} from 'express'; import supertest from 'supertest'; import {Logger} from '@openstapps/logger'; import {DEFAULT_TEST_TIMEOUT} from '../common'; interface ReturnType { foo: boolean; } describe('Create route', async function () { let routeClass: SCRoute; let handler: ( validatedBody: any, app: Application, parameters?: {[parameterName: string]: string}, ) => Promise; let app: Express; const statusCodeSuccess = 222; const bodySuccess = {foo: true}; const sandbox = sinon.createSandbox(); const validationError = new SCValidationErrorResponse([]); const internalServerError = new SCInternalServerErrorResponse(); beforeEach(function () { app = express(); app.use(bodyParser.json()); routeClass = { errorNames: [], method: SCRouteHttpVerbs.POST, requestBodyName: 'fooBodyName', responseBodyName: 'barBodyName', statusCodeSuccess: statusCodeSuccess, urlPath: '/foo', }; handler = (_request, _app) => { return Promise.resolve(bodySuccess); }; }); afterEach(function () { sandbox.restore(); }); it('should complain (throw an error) if provided method is not a valid HTTP verb', async function () { // put a "method" which is not a valid HTTP verb and pretend that it is defined in SCRouteHttpVerbs routeClass.method = 'update' as SCRouteHttpVerbs; expect(() => createRoute(routeClass, handler)).to.throw(Error); }); it('should complain (throw an error) if used method is other than defined in the route creation', async function () { const methodNotAllowedError = new SCMethodNotAllowedErrorResponse(); // @ts-expect-error not assignable sandbox.stub(validator, 'validate').returns({errors: []}); let error: any = {}; sandbox.stub(Logger, 'warn').callsFake(error_ => { error = error_; }); const router = createRoute(routeClass, handler); await app.use(router); const response = await supertest(app) // use method other than defined ("get" is not the method of the route) .get(routeClass.urlPath) .send(); expect(response.status).to.be.equal(methodNotAllowedError.statusCode); expect(error).to.be.instanceOf(SCMethodNotAllowedErrorResponse); }); it('should provide a route which returns handler response and success code', async function () { // @ts-expect-error not assignable sandbox.stub(validator, 'validate').returns({errors: []}); const router = createRoute(routeClass, handler); app.use(router); const response = await supertest(app).post(routeClass.urlPath).send(); expect(response.status).to.be.equal(statusCodeSuccess); expect(response.body).to.be.deep.equal(bodySuccess); }); it('should complain (throw an error) if provided request is not valid', async function () { this.timeout(DEFAULT_TEST_TIMEOUT); const body = {invalid: 'request'}; const router = createRoute(routeClass, handler); app.use(router); const startApp = supertest(app); const validatorStub = sandbox.stub(validator, 'validate'); // @ts-expect-error not assignable validatorStub.withArgs(body, routeClass.requestBodyName).returns({errors: [new Error('Foo Error')]}); const response = await startApp .post(routeClass.urlPath) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .send(body); expect(response.status).to.be.equal(validationError.statusCode); }); it('should complain (throw an error) if response got through the handler is not valid', async function () { const router = createRoute(routeClass, handler); await app.use(router); const startApp = supertest(app); // @ts-expect-error not assignable const validatorStub = sandbox.stub(validator, 'validate').returns({errors: []}); validatorStub .withArgs(bodySuccess, routeClass.responseBodyName) // @ts-expect-error not assignable .returns({errors: [new Error('Foo Error')]}); const response = await startApp.post(routeClass.urlPath).send(); expect(response.status).to.be.equal(internalServerError.statusCode); }); it('should return internal server error if error response not allowed', async function () { class FooErrorResponse { statusCode: number; name: string; message: string; constructor(statusCode: number, name: string, message: string) { this.statusCode = statusCode; this.name = name; this.message = message; } } class BarErrorResponse { statusCode: number; constructor(statusCode: number) { this.statusCode = statusCode; } } const routeClassWithErrorNames: SCRoute = { ...routeClass, errorNames: [FooErrorResponse], }; const barErrorResponse = new BarErrorResponse(599); const handlerThatThrows = () => { throw barErrorResponse; }; const router = createRoute(routeClassWithErrorNames, handlerThatThrows); await app.use(router); const startApp = supertest(app); // @ts-expect-error not assignable sandbox.stub(validator, 'validate').returns({errors: []}); const response = await startApp.post(routeClass.urlPath).send(); expect(response.status).to.be.equal(internalServerError.statusCode); }); it('should return the exact error if error response is allowed', async function () { class FooErrorResponse { statusCode: number; name: string; message: string; constructor(statusCode: number, name: string, message: string) { this.statusCode = statusCode; this.name = name; this.message = message; } } const routeClassWithErrorNames: SCRoute = { ...routeClass, errorNames: [FooErrorResponse], }; const fooErrorResponse = new FooErrorResponse(598, 'Foo Error', 'Foo Error occurred'); const handlerThatThrows = () => { throw fooErrorResponse; }; const router = createRoute(routeClassWithErrorNames, handlerThatThrows); await app.use(router); const startApp = supertest(app); // @ts-expect-error not assignable sandbox.stub(validator, 'validate').returns({errors: []}); const response = await startApp.post(routeClass.urlPath).send(); expect(response.status).to.be.equal(fooErrorResponse.statusCode); }); });