diff --git a/packages/orm/mongoose/src/decorators/virtualRef.spec.ts b/packages/orm/mongoose/src/decorators/virtualRef.spec.ts index 78f5fba42f5..0fdb053ac11 100644 --- a/packages/orm/mongoose/src/decorators/virtualRef.spec.ts +++ b/packages/orm/mongoose/src/decorators/virtualRef.spec.ts @@ -1,8 +1,10 @@ -import {getJsonSchema, Property} from "@tsed/schema"; +import {Default, Format, getJsonSchema, getSpec, Post, Property, ReadOnly, Returns, SpecTypes} from "@tsed/schema"; import {Store} from "@tsed/core"; import {Model} from "@tsed/mongoose"; import {MONGOOSE_SCHEMA} from "../constants"; import {VirtualRef} from "./virtualRef"; +import {Controller} from "@tsed/di"; +import {BodyParams} from "@tsed/platform-params"; describe("@VirtualRef()", () => { describe("when type and foreign value are given", () => { @@ -131,5 +133,132 @@ describe("@VirtualRef()", () => { type: "object" }); }); + it("should generate spec", () => { + @Model({name: "VirtualTestPerson"}) + class TestPerson { + @Property() + name: string; + + @Property() + band: string; + } + + // WHEN + @Model({name: "VirtualTestBand"}) + class TestBand { + @Format("date-time") + @ReadOnly() + createdAt: number = Date.now(); + + @Format("date-time") + @ReadOnly() + updatedAt: number = Date.now(); + + @ReadOnly() + @VirtualRef({ + ref: TestPerson, + foreignField: "foreign", + localField: "test_2", + justOne: true, + options: {} + }) + members: VirtualRef; + } + + @Controller("/") + class MyCtrl { + @Post("/") + @Returns(200, TestBand) + post(@BodyParams() model: TestBand) {} + } + + // THEN + const store = Store.from(TestBand, "members"); + + expect(store.get(MONGOOSE_SCHEMA)).toEqual({ + ref: "VirtualTestPerson", + localField: "test_2", + foreignField: "foreign", + justOne: true, + options: {} + }); + + expect(getSpec(MyCtrl, {specType: SpecTypes.OPENAPI})).toEqual({ + components: { + schemas: { + TestBand: { + properties: { + createdAt: { + format: "date-time", + readOnly: true, + type: "number" + }, + members: { + allOf: [ + { + $ref: "#/components/schemas/TestPerson" + } + ], + readOnly: true + }, + updatedAt: { + format: "date-time", + readOnly: true, + type: "number" + } + }, + type: "object" + }, + TestPerson: { + properties: { + band: { + type: "string" + }, + name: { + type: "string" + } + }, + type: "object" + } + } + }, + paths: { + "/": { + post: { + operationId: "myCtrlPost", + parameters: [], + requestBody: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/TestBand" + } + } + }, + required: false + }, + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/TestBand" + } + } + }, + description: "Success" + } + }, + tags: ["MyCtrl"] + } + } + }, + tags: [ + { + name: "MyCtrl" + } + ] + }); + }); }); }); diff --git a/packages/specs/schema/src/decorators/common/readOnly.spec.ts b/packages/specs/schema/src/decorators/common/readOnly.spec.ts index fad52b268e7..5284cc0199e 100644 --- a/packages/specs/schema/src/decorators/common/readOnly.spec.ts +++ b/packages/specs/schema/src/decorators/common/readOnly.spec.ts @@ -1,6 +1,8 @@ -import {getJsonSchema} from "@tsed/schema"; +import {Format, getJsonSchema, getSpec, Post, Property, Returns, SpecTypes} from "@tsed/schema"; import {expect} from "chai"; import {ReadOnly} from "./readOnly"; +import {Controller} from "@tsed/di"; +import {BodyParams} from "@tsed/platform-params"; describe("@ReadOnly", () => { it("should declare readOnly field", () => { @@ -23,4 +25,215 @@ describe("@ReadOnly", () => { type: "object" }); }); + + it("should declare property as readonly (OS3)", () => { + class TestPerson { + @Property() + name: string; + + @Property() + band: string; + } + + // WHEN + class TestBand { + @Format("date-time") + @ReadOnly() + createdAt: number = Date.now(); + + @Format("date-time") + @ReadOnly() + updatedAt: number = Date.now(); + + @ReadOnly() + @Property(TestPerson) + members: TestPerson; + } + + @Controller("/") + class MyCtrl { + @Post("/") + @Returns(200, TestBand) + post(@BodyParams() model: TestBand) {} + } + + // THEN + expect(getSpec(MyCtrl, {specType: SpecTypes.OPENAPI})).to.deep.equal({ + components: { + schemas: { + TestBand: { + properties: { + createdAt: { + format: "date-time", + readOnly: true, + type: "number" + }, + members: { + allOf: [ + { + $ref: "#/components/schemas/TestPerson" + } + ], + readOnly: true + }, + updatedAt: { + format: "date-time", + readOnly: true, + type: "number" + } + }, + type: "object" + }, + TestPerson: { + properties: { + band: { + type: "string" + }, + name: { + type: "string" + } + }, + type: "object" + } + } + }, + paths: { + "/": { + post: { + operationId: "myCtrlPost", + parameters: [], + requestBody: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/TestBand" + } + } + }, + required: false + }, + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/TestBand" + } + } + }, + description: "Success" + } + }, + tags: ["MyCtrl"] + } + } + }, + tags: [ + { + name: "MyCtrl" + } + ] + }); + }); + it("should declare property as readonly (OS2)", () => { + class TestPerson { + @Property() + name: string; + + @Property() + band: string; + } + + // WHEN + class TestBand { + @Format("date-time") + @ReadOnly() + createdAt: number = Date.now(); + + @Format("date-time") + @ReadOnly() + updatedAt: number = Date.now(); + + @ReadOnly() + @Property(TestPerson) + members: TestPerson; + } + + @Controller("/") + class MyCtrl { + @Post("/") + @Returns(200, TestBand) + post(@BodyParams() model: TestBand) {} + } + + // THEN + expect(getSpec(MyCtrl, {specType: SpecTypes.SWAGGER})).to.deep.equal({ + definitions: { + TestBand: { + properties: { + createdAt: { + format: "date-time", + type: "number" + }, + members: { + allOf: [ + { + $ref: "#/definitions/TestPerson" + } + ], + readOnly: true + }, + updatedAt: { + format: "date-time", + type: "number" + } + }, + type: "object" + }, + TestPerson: { + properties: { + band: { + type: "string" + }, + name: { + type: "string" + } + }, + type: "object" + } + }, + paths: { + "/": { + post: { + operationId: "myCtrlPost", + parameters: [ + { + in: "body", + name: "body", + required: false, + schema: { + $ref: "#/definitions/TestBand" + } + } + ], + produces: ["application/json"], + responses: { + "200": { + description: "Success", + schema: { + $ref: "#/definitions/TestBand" + } + } + }, + tags: ["MyCtrl"] + } + } + }, + tags: [ + { + name: "MyCtrl" + } + ] + }); + }); }); diff --git a/packages/specs/schema/src/utils/ref.ts b/packages/specs/schema/src/utils/ref.ts index 3d76ca93c63..76c233c4939 100644 --- a/packages/specs/schema/src/utils/ref.ts +++ b/packages/specs/schema/src/utils/ref.ts @@ -2,6 +2,7 @@ import {pascalCase} from "change-case"; import type {JsonSchema} from "../domain/JsonSchema"; import {SpecTypes} from "../domain/SpecTypes"; import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions"; +import {cleanObject} from "@tsed/core"; /** * ignore @@ -33,18 +34,29 @@ export function createRef(name: string, schema: JsonSchema, options: JsonSchemaO $ref: `${host}/${name}` }; - if (schema.nullable) { + const nullable = schema.nullable; + const readOnly = schema.get ? schema.get("readOnly") : undefined; + + if (nullable || readOnly) { switch (options.specType) { case SpecTypes.OPENAPI: - return { - nullable: true, + return cleanObject({ + nullable: nullable ? true : undefined, + readOnly: readOnly ? true : undefined, allOf: [ref] - }; + }); case SpecTypes.JSON: - return { - oneOf: [{type: "null"}, ref] - }; + return cleanObject({ + readOnly, + oneOf: [nullable && {type: "null"}, ref].filter(Boolean) + }); case SpecTypes.SWAGGER: // unsupported + if (readOnly) { + return { + readOnly, + allOf: [ref] + }; + } break; } }