@scalar/oas-utils 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/entities/spec/collection.d.ts +6 -12
  3. package/dist/entities/spec/collection.d.ts.map +1 -1
  4. package/dist/entities/spec/collection.js +1 -1
  5. package/dist/entities/spec/collection.js.map +2 -2
  6. package/dist/entities/spec/operation.d.ts +3 -0
  7. package/dist/entities/spec/operation.d.ts.map +1 -1
  8. package/dist/entities/spec/request-examples.js +1 -1
  9. package/dist/entities/spec/request-examples.js.map +2 -2
  10. package/dist/entities/spec/requests.d.ts +8 -0
  11. package/dist/entities/spec/requests.d.ts.map +1 -1
  12. package/dist/entities/spec/requests.js +2 -0
  13. package/dist/entities/spec/requests.js.map +2 -2
  14. package/dist/helpers/http-methods.js +9 -9
  15. package/dist/helpers/http-methods.js.map +1 -1
  16. package/dist/helpers/pretty-print-json.d.ts.map +1 -1
  17. package/dist/helpers/pretty-print-json.js.map +2 -2
  18. package/dist/transforms/import-spec.d.ts +8 -3
  19. package/dist/transforms/import-spec.d.ts.map +1 -1
  20. package/dist/transforms/import-spec.js +30 -9
  21. package/dist/transforms/import-spec.js.map +2 -2
  22. package/package.json +4 -4
  23. package/dist/entities/spec/operation.test.js +0 -43
  24. package/dist/entities/spec/operation.test.js.map +0 -7
  25. package/dist/entities/spec/parameters.test.js +0 -80
  26. package/dist/entities/spec/parameters.test.js.map +0 -7
  27. package/dist/entities/spec/request-example.test.js +0 -636
  28. package/dist/entities/spec/request-example.test.js.map +0 -7
  29. package/dist/entities/spec/server.test.js +0 -120
  30. package/dist/entities/spec/server.test.js.map +0 -7
  31. package/dist/entities/spec/spec-objects.test.js +0 -205
  32. package/dist/entities/spec/spec-objects.test.js.map +0 -7
  33. package/dist/entities/spec/x-scalar-environments.test.js +0 -11
  34. package/dist/entities/spec/x-scalar-environments.test.js.map +0 -7
  35. package/dist/entities/spec/x-scalar-secrets.test.js +0 -11
  36. package/dist/entities/spec/x-scalar-secrets.test.js.map +0 -7
  37. package/dist/helpers/ensure-protocol.test.js +0 -70
  38. package/dist/helpers/ensure-protocol.test.js.map +0 -7
  39. package/dist/helpers/fetch-document.test.js +0 -63
  40. package/dist/helpers/fetch-document.test.js.map +0 -7
  41. package/dist/helpers/find-variables.test.js +0 -20
  42. package/dist/helpers/find-variables.test.js.map +0 -7
  43. package/dist/helpers/is-defined.test.js +0 -37
  44. package/dist/helpers/is-defined.test.js.map +0 -7
  45. package/dist/helpers/is-local-url.test.js +0 -40
  46. package/dist/helpers/is-local-url.test.js.map +0 -7
  47. package/dist/helpers/is-valid-url.test.js +0 -17
  48. package/dist/helpers/is-valid-url.test.js.map +0 -7
  49. package/dist/helpers/json2xml.test.js +0 -19
  50. package/dist/helpers/json2xml.test.js.map +0 -7
  51. package/dist/helpers/make-url-absolute.test.js +0 -61
  52. package/dist/helpers/make-url-absolute.test.js.map +0 -7
  53. package/dist/helpers/merge-urls.test.js +0 -339
  54. package/dist/helpers/merge-urls.test.js.map +0 -7
  55. package/dist/helpers/normalize-mime-type-object.test.js +0 -53
  56. package/dist/helpers/normalize-mime-type-object.test.js.map +0 -7
  57. package/dist/helpers/normalize-mime-type.test.js +0 -33
  58. package/dist/helpers/normalize-mime-type.test.js.map +0 -7
  59. package/dist/helpers/omit-undefined-values.test.js +0 -89
  60. package/dist/helpers/omit-undefined-values.test.js.map +0 -7
  61. package/dist/helpers/parse.test.js +0 -45
  62. package/dist/helpers/parse.test.js.map +0 -7
  63. package/dist/helpers/pretty-print-json.test.js +0 -28
  64. package/dist/helpers/pretty-print-json.test.js.map +0 -7
  65. package/dist/helpers/redirect-to-proxy.test.js +0 -54
  66. package/dist/helpers/redirect-to-proxy.test.js.map +0 -7
  67. package/dist/helpers/regex-helpers.test.js +0 -154
  68. package/dist/helpers/regex-helpers.test.js.map +0 -7
  69. package/dist/helpers/replace-variables.test.js +0 -30
  70. package/dist/helpers/replace-variables.test.js.map +0 -7
  71. package/dist/helpers/security/get-schemes.test.js +0 -71
  72. package/dist/helpers/security/get-schemes.test.js.map +0 -7
  73. package/dist/helpers/security/has-token.test.js +0 -157
  74. package/dist/helpers/security/has-token.test.js.map +0 -7
  75. package/dist/migrations/semver.test.js +0 -21
  76. package/dist/migrations/semver.test.js.map +0 -7
  77. package/dist/migrations/v-2.4.0/migration.test.js +0 -90
  78. package/dist/migrations/v-2.4.0/migration.test.js.map +0 -7
  79. package/dist/migrations/v-2.5.0/migration.test.js +0 -108
  80. package/dist/migrations/v-2.5.0/migration.test.js.map +0 -7
  81. package/dist/spec-getters/get-example-from-schema.test.js +0 -1092
  82. package/dist/spec-getters/get-example-from-schema.test.js.map +0 -7
  83. package/dist/spec-getters/get-parameters-from-operation.test.js +0 -178
  84. package/dist/spec-getters/get-parameters-from-operation.test.js.map +0 -7
  85. package/dist/spec-getters/get-request-body-from-operation.test.js +0 -289
  86. package/dist/spec-getters/get-request-body-from-operation.test.js.map +0 -7
  87. package/dist/transforms/import-spec.test.js +0 -1124
  88. package/dist/transforms/import-spec.test.js.map +0 -7
