UNPKG

@tsed/schema

Version:
1,264 lines (1,210 loc) 34.5 kB
import {join} from "node:path"; import {BodyParams} from "@tsed/platform-params"; import fs from "fs-extra"; import {validateSpec} from "../../test/helpers/validateSpec.js"; import {CollectionOf} from "../decorators/collections/collectionOf.js"; import {AnyOf} from "../decorators/common/anyOf.js"; import {Description} from "../decorators/common/description.js"; import {Min} from "../decorators/common/minimum.js"; import {Name} from "../decorators/common/name.js"; import {Property} from "../decorators/common/property.js"; import {Required} from "../decorators/common/required.js"; import {Consumes} from "../decorators/operations/consumes.js"; import {In} from "../decorators/operations/in.js"; import {OperationPath} from "../decorators/operations/operationPath.js"; import {Path} from "../decorators/operations/path.js"; import {Returns} from "../decorators/operations/returns.js"; import {Post} from "../decorators/operations/route.js"; import {SpecTypes} from "../domain/SpecTypes.js"; import {generateSpec} from "./generateSpec.js"; const rootDir = import.meta.dirname; // automatically replaced by import.meta.dirname on build describe("generateSpec()", () => { describe("OS 3.0.1", () => { it("should generate spec with options", () => { // WHEN @Path("/controller1") class Controller1 { @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} } @Path("/controller2") class Controller2 { @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} } const result = generateSpec({ tokens: [ {token: Controller1, rootPath: "/rest"}, {token: Controller2, rootPath: "/rest"} ], specVersion: "3.0.1", fileSpec: fs.readJSONSync(join(rootDir, "__mock__", "spec.json")) }); expect(result).toEqual({ info: { contact: { email: "apiteam@swagger.io" }, description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", license: { name: "Apache 2.0", url: "http://www.apache.org/licenses/LICENSE-2.0.html" }, termsOfService: "http://swagger.io/terms/", title: "Swagger Petstore", version: "1.0.0" }, openapi: "3.0.1", paths: { "/rest/controller1": { get: { operationId: "controller1Method", parameters: [], responses: { "200": { description: "Success" } }, tags: ["Controller1"] } }, "/rest/controller1/{id}": { get: { operationId: "controller1MethodById", parameters: [ { in: "path", name: "id", required: true, schema: { type: "string" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller1"] } }, "/rest/controller2": { get: { operationId: "controller2Method", parameters: [], responses: { "200": { description: "Success" } }, tags: ["Controller2"] } }, "/rest/controller2/{id}": { get: { operationId: "controller2MethodById", parameters: [ { in: "path", name: "id", required: true, schema: { type: "string" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller2"] } } }, tags: [ { name: "Controller1" }, { name: "Controller2" } ] }); }); it("should generate spec and correctly merge shared model with custom schema", () => { class Model { @AnyOf(Number, Boolean, String, {type: "array", items: {type: "number"}}, {type: "array", items: {type: "string"}}) test: number | boolean | string | number[] | string[]; } @Path("/controller1") class Controller1 { @Post("/post") method(@BodyParams() body: Model) {} } @Path("/controller2") class Controller2 { @Post("/post") method(@BodyParams() body: Model) {} } const result = generateSpec({ tokens: [ {token: Controller1, rootPath: "/rest"}, {token: Controller2, rootPath: "/rest"} ], specVersion: "3.0.1", fileSpec: fs.readJSONSync(join(rootDir, "__mock__", "spec.json")) }); expect(result).toEqual({ info: { contact: { email: "apiteam@swagger.io" }, description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", license: { name: "Apache 2.0", url: "http://www.apache.org/licenses/LICENSE-2.0.html" }, termsOfService: "http://swagger.io/terms/", title: "Swagger Petstore", version: "1.0.0" }, openapi: "3.0.1", paths: { "/rest/controller1/post": { post: { operationId: "controller1Method", parameters: [], requestBody: { content: { "application/json": { schema: { $ref: "#/components/schemas/Model" } } }, required: false }, responses: { "200": { description: "Success" } }, tags: ["Controller1"] } }, "/rest/controller2/post": { post: { operationId: "controller2Method", parameters: [], requestBody: { content: { "application/json": { schema: { $ref: "#/components/schemas/Model" } } }, required: false }, responses: { "200": { description: "Success" } }, tags: ["Controller2"] } } }, tags: [ { name: "Controller1" }, { name: "Controller2" } ], components: { schemas: { Model: { type: "object", properties: { test: { anyOf: [ {type: "number"}, {type: "boolean"}, {type: "string"}, {type: "array", items: {type: "number"}}, {type: "array", items: {type: "string"}} ] } } } } } }); }); it("should generate spec with sorted paths", () => { // WHEN @Path("/controller2") class Controller2 { @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} } @Path("/controller1") class Controller1 { @OperationPath("GET", "/") method2() {} @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} @OperationPath("POST", "/test") method5() {} } const result = generateSpec({ sortPaths: true, tokens: [ {token: Controller2, rootPath: "/rest"}, {token: Controller1, rootPath: "/rest"} ], specVersion: "3.0.1", specPath: join(rootDir, "__mock__", "spec.json") }); expect(result).toMatchSnapshot(); }); }); describe("Swagger 2", () => { it("should generate spec with options", () => { // WHEN @Path("/controller1") class Controller1 { @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} } @Path("/controller2") class Controller2 { @OperationPath("GET", "/:id?") method(@In("path") @Name("id") id: string) {} } const result = generateSpec({ tokens: [ {token: Controller1, rootPath: "/rest"}, {token: Controller2, rootPath: "/rest"} ], fileSpec: fs.readJSONSync(join(rootDir, "__mock__", "spec.json")) }); expect(result).toEqual({ consumes: ["application/json"], info: { contact: { email: "apiteam@swagger.io" }, description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", license: { name: "Apache 2.0", url: "http://www.apache.org/licenses/LICENSE-2.0.html" }, termsOfService: "http://swagger.io/terms/", title: "Swagger Petstore", version: "1.0.0" }, paths: { "/rest/controller1": { get: { operationId: "controller1Method", parameters: [], responses: { "200": { description: "Success" } }, tags: ["Controller1"] } }, "/rest/controller1/{id}": { get: { operationId: "controller1MethodById", parameters: [ { in: "path", name: "id", required: true, type: "string" } ], responses: { "200": { description: "Success" } }, tags: ["Controller1"] } }, "/rest/controller2": { get: { operationId: "controller2Method", parameters: [], responses: { "200": { description: "Success" } }, tags: ["Controller2"] } }, "/rest/controller2/{id}": { get: { operationId: "controller2MethodById", parameters: [ { in: "path", name: "id", required: true, type: "string" } ], responses: { "200": { description: "Success" } }, tags: ["Controller2"] } } }, produces: ["application/json"], securityDefinitions: { calendar_auth: { authorizationUrl: "http://petstore.swagger.io/oauth/dialog", flow: "implicit", scopes: { "read:calendar": "read your pets", "write:calendar": "modify pets in your account" }, type: "oauth2" }, global_auth: { authorizationUrl: "http://petstore.swagger.io/oauth/dialog", flow: "implicit", scopes: { "read:global": "read your pets", "write:global": "modify pets in your account" }, type: "oauth2" } }, swagger: "2.0", tags: [ { name: "Controller1" }, { name: "Controller2" } ] }); }); it("should generate spec with given data", () => { const result = generateSpec({ tokens: [], spec: { produces: ["application/json", "application/octet-stream", "application/xml"] } }); expect(result).toEqual({ swagger: "2.0", info: { title: "Api documentation", version: "1.0.0" }, produces: ["application/json", "application/octet-stream", "application/xml"], consumes: ["application/json"] }); }); it("should generated default spec", () => { // @ts-ignore const result = generateSpec({tokens: []}); expect(result).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, produces: ["application/json"], swagger: "2.0" }); }); it("PATH - should declare all schema correctly", async () => { // WHEN class Controller { @OperationPath("GET", "/") method(@In("path") @Name("basic") basic: string) {} } // THEN const spec = generateSpec({ tokens: [{token: Controller}], specType: SpecTypes.SWAGGER }); expect(await validateSpec(spec)).toBe(true); }); it("PATH - should declare all schema correctly with expression", async () => { // WHEN class Controller { @OperationPath("GET", "/:id?") method(@In("path") id: string) {} } // THEN const spec = generateSpec({ tokens: [{token: Controller}], specType: SpecTypes.SWAGGER }); expect(await validateSpec(spec)).toBe(true); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { get: { operationId: "controllerMethod", parameters: [], responses: { "200": { description: "Success" } }, tags: ["Controller"] } }, "/{id}": { get: { operationId: "controllerMethodById", parameters: [ { in: "path", name: "id", required: true, type: "string" } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("QUERY - should declare all schema correctly (query)", () => { // WHEN class Controller { @OperationPath("GET", "/:id") method(@In("query") @Name("basic") basic: string) {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/{id}": { get: { operationId: "controllerMethod", parameters: [ { in: "path", name: "id", required: true, type: "string" }, { in: "query", name: "basic", required: false, type: "string" } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("QUERY - should declare all schema correctly (query - model)", () => { // WHEN class QueryModel { @Property() id: string; @Property() name: string; } class Controller { @OperationPath("GET", "/:id") method(@In("query") basic: QueryModel) {} } // THEN const spec = generateSpec({ tokens: [{token: Controller}], specType: SpecTypes.SWAGGER }); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/{id}": { get: { operationId: "controllerMethod", parameters: [ { in: "path", name: "id", required: true, type: "string" }, { in: "query", name: "id", required: false, type: "string" }, { in: "query", name: "name", required: false, type: "string" } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("QUERY - should declare all schema correctly (query - array string)", () => { // WHEN class Controller { @OperationPath("GET", "/:id") method(@In("query") @Name("basic") basic: string[]) {} } // THEN const spec = generateSpec({ tokens: [{token: Controller}], specType: SpecTypes.SWAGGER }); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/{id}": { get: { operationId: "controllerMethod", parameters: [ { in: "path", name: "id", required: true, type: "string" }, { collectionFormat: "multi", in: "query", items: { type: "object" }, name: "basic", required: false, type: "array" } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("QUERY - should declare all schema correctly (query - Map)", () => { // WHEN class Controller { @OperationPath("GET", "/:id") method(@In("query") @Name("basic") @CollectionOf(String) basic: Map<string, string>) {} } // THEN const spec = generateSpec({ tokens: [{token: Controller}], specType: SpecTypes.SWAGGER }); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/{id}": { get: { operationId: "controllerMethod", parameters: [ { in: "path", name: "id", required: true, type: "string" }, { additionalProperties: { type: "string" }, in: "query", name: "basic", required: false, type: "object" } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("BODY - should declare all schema correctly (model)", () => { class MyModel { @Property() prop: string; } class Controller { @Consumes("application/json") @OperationPath("POST", "/") method(@In("body") @Required() num: MyModel) {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], definitions: { MyModel: { properties: { prop: { type: "string" } }, type: "object" } }, info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { consumes: ["application/json"], operationId: "controllerMethod", parameters: [ { in: "body", name: "body", required: true, schema: { $ref: "#/definitions/MyModel" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("BODY - should declare all schema correctly (Array - model)", () => { // WHEN class Product { @Property() title: string; } class Controller { @OperationPath("POST", "/") method(@In("body") @CollectionOf(Product) products: Product[]) { return null; } } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], definitions: { Product: { properties: { title: { type: "string" } }, type: "object" } }, info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { consumes: ["application/json"], operationId: "controllerMethod", parameters: [ { in: "body", name: "body", required: false, schema: { items: { $ref: "#/definitions/Product" }, type: "array" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("BODY - should declare all schema correctly (Map - model)", () => { // WHEN class Product { @Property() title: string; } class Controller { @OperationPath("POST", "/") method(@In("body") @CollectionOf(Product) products: Map<string, Product>) { return null; } } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], definitions: { Product: { properties: { title: { type: "string" } }, type: "object" } }, info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { consumes: ["application/json"], operationId: "controllerMethod", parameters: [ { in: "body", name: "body", required: false, schema: { additionalProperties: { $ref: "#/definitions/Product" }, type: "object" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("BODY - should declare all schema correctly (inline)", () => { class Controller { @Consumes("application/json") @OperationPath("POST", "/") method(@In("body") @Required() @Name("num") @Min(0) num: number, @In("body") @Required() @Name("test") @Min(0) num2: number) {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { consumes: ["application/json"], operationId: "controllerMethod", parameters: [ { in: "body", name: "body", required: true, schema: { properties: { num: { minimum: 0, type: "number" }, test: { minimum: 0, type: "number" } }, required: ["num", "test"], type: "object" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("BODY - should declare all schema correctly (Array - inline)", async () => { class Controller { @Consumes("application/json") @OperationPath("POST", "/") method( @In("body") @Required() @Name("num") @CollectionOf(Number) @Min(0) num: number[], @In("body") @Required() @Name("test") @Min(0) num2: number ) {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(await validateSpec(spec)).toBe(true); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { consumes: ["application/json"], operationId: "controllerMethod", parameters: [ { in: "body", name: "body", required: true, schema: { properties: { num: { items: { minimum: 0, type: "number" }, type: "array" }, test: { minimum: 0, type: "number" } }, required: ["num", "test"], type: "object" } } ], responses: { "200": { description: "Success" } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("RESPONSE - should declare all schema correctly", () => { // WHEN @Name("AliasController") @Description("Class description") class Controller { @OperationPath("POST", "/") @(Returns(200, String).Description("description")) method() {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { operationId: "aliasControllerMethod", parameters: [], produces: ["application/octet-stream"], responses: { "200": { description: "description", schema: { type: "string" } } }, tags: ["AliasController"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { description: "Class description", name: "AliasController" } ] }); }); it("RESPONSE - should declare an Array of string", () => { // WHEN class Controller { @OperationPath("POST", "/") @(Returns(200, Array).Of(String).Description("description")) method() {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { operationId: "controllerMethod", parameters: [], produces: ["application/json"], responses: { "200": { description: "description", schema: { items: { type: "string" }, type: "array" } } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); it("HEADERS - should declare a return type with headers", () => { // WHEN class Controller { @OperationPath("POST", "/") @(Returns(200, String) .Description("description") .Header("x-token", "token") .Header("x-header", { value: "" }) .Examples({test: "Examples"}) .Schema({ minLength: 3 })) method() {} } // THEN const spec = generateSpec({tokens: [{token: Controller}], specType: SpecTypes.SWAGGER}); expect(spec).toEqual({ consumes: ["application/json"], info: { title: "Api documentation", version: "1.0.0" }, paths: { "/": { post: { operationId: "controllerMethod", parameters: [], produces: ["application/octet-stream"], responses: { "200": { description: "description", headers: { "x-header": { example: "", type: "string" }, "x-token": { example: "token", type: "string" } }, schema: { minLength: 3, type: "string" } } }, tags: ["Controller"] } } }, produces: ["application/json"], swagger: "2.0", tags: [ { name: "Controller" } ] }); }); }); });