Skip to content

Commit

Permalink
test/checks for query and url parameter definition (#141)
Browse files Browse the repository at this point in the history
* test: test query and url parameter definition

* chore: move parser to dev dependencies

* chore: typo

* chore: revert unwanted change

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
sebbi08 and mergify[bot] authored Mar 6, 2024
1 parent 57fe587 commit c1da6ba
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 19 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

-> SDK 4.1.0

- add routes for querying versions of attrbutes
- add routes for querying versions of attributes
- `GET /api/v2/Attributes/Own/Repository` Get all the repository attributes
- `GET /api/v2/Attributes/Own/Shared/Identity` Get all own shared indentity attributes
- `GET /api/v2/Attributes/Own/Shared/Identity` Get all own shared identity attributes
- `GET /api/v2/Attributes/Peer/Shared/Identity` Get all peer shared identity attributes
- `GET /api/v2/Attributes/{id}/Versions` Get all versions of one repository attribute
- `GET /api/v2/Attributes/{id}/Versions/Shared` Get all shard versions of one repository attribute
- `GET /api/v2/Attributes/{id}/Versions/Shared` Get all shared versions of one repository attribute

## 3.7.3

Expand Down
96 changes: 95 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"yamljs": "0.3.0"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@js-soft/eslint-config-ts": "1.6.6",
"@js-soft/license-check": "1.0.9",
"@nmshd/connector-sdk": "*",
Expand Down Expand Up @@ -117,7 +118,7 @@
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"typescript-rest-swagger": "github:nmshd/typescript-rest-swagger#1.1.7"
"typescript-rest-swagger": "github:nmshd/typescript-rest-swagger#1.2.1"
},
"overrides": {
"typescript-rest": {
Expand Down
98 changes: 84 additions & 14 deletions test/spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import yamljs from "yamljs";

import { MetadataGenerator, SpecGenerator, Swagger } from "typescript-rest-swagger";

import swaggerParser from "@apidevtools/swagger-parser";
import tsConfigBase from "../tsconfig.json";

describe("test openapi spec against routes", () => {
test("all route names should match the generated ones", async () => {
const manualOpenApiSpec: Swagger.Spec = yamljs.load("src/modules/coreHttpApi/openapi.yml");
let manualOpenApiSpec: Swagger.Spec;
let generatedOpenApiSpec: Swagger.Spec;

beforeAll(async () => {
manualOpenApiSpec = yamljs.load("src/modules/coreHttpApi/openapi.yml");

const files = "src/modules/**/*.ts";

Expand All @@ -26,22 +30,33 @@ describe("test openapi spec against routes", () => {
yaml: false
};
const generator = new SpecGenerator(metadata, defaultOptions);
const generatedOpenApiSpec: Swagger.Spec = await generator.getOpenApiSpec();
generatedOpenApiSpec = await generator.getOpenApiSpec();
generatedOpenApiSpec = (await swaggerParser.dereference(generatedOpenApiSpec as any)) as Swagger.Spec;
manualOpenApiSpec = (await swaggerParser.dereference(manualOpenApiSpec as any)) as Swagger.Spec;
harmonizeSpec(manualOpenApiSpec);
harmonizeSpec(generatedOpenApiSpec);

const manualPaths = Object.keys(manualOpenApiSpec.paths);
const generatedPaths = Object.keys(generatedOpenApiSpec.paths);
});
test("all route names should match the generated ones", () => {
const manualPaths = getPaths(manualOpenApiSpec);
const generatedPaths = getPaths(generatedOpenApiSpec);

generatedPaths.forEach((path) => {
expect(manualPaths).toContain(path);
});

// Paths not defined in the typescript-rest way
const ignorePaths = ["/health", "/Monitoring/Version", "/Monitoring/Requests", "/Monitoring/Support"];
// Paths to ignroe in regard to return code consistencie (Post requests that return 200 due to no creation)
manualPaths.forEach((path) => {
if (ignorePaths.includes(path)) {
return;
}

expect(generatedPaths).toContain(path);
});
});
test("all routes should have the same HTTP methods", () => {
const manualPaths = getPaths(manualOpenApiSpec);
// Paths to ignore in regard to return code consistency (Post requests that return 200 due to no creation)
/* eslint-disable @typescript-eslint/naming-convention */
const returnCodeOvererite: Record<string, string | undefined> = {
const returnCodeOverwrite: Record<string, string | undefined> = {
"/api/v2/Account/Sync": "200",
"/api/v2/Attributes/ExecuteIQLQuery": "200",
"/api/v2/Attributes/ValidateIQLQuery": "200",
Expand All @@ -53,9 +68,6 @@ describe("test openapi spec against routes", () => {
if (ignorePaths.includes(path)) {
return;
}

expect(generatedPaths).toContain(path);

const generatedMethods = Object.keys(generatedOpenApiSpec.paths[path])
.map((method) => method.toLocaleLowerCase())
.sort();
Expand All @@ -69,11 +81,59 @@ describe("test openapi spec against routes", () => {
const key = method as "get" | "put" | "post" | "delete" | "options" | "head" | "patch";
const manualResponses = Object.keys(manualOpenApiSpec.paths[path][key]?.responses ?? {});
let expectedResponseCode = key === "post" ? "201" : "200";
expectedResponseCode = returnCodeOvererite[path] ?? expectedResponseCode;
expectedResponseCode = returnCodeOverwrite[path] ?? expectedResponseCode;
expect(manualResponses, `Path ${path} and method ${method} does not contain response code ${expectedResponseCode}`).toContainEqual(expectedResponseCode);
});
});
});

test("all generated params should be in the manual spec", () => {
const pathsWithDBQueries = [
{ path: "/api/v2/Attributes/Own/Shared/Identity", method: "get" },
{ path: "/api/v2/Attributes/Peer/Shared/Identity", method: "get" },
{ path: "/api/v2/Attributes/Own/Shared/Identity", method: "get" }
];

const generatedPaths = getPaths(generatedOpenApiSpec);
generatedPaths.forEach((path) => {
const generatedMethods = Object.keys(generatedOpenApiSpec.paths[path])
.map((method) => method.toLowerCase())
.sort() as (keyof Swagger.Path)[];
generatedMethods.forEach((method: keyof Swagger.Path) => {
const generatedOperation = generatedOpenApiSpec.paths[path][method];
if (!isOperation(generatedOperation) || !generatedOperation.parameters) {
return;
}

const manualOperation = manualOpenApiSpec.paths[path][method];
if (!isOperation(manualOperation) || !manualOperation.parameters) {
throw new Error(`${path} ${method} does not contain parameters but generated do`);
}

// DBQuery are used via context.query and not by injection as QueryParameter so they will not be generated and the length will be different
if (!pathsWithDBQueries.some((p) => p.path === path && p.method.toLowerCase() === method.toLowerCase())) {
// eslint-disable-next-line jest/no-conditional-expect
expect(generatedOperation.parameters, `Parameter length for ${method.toUpperCase()} ${path} is wrong`).toHaveLength(manualOperation.parameters.length);
}

const manualPathParams = manualOperation.parameters.filter((param) => param.in === "path");
const generatedPathParams = generatedOperation.parameters.filter((param) => param.in === "path");
expect(generatedPathParams).toHaveLength(manualPathParams.length);

generatedOperation.parameters
.filter((param) => param.in === "query")
.forEach((param) => {
const manualParameter = manualOperation.parameters!.find((manualParam) => manualParam.name === param.name);

expect(manualParameter, `${path} ${method} should contain param with name ${param.name}`).toBeDefined();

expect(param.name).toBe(manualParameter!.name);
expect(param.in).toBe(manualParameter!.in);
expect(param.required).toBe(manualParameter!.required);
});
});
});
});
});

function harmonizeSpec(spec: any) {
Expand All @@ -85,3 +145,13 @@ function harmonizeSpec(spec: any) {
}
}
}

// Paths not defined in the typescript-rest way
const ignorePaths = ["/health", "/Monitoring/Version", "/Monitoring/Requests", "/Monitoring/Support"];
function getPaths(spec: Swagger.Spec) {
return Object.keys(spec.paths).filter((paths) => !ignorePaths.includes(paths));
}

function isOperation(obj: any): obj is Swagger.Operation {
return obj.responses !== undefined;
}

0 comments on commit c1da6ba

Please sign in to comment.