@@ -1,1124 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import circular from "@test/fixtures/basic-circular-spec.json";
3
- import modifiedPetStoreExample from "@test/fixtures/petstore-tls.json";
4
- import galaxy from "../../../galaxy/dist/latest.json";
5
- import { getSelectedSecuritySchemeUids, importSpecToWorkspace, parseSchema } from "./import-spec.js";
6
- describe("getSelectedSecuritySchemeUids", () => {
7
- const securitySchemeMap = {
8
- "basic-auth": "basic-uid",
9
- "api-key": "apikey-uid",
10
- "oauth2": "oauth-uid"
11
- };
12
- it("should return first security requirement when no preferred scheme is provided", () => {
13
- const securityRequirements = ["basic-auth", "api-key"];
14
- const result = getSelectedSecuritySchemeUids(securityRequirements, void 0, securitySchemeMap);
15
- expect(result).toEqual(["basic-uid"]);
16
- });
17
- it("should use preferred security scheme when available and valid", () => {
18
- const securityRequirements = ["basic-auth", "api-key"];
19
- const result = getSelectedSecuritySchemeUids(securityRequirements, ["api-key"], securitySchemeMap);
20
- expect(result).toEqual(["apikey-uid"]);
21
- });
22
- it("preferred security scheme should override when not in requirements", () => {
23
- const securityRequirements = ["basic-auth", "api-key"];
24
- const result = getSelectedSecuritySchemeUids(securityRequirements, ["oauth2"], securitySchemeMap);
25
- expect(result).toEqual(["oauth-uid"]);
26
- });
27
- it("should set the preferred security scheme when no requirements are provided", () => {
28
- const result = getSelectedSecuritySchemeUids([], ["basic-auth"], securitySchemeMap);
29
- expect(result).toEqual(["basic-uid"]);
30
- });
31
- it("should select multiple security schemes when preferred scheme is an array", () => {
32
- const securityRequirements = ["basic-auth", "api-key"];
33
- const result = getSelectedSecuritySchemeUids(securityRequirements, ["basic-auth", "api-key"], securitySchemeMap);
34
- expect(result).toEqual(["basic-uid", "apikey-uid"]);
35
- });
36
- it("should select multiple security schemes when preferred scheme is an array including complex", () => {
37
- const securityRequirements = ["basic-auth", "api-key", ["basic-auth", "api-key", "oauth2"]];
38
- const result = getSelectedSecuritySchemeUids(
39
- securityRequirements,
40
- [["basic-auth", "api-key", "oauth2"], "api-key"],
41
- securitySchemeMap
42
- );
43
- expect(result).toEqual([["basic-uid", "apikey-uid", "oauth-uid"], "apikey-uid"]);
44
- });
45
- it("should handle array-type security requirements", () => {
46
- const securityRequirements = [["basic-auth", "api-key"]];
47
- const result = getSelectedSecuritySchemeUids(securityRequirements, void 0, securitySchemeMap);
48
- expect(result).toEqual([["basic-uid", "apikey-uid"]]);
49
- });
50
- it("should handle empty security requirements", () => {
51
- const securityRequirements = [];
52
- const result = getSelectedSecuritySchemeUids(securityRequirements, void 0, securitySchemeMap);
53
- expect(result).toEqual([]);
54
- });
55
- it("should handle undefined preferred scheme", () => {
56
- const securityRequirements = ["basic-auth"];
57
- const result = getSelectedSecuritySchemeUids(securityRequirements, void 0, securitySchemeMap);
58
- expect(result).toEqual(["basic-uid"]);
59
- });
60
- });
61
- describe("importSpecToWorkspace", () => {
62
- const findSchemeUidByKey = (key, securitySchemes) => securitySchemes.find((s) => s.nameKey === key)?.uid;
63
- describe("basics", () => {
64
- it("handles circular references", async () => {
65
- const res = await importSpecToWorkspace(circular);
66
- if (res.error) {
67
- return;
68
- }
69
- expect(res.requests[0]?.path).toEqual("/api/v1/updateEmployee");
70
- expect(res.tags[0]?.children.includes(res.tags[1].uid)).toEqual(true);
71
- expect(res.tags[0]?.children.includes(Object.values(res.requests)[0].uid)).toEqual(true);
72
- });
73
- it("handles a weird Petstore example", async () => {
74
- const res = await importSpecToWorkspace(modifiedPetStoreExample);
75
- expect(res.error).toBe(false);
76
- });
77
- it("loads the Scalar Galaxy example (with cyclic dependencies)", async () => {
78
- const res = await importSpecToWorkspace(galaxy);
79
- expect(res.error).toEqual(false);
80
- });
81
- it("merges path and operation parameters", async () => {
82
- const specWithParams = {
83
- ...galaxy,
84
- paths: {
85
- "/test/{id}": {
86
- parameters: [{ name: "id", in: "path", required: true }],
87
- get: {
88
- parameters: [{ name: "filter", in: "query" }]
89
- }
90
- }
91
- }
92
- };
93
- const res = await importSpecToWorkspace(specWithParams);
94
- if (res.error) {
95
- throw res.error;
96
- }
97
- const request = res.requests[0];
98
- expect(request.parameters).toHaveLength(2);
99
- expect(request.parameters?.map((p) => p.name)).toContain("id");
100
- expect(request.parameters?.map((p) => p.name)).toContain("filter");
101
- });
102
- it("handles requests in the order defined in the OpenAPI document", async () => {
103
- const res = await importSpecToWorkspace(galaxy);
104
- if (res.error) {
105
- throw res.error;
106
- }
107
- expect(res.requests.map((r) => r.operationId)).toEqual([
108
- "getAllData",
109
- "createPlanet",
110
- "getPlanet",
111
- "updatePlanet",
112
- "deletePlanet",
113
- "uploadImage",
114
- "createUser",
115
- "getToken",
116
- "getMe"
117
- ]);
118
- });
119
- });
120
- describe("info", () => {
121
- it("handles missing info object", async () => {
122
- const result = await importSpecToWorkspace({
123
- openapi: "3.1.0"
124
- // Missing info object
125
- });
126
- expect(result.error).toBe(false);
127
- expect(result.collection?.info).toEqual({
128
- title: "API",
129
- version: "1.0"
130
- });
131
- });
132
- it("handles wrong format for info.title", async () => {
133
- const result = await importSpecToWorkspace({
134
- openapi: "3.1.0",
135
- info: {
136
- title: 123
137
- }
138
- });
139
- expect(result.error).toBe(false);
140
- expect(result.collection?.info).toEqual({
141
- title: "API",
142
- version: "1.0"
143
- });
144
- });
145
- it("handles wrong format for info.version", async () => {
146
- const result = await importSpecToWorkspace({
147
- openapi: "3.1.0",
148
- info: {
149
- title: "My API",
150
- version: 123
151
- }
152
- });
153
- expect(result.error).toBe(false);
154
- expect(result.collection?.info).toEqual({
155
- title: "My API",
156
- version: "1.0"
157
- });
158
- });
159
- describe("contact", () => {
160
- it("leaves invalid email in contact object as is", async () => {
161
- const result = await importSpecToWorkspace({
162
- openapi: "3.1.0",
163
- info: {
164
- contact: {
165
- name: "John Doe",
166
- url: "not-actually-an-url",
167
- email: "invalid @ email.com"
168
- }
169
- }
170
- });
171
- expect(result.error).toBe(false);
172
- expect(result.collection?.info?.contact).toEqual({
173
- name: "John Doe",
174
- email: "invalid @ email.com"
175
- });
176
- });
177
- });
178
- it("throws away the contact if it's not even strings", async () => {
179
- const result = await importSpecToWorkspace({
180
- openapi: "3.1.0",
181
- info: {
182
- contact: {
183
- name: 123
184
- }
185
- }
186
- });
187
- expect(result.collection?.info?.contact).toEqual(void 0);
188
- });
189
- it("deals with incomeplete contact object", async () => {
190
- const result = await importSpecToWorkspace({
191
- openapi: "3.1.0",
192
- info: {
193
- contact: {
194
- name: "John Doe"
195
- }
196
- }
197
- });
198
- expect(result.collection?.info?.contact).toEqual({
199
- name: "John Doe"
200
- });
201
- });
202
- it("ignores additional properties in the contact object", async () => {
203
- const result = await importSpecToWorkspace({
204
- openapi: "3.1.0",
205
- info: {
206
- contact: {
207
- name: "John Doe",
208
- extra: "extra"
209
- }
210
- }
211
- });
212
- expect(result.collection?.info?.contact).toEqual({
213
- name: "John Doe"
214
- });
215
- });
216
- });
217
- describe("tags", () => {
218
- it("creates missing tag definitions", async () => {
219
- const specWithUndefinedTags = {
220
- ...galaxy,
221
- tags: [],
222
- paths: {
223
- "/test": {
224
- get: {
225
- tags: ["undefined-tag"]
226
- }
227
- }
228
- }
229
- };
230
- const res = await importSpecToWorkspace(specWithUndefinedTags);
231
- if (res.error) {
232
- throw res.error;
233
- }
234
- expect(res.tags.some((t) => t.name === "undefined-tag")).toBe(true);
235
- });
236
- it("handles requests without tags", async () => {
237
- const specWithUntaggedRequest = {
238
- ...galaxy,
239
- paths: {
240
- "/test": {
241
- get: {
242
- // No tags specified
243
- operationId: "untaggedRequest"
244
- }
245
- }
246
- }
247
- };
248
- const res = await importSpecToWorkspace(specWithUntaggedRequest);
249
- if (res.error) {
250
- throw res.error;
251
- }
252
- expect(res.collection.children).toContain(res.requests[0].uid);
253
- });
254
- });
255
- describe("security", () => {
256
- const testSchemes = [
257
- {
258
- bearerFormat: "JWT",
259
- description: "JWT Bearer token authentication",
260
- nameKey: "bearerAuth",
261
- password: "",
262
- scheme: "bearer",
263
- token: "",
264
- type: "http",
265
- username: ""
266
- },
267
- {
268
- bearerFormat: "JWT",
269
- description: "Basic HTTP authentication",
270
- nameKey: "basicAuth",
271
- password: "",
272
- scheme: "basic",
273
- token: "",
274
- type: "http",
275
- username: ""
276
- },
277
- {
278
- description: "API key request header",
279
- in: "header",
280
- name: "X-API-Key",
281
- nameKey: "apiKeyHeader",
282
- type: "apiKey",
283
- value: ""
284
- },
285
- {
286
- description: "API key query parameter",
287
- in: "query",
288
- name: "api_key",
289
- nameKey: "apiKeyQuery",
290
- type: "apiKey",
291
- value: ""
292
- },
293
- {
294
- description: "API key browser cookie",
295
- in: "cookie",
296
- name: "api_key",
297
- nameKey: "apiKeyCookie",
298
- type: "apiKey",
299
- value: ""
300
- },
301
- {
302
- description: "OAuth 2.0 authentication",
303
- nameKey: "oAuth2",
304
- type: "oauth2",
305
- flows: {
306
- authorizationCode: {
307
- "authorizationUrl": "https://galaxy.scalar.com/oauth/authorize",
308
- "clientSecret": "",
309
- "refreshUrl": "",
310
- "scopes": {
311
- "read:account": "read your account information",
312
- "read:planets": "read your planets",
313
- "write:planets": "modify planets in your account"
314
- },
315
- "selectedScopes": [],
316
- "token": "",
317
- "tokenUrl": "https://galaxy.scalar.com/oauth/token",
318
- "type": "authorizationCode",
319
- "x-scalar-redirect-uri": "http://localhost:3000/",
320
- "x-usePkce": "no",
321
- "x-scalar-client-id": ""
322
- },
323
- clientCredentials: {
324
- "clientSecret": "",
325
- "refreshUrl": "",
326
- "scopes": {
327
- "read:account": "read your account information",
328
- "read:planets": "read your planets",
329
- "write:planets": "modify planets in your account"
330
- },
331
- "selectedScopes": [],
332
- "token": "",
333
- "tokenUrl": "https://galaxy.scalar.com/oauth/token",
334
- "type": "clientCredentials",
335
- "x-scalar-client-id": ""
336
- },
337
- implicit: {
338
- "authorizationUrl": "https://galaxy.scalar.com/oauth/authorize",
339
- "refreshUrl": "",
340
- "scopes": {
341
- "read:account": "read your account information",
342
- "read:planets": "read your planets",
343
- "write:planets": "modify planets in your account"
344
- },
345
- "selectedScopes": [],
346
- "token": "",
347
- "type": "implicit",
348
- "x-scalar-client-id": "",
349
- "x-scalar-redirect-uri": "http://localhost:3000/"
350
- },
351
- password: {
352
- "clientSecret": "",
353
- "password": "",
354
- "refreshUrl": "",
355
- "scopes": {
356
- "read:account": "read your account information",
357
- "read:planets": "read your planets",
358
- "write:planets": "modify planets in your account"
359
- },
360
- "selectedScopes": [],
361
- "token": "",
362
- "tokenUrl": "https://galaxy.scalar.com/oauth/token",
363
- "type": "password",
364
- "username": "",
365
- "x-scalar-client-id": ""
366
- }
367
- }
368
- },
369
- {
370
- description: "OpenID Connect Authentication",
371
- nameKey: "openIdConnect",
372
- openIdConnectUrl: "https://galaxy.scalar.com/.well-known/openid-configuration",
373
- type: "openIdConnect"
374
- }
375
- ];
376
- it("handles vanilla security schemes", async () => {
377
- const res = await importSpecToWorkspace(galaxy);
378
- if (res.error) {
379
- throw res.error;
380
- }
381
- expect(res.securitySchemes.map(({ uid, ...rest }) => rest)).toEqual(testSchemes);
382
- });
383
- it("supports the x-defaultClientId extension", async () => {
384
- const clonedGalaxy = structuredClone(galaxy);
385
- clonedGalaxy.components.securitySchemes.oAuth2.flows.authorizationCode["x-defaultClientId"] = "test-default-client-id";
386
- const res = await importSpecToWorkspace(clonedGalaxy);
387
- if (res.error) {
388
- throw res.error;
389
- }
390
- const authScheme = res.securitySchemes[5];
391
- expect(authScheme.flows.authorizationCode?.["x-scalar-client-id"]).toEqual("test-default-client-id");
392
- });
393
- it("prefills from the new authentication config", async () => {
394
- const res = await importSpecToWorkspace(galaxy, {
395
- authentication: {
396
- preferredSecurityScheme: "apiKeyHeader",
397
- securitySchemes: {
398
- apiKeyHeader: {
399
- value: "tokenHeader"
400
- },
401
- apiKeyQuery: {
402
- value: "tokenQuery"
403
- },
404
- apiKeyCookie: {
405
- value: "tokenCookie"
406
- },
407
- bearerAuth: {
408
- token: "xyz token value"
409
- },
410
- basicAuth: {
411
- username: "username",
412
- password: "password"
413
- },
414
- oAuth2: {
415
- flows: {
416
- authorizationCode: {
417
- token: "auth code token"
418
- },
419
- clientCredentials: {
420
- token: "client credentials token"
421
- },
422
- implicit: {
423
- token: "implicit token"
424
- },
425
- password: {
426
- username: "user",
427
- password: "pass",
428
- token: "user pass token"
429
- }
430
- }
431
- }
432
- }
433
- },
434
- useCollectionSecurity: true
435
- });
436
- if (res.error) {
437
- throw res.error;
438
- }
439
- const clonedSchemes = structuredClone(testSchemes);
440
- clonedSchemes[0].token = "xyz token value";
441
- clonedSchemes[1].username = "username";
442
- clonedSchemes[1].password = "password";
443
- clonedSchemes[2].value = "tokenHeader";
444
- clonedSchemes[3].value = "tokenQuery";
445
- clonedSchemes[4].value = "tokenCookie";
446
- const flows = clonedSchemes[5].flows;
447
- flows.authorizationCode.token = "auth code token";
448
- flows.password.token = "user pass token";
449
- flows.password.username = "user";
450
- flows.password.password = "pass";
451
- flows.clientCredentials.token = "client credentials token";
452
- flows.implicit.token = "implicit token";
453
- const apiKey = res.securitySchemes.find(({ nameKey }) => nameKey === "apiKeyHeader");
454
- expect(res.securitySchemes.map(({ uid, ...rest }) => rest)).toEqual(clonedSchemes);
455
- expect(res.collection.selectedSecuritySchemeUids).toEqual([apiKey.uid]);
456
- });
457
- it("prefills from the deprecated authentication property", async () => {
458
- const res = await importSpecToWorkspace(galaxy, {
459
- authentication: {
460
- apiKey: {
461
- token: "test-api-key"
462
- },
463
- preferredSecurityScheme: "apiKeyHeader",
464
- http: {
465
- basic: {
466
- username: "test-username",
467
- password: "test-password"
468
- },
469
- bearer: {
470
- token: "test-bearer-token"
471
- }
472
- },
473
- oAuth2: {
474
- clientId: "test-client-id",
475
- scopes: ["read:account", "read:planets"],
476
- accessToken: "test-access-token",
477
- state: "test-state",
478
- username: "test-username",
479
- password: "test-password"
480
- }
481
- },
482
- useCollectionSecurity: true
483
- });
484
- if (res.error) {
485
- throw res.error;
486
- }
487
- const clonedSchemes = structuredClone(testSchemes);
488
- clonedSchemes[0].token = "test-bearer-token";
489
- clonedSchemes[1].username = "test-username";
490
- clonedSchemes[1].password = "test-password";
491
- clonedSchemes[2].value = "test-api-key";
492
- clonedSchemes[3].value = "test-api-key";
493
- clonedSchemes[4].value = "test-api-key";
494
- const flows = clonedSchemes[5].flows;
495
- flows.authorizationCode["x-scalar-client-id"] = "test-client-id";
496
- flows.authorizationCode.token = "test-access-token";
497
- flows.authorizationCode.selectedScopes = ["read:account", "read:planets"];
498
- flows.clientCredentials["x-scalar-client-id"] = "test-client-id";
499
- flows.clientCredentials.token = "test-access-token";
500
- flows.clientCredentials.selectedScopes = ["read:account", "read:planets"];
501
- flows.implicit["x-scalar-client-id"] = "test-client-id";
502
- flows.implicit.token = "test-access-token";
503
- flows.implicit.selectedScopes = ["read:account", "read:planets"];
504
- flows.password["x-scalar-client-id"] = "test-client-id";
505
- flows.password.token = "test-access-token";
506
- flows.password.selectedScopes = ["read:account", "read:planets"];
507
- flows.password.username = "test-username";
508
- flows.password.password = "test-password";
509
- const apiKey = res.securitySchemes.find(({ nameKey }) => nameKey === "apiKeyHeader");
510
- expect(res.securitySchemes.map(({ uid, ...rest }) => rest)).toEqual(clonedSchemes);
511
- expect(res.collection.selectedSecuritySchemeUids).toEqual([apiKey.uid]);
512
- });
513
- it("converts scope arrays to objects", async () => {
514
- const clonedGalaxy = structuredClone(galaxy);
515
- clonedGalaxy.components.securitySchemes.oAuth2.flows.authorizationCode.scopes = ["read:account", "read:planets"];
516
- const res = await importSpecToWorkspace(clonedGalaxy);
517
- if (res.error) {
518
- throw res.error;
519
- }
520
- expect(res.securitySchemes[5].flows.authorizationCode.scopes).toEqual({
521
- "read:account": "",
522
- "read:planets": ""
523
- });
524
- });
525
- it("handles empty security requirements", async () => {
526
- const specWithEmptySecurity = {
527
- ...galaxy,
528
- security: [{}],
529
- paths: {
530
- "/test": {
531
- get: {
532
- security: [{}]
533
- }
534
- }
535
- }
536
- };
537
- const res = await importSpecToWorkspace(specWithEmptySecurity);
538
- if (res.error) {
539
- throw res.error;
540
- }
541
- expect(res.requests[0].security).toEqual([{}]);
542
- });
543
- it("handles empty security requirements with preferred security scheme", async () => {
544
- const specWithEmptySecurity = {
545
- ...galaxy,
546
- security: [{}],
547
- paths: {
548
- "/test": {
549
- get: {
550
- security: [{}]
551
- }
552
- }
553
- }
554
- };
555
- const res = await importSpecToWorkspace(specWithEmptySecurity, {
556
- useCollectionSecurity: true,
557
- authentication: {
558
- preferredSecurityScheme: "basicAuth"
559
- }
560
- });
561
- if (res.error) {
562
- throw res.error;
563
- }
564
- const scheme = res.securitySchemes.find((s) => s.nameKey === "basicAuth");
565
- expect(res.collection.selectedSecuritySchemeUids).toEqual([scheme?.uid]);
566
- });
567
- it("handles empty operation security requirements", async () => {
568
- const res = await importSpecToWorkspace({
569
- ...galaxy,
570
- paths: {
571
- "/test": {
572
- get: { security: [] }
573
- }
574
- }
575
- });
576
- if (res.error) {
577
- throw res.error;
578
- }
579
- expect(res.requests[0].security).toEqual([]);
580
- });
581
- it("imports path-level parameters", async () => {
582
- const example = {
583
- paths: {
584
- "/foobar": {
585
- get: {
586
- // operation-level parameter
587
- parameters: [{ name: "bar", in: "path" }]
588
- },
589
- // path-level parameter
590
- parameters: [{ name: "foo", in: "path" }]
591
- }
592
- }
593
- };
594
- const res = await importSpecToWorkspace(example);
595
- if (res.error) {
596
- throw res.error;
597
- }
598
- expect(res.requests[0].parameters).toMatchObject([
599
- { name: "foo", in: "path" },
600
- { name: "bar", in: "path" }
601
- ]);
602
- });
603
- it("prefers operation level security over global security", async () => {
604
- const specWithGlobalAndOperationSecurity = {
605
- ...galaxy,
606
- security: [{ bearerAuth: [] }],
607
- paths: {
608
- "/test": {
609
- get: {
610
- security: [{ basicAuth: [] }]
611
- }
612
- }
613
- }
614
- };
615
- const res = await importSpecToWorkspace(specWithGlobalAndOperationSecurity);
616
- if (res.error) {
617
- throw res.error;
618
- }
619
- expect(res.requests[0].security).toEqual([{ basicAuth: [] }]);
620
- });
621
- it("sets collection level security when specified", async () => {
622
- const res = await importSpecToWorkspace(galaxy, {
623
- useCollectionSecurity: true,
624
- authentication: {
625
- preferredSecurityScheme: "basicAuth"
626
- }
627
- });
628
- if (res.error) {
629
- throw res.error;
630
- }
631
- expect(res.collection.selectedSecuritySchemeUids).toHaveLength(1);
632
- const scheme = res.securitySchemes.find((s) => s.uid === res.collection.selectedSecuritySchemeUids[0]);
633
- expect(scheme?.nameKey).toBe("basicAuth");
634
- });
635
- it("handles oauth2 authentication configuration", async () => {
636
- const res = await importSpecToWorkspace(galaxy, {
637
- authentication: {
638
- oAuth2: {
639
- clientId: "test-client",
640
- scopes: ["read:account"]
641
- }
642
- }
643
- });
644
- if (res.error) {
645
- throw res.error;
646
- }
647
- const flow = res.securitySchemes.find((s) => s.type === "oauth2")?.flows.authorizationCode;
648
- expect(flow?.["x-scalar-client-id"]).toBe("test-client");
649
- expect(flow?.selectedScopes).toEqual(["read:account"]);
650
- });
651
- it("handles an optional security scheme and sets selected security accordingly", async () => {
652
- const specWithAndSecurity = {
653
- ...galaxy,
654
- paths: {
655
- "/test": {
656
- get: {
657
- security: [{}],
658
- operationId: "testOperation"
659
- }
660
- }
661
- }
662
- };
663
- const res = await importSpecToWorkspace(specWithAndSecurity);
664
- if (res.error) {
665
- throw res.error;
666
- }
667
- expect(res.requests[0].selectedSecuritySchemeUids).toEqual([]);
668
- });
669
- it("sets the correct selectedSecuritySchemeUids when theres no collection security requirement", async () => {
670
- const { security, ...noSecurity } = galaxy;
671
- const res = await importSpecToWorkspace(noSecurity, {
672
- useCollectionSecurity: true
673
- });
674
- if (res.error) {
675
- throw res.error;
676
- }
677
- expect(res.collection.selectedSecuritySchemeUids).toEqual([]);
678
- });
679
- });
680
- describe("complex security", () => {
681
- it("handles AND security requirements", async () => {
682
- const specWithAndSecurity = {
683
- ...galaxy,
684
- security: [{ apiKeyHeader: [], basicAuth: [] }],
685
- paths: {
686
- "/test": {
687
- get: {
688
- operationId: "testOperation"
689
- }
690
- }
691
- }
692
- };
693
- const res = await importSpecToWorkspace(specWithAndSecurity);
694
- if (res.error) {
695
- throw res.error;
696
- }
697
- const selectedSecuritySchemeUids = [
698
- [findSchemeUidByKey("apiKeyHeader", res.securitySchemes), findSchemeUidByKey("basicAuth", res.securitySchemes)]
699
- ];
700
- expect(res.requests[0].selectedSecuritySchemeUids).toEqual(selectedSecuritySchemeUids);
701
- });
702
- it("handles AND security requirements with useCollectionSecurity", async () => {
703
- const specWithAndSecurity = {
704
- ...galaxy,
705
- security: [{ apiKeyHeader: [], basicAuth: [] }, { bearerAuth: [] }],
706
- paths: {
707
- "/test": {
708
- get: {
709
- operationId: "testOperation"
710
- }
711
- }
712
- }
713
- };
714
- const res = await importSpecToWorkspace(specWithAndSecurity, {
715
- useCollectionSecurity: true
716
- });
717
- if (res.error) {
718
- throw res.error;
719
- }
720
- const selectedSecuritySchemeUids = [
721
- [findSchemeUidByKey("apiKeyHeader", res.securitySchemes), findSchemeUidByKey("basicAuth", res.securitySchemes)]
722
- ];
723
- expect(res.collection.selectedSecuritySchemeUids).toEqual(selectedSecuritySchemeUids);
724
- });
725
- it("selects the first required scheme as selected", async () => {
726
- const specWithOrSecurity = {
727
- ...galaxy,
728
- security: [{ apiKeyHeader: [] }, { basicAuth: [] }],
729
- // Either one
730
- paths: {
731
- "/test": {
732
- get: {
733
- operationId: "testOperation"
734
- }
735
- }
736
- }
737
- };
738
- const res = await importSpecToWorkspace(specWithOrSecurity);
739
- if (res.error) {
740
- throw res.error;
741
- }
742
- expect(res.requests[0].selectedSecuritySchemeUids).toEqual([
743
- findSchemeUidByKey("apiKeyHeader", res.securitySchemes)
744
- ]);
745
- });
746
- it("handles empty security requirement in combination", async () => {
747
- const specWithOptionalAndRequired = {
748
- ...galaxy,
749
- security: [
750
- { apiKeyHeader: [] },
751
- {}
752
- // Optional - no auth required
753
- ],
754
- paths: {
755
- "/test": {
756
- get: {
757
- operationId: "testOperation"
758
- }
759
- }
760
- }
761
- };
762
- const res = await importSpecToWorkspace(specWithOptionalAndRequired);
763
- if (res.error) {
764
- throw res.error;
765
- }
766
- expect(res.requests[0].selectedSecuritySchemeUids).toEqual([
767
- findSchemeUidByKey("apiKeyHeader", res.securitySchemes)
768
- ]);
769
- });
770
- });
771
- describe("servers", () => {
772
- it("handles servers with different formats", async () => {
773
- const originalLocation = typeof window !== "undefined" ? window.location : { origin: void 0 };
774
- vi.stubGlobal("window", {
775
- location: {
776
- origin: "http://localhost:3000"
777
- }
778
- });
779
- const result = await importSpecToWorkspace({
780
- servers: [
781
- { url: "https://api.example.com" },
782
- // Absolute URL
783
- { url: "/v2/api" },
784
- // Relative path
785
- {
786
- url: "{scheme}://{environment}.api.example.com",
787
- // URL with variables
788
- variables: {
789
- scheme: {
790
- default: "https",
791
- enum: ["http", "https"]
792
- },
793
- environment: {
794
- default: "prod",
795
- enum: ["dev", "staging", "prod"]
796
- }
797
- }
798
- }
799
- ]
800
- });
801
- if (result.error) {
802
- throw result.error;
803
- }
804
- expect(result.servers).toMatchObject([
805
- { url: "https://api.example.com" },
806
- { url: "http://localhost:3000/v2/api" },
807
- {
808
- url: "{scheme}://{environment}.api.example.com",
809
- variables: {
810
- scheme: {
811
- default: "https",
812
- enum: ["http", "https"]
813
- },
814
- environment: {
815
- default: "prod",
816
- enum: ["dev", "staging", "prod"]
817
- }
818
- }
819
- }
820
- ]);
821
- vi.stubGlobal("location", originalLocation);
822
- });
823
- it("returns both collection and operation servers when present", async () => {
824
- const result = await importSpecToWorkspace({
825
- servers: [{ url: "https://collection-server.com" }],
826
- paths: {
827
- "/test": {
828
- get: {
829
- servers: [{ url: "https://operation-server.com" }]
830
- }
831
- }
832
- }
833
- });
834
- if (result.error) {
835
- throw result.error;
836
- }
837
- expect(result.servers).toHaveLength(2);
838
- expect(result.servers.map((s) => s.url)).toContain("https://collection-server.com");
839
- expect(result.servers.map((s) => s.url)).toContain("https://operation-server.com");
840
- });
841
- });
842
- });
843
- describe("parseSchema", () => {
844
- it("handles valid OpenAPI spec", async () => {
845
- const input = {
846
- openapi: "3.1.0",
847
- info: {
848
- title: "Test API",
849
- version: "1.0.0"
850
- },
851
- paths: {
852
- "/test": {
853
- get: {
854
- operationId: "getTest",
855
- responses: {
856
- "200": {
857
- description: "Success"
858
- }
859
- }
860
- }
861
- }
862
- }
863
- };
864
- const { schema, errors } = await parseSchema(input);
865
- expect(errors).toHaveLength(0);
866
- expect(schema).toMatchObject({
867
- openapi: "3.1.0",
868
- info: {
869
- title: "Test API",
870
- version: "1.0.0"
871
- },
872
- paths: {
873
- "/test": {
874
- get: {
875
- operationId: "getTest"
876
- }
877
- }
878
- }
879
- });
880
- });
881
- it("handles internal references", async () => {
882
- const input = {
883
- openapi: "3.1.0",
884
- info: {
885
- title: "Test API",
886
- version: "1.0.0"
887
- },
888
- components: {
889
- schemas: {
890
- Foobar: {
891
- type: "object",
892
- properties: {
893
- name: { type: "string" }
894
- }
895
- }
896
- }
897
- },
898
- paths: {
899
- "/foobar": {
900
- get: {
901
- responses: {
902
- "200": {
903
- description: "Success",
904
- content: {
905
- "application/json": {
906
- schema: {
907
- $ref: "#/components/schemas/Foobar"
908
- }
909
- }
910
- }
911
- }
912
- }
913
- }
914
- }
915
- }
916
- };
917
- const { schema, errors } = await parseSchema(input);
918
- expect(errors).toHaveLength(0);
919
- expect(schema.components?.schemas?.Foobar).toMatchObject({
920
- type: "object",
921
- properties: {
922
- name: { type: "string" }
923
- }
924
- });
925
- expect(schema.paths?.["/foobar"]?.get?.responses?.["200"]?.content?.["application/json"]?.schema).toMatchObject({
926
- type: "object",
927
- properties: {
928
- name: { type: "string" }
929
- }
930
- });
931
- });
932
- it("handles invalid JSON", async () => {
933
- const { errors } = await parseSchema('"invalid');
934
- expect(errors).toMatchObject([{ code: "MISSING_CHAR" }]);
935
- expect(errors).toHaveLength(1);
936
- });
937
- it("handles invalid YAML", async () => {
938
- const { errors } = await parseSchema(`
939
-
940
- openapi: 3.1.0
941
- asd
942
- `);
943
- expect(errors).toMatchObject([{ code: "MISSING_CHAR" }]);
944
- expect(errors).toHaveLength(1);
945
- });
946
- });
947
- describe("getServersFromOpenApiDocument", () => {
948
- it("parses a simple server", async () => {
949
- const result = await importSpecToWorkspace({
950
- servers: [{ url: "https://example.com" }]
951
- });
952
- if (result.error) {
953
- throw result.error;
954
- }
955
- expect(result.servers).toMatchObject([{ url: "https://example.com" }]);
956
- });
957
- it("prefixes relative servers with window.location.origin", async () => {
958
- const originalLocation = typeof window !== "undefined" ? window.location : { origin: void 0 };
959
- vi.stubGlobal("window", {
960
- location: {
961
- origin: "http://localhost:3000"
962
- }
963
- });
964
- const result = await importSpecToWorkspace({
965
- servers: [{ url: "/api/v1" }]
966
- });
967
- if (result.error) {
968
- throw result.error;
969
- }
970
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000/api/v1" }]);
971
- vi.stubGlobal("location", originalLocation);
972
- });
973
- it("prefixes relative servers with baseServerURL when provided", async () => {
974
- const result = await importSpecToWorkspace(
975
- {
976
- servers: [{ url: "/api/v1" }]
977
- },
978
- {
979
- baseServerURL: "https://scalar.com"
980
- }
981
- );
982
- if (result.error) {
983
- throw result.error;
984
- }
985
- expect(result.servers).toMatchObject([{ url: "https://scalar.com/api/v1" }]);
986
- });
987
- it("handles empty server objects by using localhost when no baseServerURL", async () => {
988
- const result = await importSpecToWorkspace({
989
- servers: [{}]
990
- });
991
- if (result.error) {
992
- throw result.error;
993
- }
994
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000" }]);
995
- });
996
- it("handles servers with variables/templating", async () => {
997
- const result = await importSpecToWorkspace({
998
- servers: [
999
- {
1000
- url: "{protocol}://api.example.com/{basePath}",
1001
- variables: {
1002
- protocol: {
1003
- default: "https",
1004
- enum: ["http", "https"]
1005
- },
1006
- basePath: {
1007
- default: "v1"
1008
- }
1009
- }
1010
- }
1011
- ]
1012
- });
1013
- if (result.error) {
1014
- throw result.error;
1015
- }
1016
- expect(result.servers[0].url).toBe("{protocol}://api.example.com/{basePath}");
1017
- expect(result.servers[0].variables).toBeDefined();
1018
- });
1019
- it("handles multiple servers with mixed formats", async () => {
1020
- const result = await importSpecToWorkspace(
1021
- {
1022
- servers: [{ url: "https://prod.example.com" }, { url: "/api/v1" }, { url: "{protocol}://dev.example.com" }, {}]
1023
- },
1024
- {
1025
- baseServerURL: "https://scalar.com"
1026
- }
1027
- );
1028
- if (result.error) {
1029
- throw result.error;
1030
- }
1031
- expect(result.servers).toMatchObject([
1032
- { url: "https://prod.example.com" },
1033
- { url: "https://scalar.com/api/v1" },
1034
- { url: "{protocol}://dev.example.com" }
1035
- ]);
1036
- });
1037
- it("handles trailing slashes in baseServerURL", async () => {
1038
- const result = await importSpecToWorkspace(
1039
- {
1040
- servers: [{ url: "/api/v1" }]
1041
- },
1042
- {
1043
- baseServerURL: "https://scalar.com/"
1044
- }
1045
- );
1046
- if (result.error) {
1047
- throw result.error;
1048
- }
1049
- expect(result.servers).toMatchObject([{ url: "https://scalar.com/api/v1" }]);
1050
- });
1051
- it("handles leading slashes in server url", async () => {
1052
- const result = await importSpecToWorkspace(
1053
- {
1054
- servers: [{ url: "//api/v1" }]
1055
- },
1056
- {
1057
- baseServerURL: "https://scalar.com"
1058
- }
1059
- );
1060
- if (result.error) {
1061
- throw result.error;
1062
- }
1063
- expect(result.servers).toMatchObject([{ url: "https://scalar.com/api/v1" }]);
1064
- });
1065
- it("returns an empty array for undefined servers", async () => {
1066
- const result = await importSpecToWorkspace({});
1067
- if (result.error) {
1068
- throw result.error;
1069
- }
1070
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000" }]);
1071
- });
1072
- it("returns an empty array when something is invalid", async () => {
1073
- const result = await importSpecToWorkspace({
1074
- servers: [{ url: false }]
1075
- });
1076
- if (result.error) {
1077
- throw result.error;
1078
- }
1079
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000" }]);
1080
- });
1081
- it("works without window.location", async () => {
1082
- const originalLocation = typeof window !== "undefined" ? window.location : { origin: void 0 };
1083
- vi.stubGlobal("window", void 0);
1084
- const result = await importSpecToWorkspace({
1085
- servers: [{ url: "/api/v1" }]
1086
- });
1087
- if (result.error) {
1088
- throw result.error;
1089
- }
1090
- expect(result.servers).toMatchObject([{ url: "/api/v1" }]);
1091
- vi.stubGlobal("location", originalLocation);
1092
- });
1093
- it("uses current window.location.origin servers is empty", async () => {
1094
- const originalLocation = typeof window !== "undefined" ? window.location : { origin: void 0 };
1095
- vi.stubGlobal("window", {
1096
- location: {
1097
- origin: "http://localhost:3000"
1098
- }
1099
- });
1100
- const result = await importSpecToWorkspace({
1101
- servers: []
1102
- });
1103
- if (result.error) {
1104
- throw result.error;
1105
- }
1106
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000" }]);
1107
- vi.stubGlobal("location", originalLocation);
1108
- });
1109
- it("uses current window.location.origin when servers is undefined", async () => {
1110
- const originalLocation = typeof window !== "undefined" ? window.location : { origin: void 0 };
1111
- vi.stubGlobal("window", {
1112
- location: {
1113
- origin: "http://localhost:3000"
1114
- }
1115
- });
1116
- const result = await importSpecToWorkspace({});
1117
- if (result.error) {
1118
- throw result.error;
1119
- }
1120
- expect(result.servers).toMatchObject([{ url: "http://localhost:3000" }]);
1121
- vi.stubGlobal("location", originalLocation);
1122
- });
1123
- });
1124
- //# sourceMappingURL=import-spec.test.js.map