microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
bd60b0a68bb23817a01ab4aef22170c18abd81ed

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/openapi3/test/openapi-output.test.ts

242lines · modecode

1import { resolvePath } from "@cadl-lang/compiler";
2import { expectDiagnosticEmpty } from "@cadl-lang/compiler/testing";
3import { deepStrictEqual, ok, strictEqual } from "assert";
4import { OpenAPI3EmitterOptions } from "../src/lib.js";
5import { OpenAPI3Document } from "../src/types.js";
6import { createOpenAPITestRunner, oapiForModel, openApiFor } from "./test-host.js";
7
8describe("openapi3: types included", () => {
9 async function openapiWithOptions(
10 code: string,
11 options: OpenAPI3EmitterOptions
12 ): Promise<OpenAPI3Document> {
13 const runner = await createOpenAPITestRunner();
14
15 const outPath = resolvePath("/openapi.json");
16
17 const diagnostics = await runner.diagnose(code, {
18 noEmit: false,
19 emit: ["@cadl-lang/openapi3"],
20 options: { "@cadl-lang/openapi3": { ...options, "output-file": outPath } },
21 });
22
23 expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@cadl-lang/rest/no-routes"));
24
25 const content = runner.fs.get(outPath)!;
26 return JSON.parse(content);
27 }
28
29 it("emit unreferenced types by default", async () => {
30 const output = await openapiWithOptions(
31 `
32 model NotReferenced {name: string}
33 model Referenced {name: string}
34
35 op test(): Referenced;
36 `,
37 {}
38 );
39 deepStrictEqual(Object.keys(output.components!.schemas!), ["NotReferenced", "Referenced"]);
40 });
41
42 it("emit only referenced types when using omit-unreachable-types", async () => {
43 const output = await openapiWithOptions(
44 `
45 model NotReferenced {name: string}
46 model Referenced {name: string}
47
48 op test(): Referenced;
49 `,
50 {
51 "omit-unreachable-types": true,
52 }
53 );
54 deepStrictEqual(Object.keys(output.components!.schemas!), ["Referenced"]);
55 });
56});
57
58describe("openapi3: literals", () => {
59 const cases = [
60 ["1", { type: "number", enum: [1] }],
61 ['"hello"', { type: "string", enum: ["hello"] }],
62 ["false", { type: "boolean", enum: [false] }],
63 ["true", { type: "boolean", enum: [true] }],
64 ];
65
66 for (const test of cases) {
67 it("knows schema for " + test[0], async () => {
68 const res = await oapiForModel(
69 "Pet",
70 `
71 model Pet { name: ${test[0]} };
72 `
73 );
74
75 const schema = res.schemas.Pet.properties.name;
76 deepStrictEqual(schema, test[1]);
77 });
78 }
79});
80
81describe("openapi3: operations", () => {
82 it("define operations with param with defaults", async () => {
83 const res = await openApiFor(
84 `
85 @route("/")
86 @get()
87 op read(@query queryWithDefault?: string = "defaultValue"): string;
88 `
89 );
90
91 deepStrictEqual(res.paths["/"].get.parameters[0].schema.default, "defaultValue");
92 });
93
94 it("define operations with param with decorators", async () => {
95 const res = await openApiFor(
96 `
97 @get
98 @route("/thing/{name}")
99 op getThing(
100 @pattern("^[a-zA-Z0-9-]{3,24}$")
101 @path name: string,
102
103 @minValue(1)
104 @maxValue(10)
105 @query count: int32
106 ): string;
107 `
108 );
109
110 const getThing = res.paths["/thing/{name}"].get;
111 ok(getThing);
112 ok(getThing.parameters[0].schema);
113 ok(getThing.parameters[0].schema.pattern);
114 strictEqual(getThing.parameters[0].schema.pattern, "^[a-zA-Z0-9-]{3,24}$");
115
116 ok(getThing.parameters[1].schema);
117 ok(getThing.parameters[1].schema.minimum);
118 ok(getThing.parameters[1].schema.maximum);
119 strictEqual(getThing.parameters[1].schema.minimum, 1);
120 strictEqual(getThing.parameters[1].schema.maximum, 10);
121 });
122
123 it("deprecate operations with @deprecated", async () => {
124 const res = await openApiFor(
125 `
126 @deprecated("use something else")
127 op read(@query query: string): string;
128 `
129 );
130
131 strictEqual(res.paths["/"].get.deprecated, true);
132 });
133});
134
135describe("openapi3: request", () => {
136 describe("binary request", () => {
137 it("bytes request should default to application/json byte", async () => {
138 const res = await openApiFor(`
139 @post op read(@body body: bytes): {};
140 `);
141
142 const requestBody = res.paths["/"].post.requestBody;
143 ok(requestBody);
144 strictEqual(requestBody.content["application/json"].schema.type, "string");
145 strictEqual(requestBody.content["application/json"].schema.format, "byte");
146 });
147
148 it("bytes request should respect @header contentType and use binary format when not json or text", async () => {
149 const res = await openApiFor(`
150 @post op read(@header contentType: "image/png", @body body: bytes): {};
151 `);
152
153 const requestBody = res.paths["/"].post.requestBody;
154 ok(requestBody);
155 strictEqual(requestBody.content["image/png"].schema.type, "string");
156 strictEqual(requestBody.content["image/png"].schema.format, "binary");
157 });
158 });
159});
160
161describe("openapi3: extension decorator", () => {
162 it("adds an arbitrary extension to a model", async () => {
163 const oapi = await openApiFor(`
164 @extension("x-model-extension", "foobar")
165 model Pet {
166 name: string;
167 }
168 @get() op read(): Pet;
169 `);
170 ok(oapi.components.schemas.Pet);
171 strictEqual(oapi.components.schemas.Pet["x-model-extension"], "foobar");
172 });
173
174 it("adds an arbitrary extension to an operation", async () => {
175 const oapi = await openApiFor(
176 `
177 model Pet {
178 name: string;
179 }
180 @get()
181 @extension("x-operation-extension", "barbaz")
182 op list(): Pet[];
183 `
184 );
185 ok(oapi.paths["/"].get);
186 strictEqual(oapi.paths["/"].get["x-operation-extension"], "barbaz");
187 });
188
189 it("adds an arbitrary extension to a parameter", async () => {
190 const oapi = await openApiFor(
191 `
192 model Pet {
193 name: string;
194 }
195 model PetId {
196 @path
197 @extension("x-parameter-extension", "foobaz")
198 petId: string;
199 }
200 @route("/Pets")
201 @get()
202 op get(... PetId): Pet;
203 `
204 );
205 ok(oapi.paths["/Pets/{petId}"].get);
206 strictEqual(
207 oapi.paths["/Pets/{petId}"].get.parameters[0]["$ref"],
208 "#/components/parameters/PetId"
209 );
210 strictEqual(oapi.components.parameters.PetId.name, "petId");
211 strictEqual(oapi.components.parameters.PetId["x-parameter-extension"], "foobaz");
212 });
213
214 it("check format and pattern decorator on model", async () => {
215 const oapi = await openApiFor(
216 `
217 model Pet extends PetId {
218 @pattern("^[a-zA-Z0-9-]{3,24}$")
219 name: string;
220 }
221 model PetId {
222 @path
223 @pattern("^[a-zA-Z0-9-]{3,24}$")
224 @format("UUID")
225 petId: string;
226 }
227 @route("/Pets")
228 @get()
229 op get(... PetId): Pet;
230 `
231 );
232 ok(oapi.paths["/Pets/{petId}"].get);
233 strictEqual(
234 oapi.paths["/Pets/{petId}"].get.parameters[0]["$ref"],
235 "#/components/parameters/PetId"
236 );
237 strictEqual(oapi.components.parameters.PetId.name, "petId");
238 strictEqual(oapi.components.schemas.Pet.properties.name.pattern, "^[a-zA-Z0-9-]{3,24}$");
239 strictEqual(oapi.components.parameters.PetId.schema.format, "UUID");
240 strictEqual(oapi.components.parameters.PetId.schema.pattern, "^[a-zA-Z0-9-]{3,24}$");
241 });
242});
243