mirror of
https://github.com/LukeHagar/prettier-plugin-openapi.git
synced 2025-12-10 20:57:50 +00:00
Add files via upload
This commit is contained in:
384
test/all-ref-types.test.ts
Normal file
384
test/all-ref-types.test.ts
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import { parsers, printers } from "../src/index.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprehensive tests for all OpenAPI object types that can be referenced via $ref
|
||||||
|
* This covers all versions: Swagger 2.0, OpenAPI 3.0.x, 3.1.x, and 3.2.0
|
||||||
|
*/
|
||||||
|
describe("All $ref Reference Types", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
const parser = parsers?.["openapi-parser"];
|
||||||
|
|
||||||
|
describe("Schema Object (all versions)", () => {
|
||||||
|
it("should format schema object with $ref at root correctly", () => {
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
type: "object",
|
||||||
|
description: "User schema",
|
||||||
|
title: "User",
|
||||||
|
properties: {
|
||||||
|
id: { type: "integer" },
|
||||||
|
name: { type: "string" },
|
||||||
|
},
|
||||||
|
$ref: "#/components/schemas/User",
|
||||||
|
required: ["id", "name"],
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const titleIndex = resultString.indexOf("title:");
|
||||||
|
expect(refIndex).toBeLessThan(titleIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Parameter Object (all versions)", () => {
|
||||||
|
it("should format parameter object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
name: "offset",
|
||||||
|
in: "query",
|
||||||
|
description: "Pagination offset",
|
||||||
|
schema: { type: "integer" },
|
||||||
|
$ref: "#/components/parameters/Offset",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const nameIndex = resultString.indexOf("name:");
|
||||||
|
expect(refIndex).toBeLessThan(nameIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Response Object (all versions)", () => {
|
||||||
|
it("should format response object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
description: "Success response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { type: "object" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$ref: "#/components/responses/Success",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const descriptionIndex = resultString.indexOf("description:");
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Header Object (OpenAPI 3.0+)", () => {
|
||||||
|
it("should format header object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
description: "Rate limit header",
|
||||||
|
required: true,
|
||||||
|
schema: { type: "integer" },
|
||||||
|
$ref: "#/components/headers/RateLimit",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const descriptionIndex = resultString.indexOf("description:");
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Path Item Object (all versions)", () => {
|
||||||
|
it("should format path item object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
summary: "User operations",
|
||||||
|
description: "Path for user-related operations",
|
||||||
|
get: {
|
||||||
|
summary: "Get user",
|
||||||
|
responses: { "200": { description: "OK" } },
|
||||||
|
},
|
||||||
|
$ref: "#/components/pathItems/UserPath",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const summaryIndex = resultString.indexOf("summary:");
|
||||||
|
expect(refIndex).toBeLessThan(summaryIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Link Object (OpenAPI 3.0+)", () => {
|
||||||
|
it("should format link object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
operationId: "getUser",
|
||||||
|
description: "Link to get user operation",
|
||||||
|
parameters: { userId: "$response.body#/id" },
|
||||||
|
$ref: "#/components/links/UserLink",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const operationIdIndex = resultString.indexOf("operationId:");
|
||||||
|
expect(refIndex).toBeLessThan(operationIdIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Request Body Object (OpenAPI 3.0+)", () => {
|
||||||
|
it("should format request body object with $ref at root correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
description: "User data",
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { type: "object" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$ref: "#/components/requestBodies/UserRequestBody",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const descriptionIndex = resultString.indexOf("description:");
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Callback Object (OpenAPI 3.0+)", () => {
|
||||||
|
it("should format callback object correctly", () => {
|
||||||
|
// Callbacks are maps where values are Path Item Objects
|
||||||
|
// Each path item already supports $ref, so we test the structure
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
"{$request.body#/callbackUrl}": {
|
||||||
|
$ref: "#/components/pathItems/CallbackPath",
|
||||||
|
post: {
|
||||||
|
requestBody: {
|
||||||
|
description: "Callback payload",
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
"200": { description: "Success" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// The path item should have $ref first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const postIndex = resultString.indexOf("post:");
|
||||||
|
expect(refIndex).toBeLessThan(postIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Components Object (OpenAPI 3.0+) - sub-objects", () => {
|
||||||
|
it("should format components/schemas object correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
type: "object",
|
||||||
|
$ref: "#/components/schemas/User",
|
||||||
|
properties: { id: { type: "integer" } },
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const typeIndex = resultString.indexOf("type:");
|
||||||
|
expect(refIndex).toBeLessThan(typeIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Definitions Object (Swagger 2.0)", () => {
|
||||||
|
it("should format definitions object correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
type: "object",
|
||||||
|
$ref: "#/definitions/User",
|
||||||
|
properties: { id: { type: "integer" } },
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const typeIndex = resultString.indexOf("type:");
|
||||||
|
expect(refIndex).toBeLessThan(typeIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Parameters Definitions Object (Swagger 2.0)", () => {
|
||||||
|
it("should format parameters definition object correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
name: "limit",
|
||||||
|
in: "query",
|
||||||
|
description: "Result limit",
|
||||||
|
$ref: "#/parameters/Limit",
|
||||||
|
type: "integer",
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const nameIndex = resultString.indexOf("name:");
|
||||||
|
expect(refIndex).toBeLessThan(nameIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Responses Definitions Object (Swagger 2.0)", () => {
|
||||||
|
it("should format responses definition object correctly", () => {
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
description: "Error response",
|
||||||
|
$ref: "#/responses/Error",
|
||||||
|
schema: { type: "object" },
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) throw new Error("Result is undefined");
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// $ref should be first
|
||||||
|
const refIndex = resultString.indexOf("$ref:");
|
||||||
|
const descriptionIndex = resultString.indexOf("description:");
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
623
test/ref-ordering.test.ts
Normal file
623
test/ref-ordering.test.ts
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import { printers } from "../src/index.js";
|
||||||
|
|
||||||
|
describe("Reference ($ref) Ordering Tests", () => {
|
||||||
|
describe("Operation key ordering with references", () => {
|
||||||
|
it("should sort operation keys correctly with $ref parameters", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/test": {
|
||||||
|
get: {
|
||||||
|
// Intentionally out of order to test sorting
|
||||||
|
description: "Get a list of Access Model Metadata Attributes",
|
||||||
|
operationId: "listAccessModelMetadataAttribute",
|
||||||
|
summary: "List access model metadata attributes",
|
||||||
|
tags: ["Access Model Metadata"],
|
||||||
|
security: [{ userAuth: ["idn:access-model-metadata:read"] }],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "filters",
|
||||||
|
description: "Filter results",
|
||||||
|
in: "query",
|
||||||
|
required: false,
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/offset.yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/limit.yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sorters",
|
||||||
|
description: "Sort results",
|
||||||
|
in: "query",
|
||||||
|
required: false,
|
||||||
|
schema: { type: "string", format: "comma-separated" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
description: "OK",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
$ref: "../schemas/gov-attributes/AttributeDTO.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
$ref: "../../v3/responses/400.yaml",
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
$ref: "../../v3/responses/401.yaml",
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
$ref: "../../v3/responses/500.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-sailpoint-userLevels": ["ORG_ADMIN"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Check operation keys appear in correct order
|
||||||
|
const operationIdIndex = resultString.indexOf("operationId");
|
||||||
|
const summaryIndex = resultString.indexOf("summary");
|
||||||
|
const tagsIndex = resultString.indexOf("tags");
|
||||||
|
const descriptionIndex = resultString.indexOf("description");
|
||||||
|
const securityIndex = resultString.indexOf("security");
|
||||||
|
const parametersIndex = resultString.indexOf("parameters");
|
||||||
|
const responsesIndex = resultString.indexOf("responses");
|
||||||
|
const xSailpointIndex = resultString.indexOf("x-sailpoint-userLevels");
|
||||||
|
|
||||||
|
// Verify operation key order: operationId -> summary -> tags -> description -> security -> parameters -> responses
|
||||||
|
expect(operationIdIndex).toBeLessThan(summaryIndex);
|
||||||
|
expect(summaryIndex).toBeLessThan(tagsIndex);
|
||||||
|
expect(tagsIndex).toBeLessThan(descriptionIndex);
|
||||||
|
expect(descriptionIndex).toBeLessThan(securityIndex);
|
||||||
|
expect(securityIndex).toBeLessThan(parametersIndex);
|
||||||
|
expect(parametersIndex).toBeLessThan(responsesIndex);
|
||||||
|
|
||||||
|
// Custom extensions should come after all standard keys
|
||||||
|
expect(responsesIndex).toBeLessThan(xSailpointIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sort parameters array with $ref items first", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/test": {
|
||||||
|
get: {
|
||||||
|
operationId: "test",
|
||||||
|
responses: { "200": { description: "OK" } },
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "filter",
|
||||||
|
in: "query",
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/offset.yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort",
|
||||||
|
in: "query",
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/limit.yaml",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Find indices of parameter items in the output
|
||||||
|
// $ref parameters should come before named parameters
|
||||||
|
const parametersSection = resultString.substring(resultString.indexOf("parameters:"));
|
||||||
|
|
||||||
|
// Check that $ref items appear before named parameters
|
||||||
|
const offsetRefIndex = parametersSection.indexOf("offset.yaml");
|
||||||
|
const limitRefIndex = parametersSection.indexOf("limit.yaml");
|
||||||
|
const filterIndex = parametersSection.indexOf("name: filter");
|
||||||
|
const sortIndex = parametersSection.indexOf("name: sort");
|
||||||
|
|
||||||
|
// $ref items should come first in parameters array
|
||||||
|
expect(offsetRefIndex).toBeLessThan(filterIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(filterIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(sortIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sort responses with $ref items correctly", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/test": {
|
||||||
|
get: {
|
||||||
|
operationId: "test",
|
||||||
|
responses: {
|
||||||
|
"500": {
|
||||||
|
$ref: "../../v3/responses/500.yaml",
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
description: "OK",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { type: "object" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
$ref: "../../v3/responses/400.yaml",
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
$ref: "../../v3/responses/401.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Response codes should be sorted numerically: 200, 400, 401, 500
|
||||||
|
const responsesSection = resultString.substring(resultString.indexOf("responses:"));
|
||||||
|
|
||||||
|
const code200Index = responsesSection.indexOf('"200":');
|
||||||
|
const code400Index = responsesSection.indexOf('"400":');
|
||||||
|
const code401Index = responsesSection.indexOf('"401":');
|
||||||
|
const code500Index = responsesSection.indexOf('"500":');
|
||||||
|
|
||||||
|
expect(code200Index).toBeLessThan(code400Index);
|
||||||
|
expect(code400Index).toBeLessThan(code401Index);
|
||||||
|
expect(code401Index).toBeLessThan(code500Index);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle parameter objects with $ref at the top", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
components: {
|
||||||
|
parameters: {
|
||||||
|
OffsetParam: {
|
||||||
|
description: "Pagination offset",
|
||||||
|
in: "query",
|
||||||
|
name: "offset",
|
||||||
|
schema: { type: "integer" },
|
||||||
|
$ref: "../../v3/parameters/offset.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// In a parameter object, if both $ref and other keys exist, $ref should come first
|
||||||
|
// But typically in OpenAPI, if $ref exists, other keys (except $ref) are ignored
|
||||||
|
// However, we should still verify the ordering
|
||||||
|
const offsetParamSection = resultString.substring(resultString.indexOf("OffsetParam:"));
|
||||||
|
|
||||||
|
// $ref should be at the top of the parameter object
|
||||||
|
const refIndex = offsetParamSection.indexOf("$ref:");
|
||||||
|
const nameIndex = offsetParamSection.indexOf("name:");
|
||||||
|
const descriptionIndex = offsetParamSection.indexOf("description:");
|
||||||
|
|
||||||
|
// When $ref is present, it should be first (though other keys should typically be removed)
|
||||||
|
if (refIndex !== -1 && nameIndex !== -1) {
|
||||||
|
expect(refIndex).toBeLessThan(nameIndex);
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle response objects with $ref correctly", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
components: {
|
||||||
|
responses: {
|
||||||
|
ErrorResponse: {
|
||||||
|
description: "Error response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { type: "object" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$ref: "../../v3/responses/error.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// In a response object with $ref, $ref should be first
|
||||||
|
const errorResponseSection = resultString.substring(resultString.indexOf("ErrorResponse:"));
|
||||||
|
|
||||||
|
const refIndex = errorResponseSection.indexOf("$ref:");
|
||||||
|
const descriptionIndex = errorResponseSection.indexOf("description:");
|
||||||
|
const contentIndex = errorResponseSection.indexOf("content:");
|
||||||
|
|
||||||
|
if (refIndex !== -1 && descriptionIndex !== -1) {
|
||||||
|
expect(refIndex).toBeLessThan(descriptionIndex);
|
||||||
|
expect(refIndex).toBeLessThan(contentIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle complete operation with mixed inline and $ref parameters", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
openapi: "3.0.0",
|
||||||
|
info: { title: "Test API", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/test": {
|
||||||
|
get: {
|
||||||
|
// All keys intentionally out of order
|
||||||
|
tags: ["Test"],
|
||||||
|
description: "Test endpoint",
|
||||||
|
summary: "Get test",
|
||||||
|
operationId: "getTest",
|
||||||
|
security: [{ apiKey: [] }],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "custom",
|
||||||
|
in: "query",
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "#/components/parameters/Offset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "another",
|
||||||
|
in: "query",
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "#/components/parameters/Limit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "#/components/parameters/Count",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
description: "Success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-custom": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Verify operation key order
|
||||||
|
const operationIdIndex = resultString.indexOf("operationId");
|
||||||
|
const summaryIndex = resultString.indexOf("summary");
|
||||||
|
const tagsIndex = resultString.indexOf("tags");
|
||||||
|
const descriptionIndex = resultString.indexOf("description");
|
||||||
|
const securityIndex = resultString.indexOf("security");
|
||||||
|
const parametersIndex = resultString.indexOf("parameters");
|
||||||
|
const responsesIndex = resultString.indexOf("responses");
|
||||||
|
const xCustomIndex = resultString.indexOf("x-custom");
|
||||||
|
|
||||||
|
expect(operationIdIndex).toBeLessThan(summaryIndex);
|
||||||
|
expect(summaryIndex).toBeLessThan(tagsIndex);
|
||||||
|
expect(tagsIndex).toBeLessThan(descriptionIndex);
|
||||||
|
expect(descriptionIndex).toBeLessThan(securityIndex);
|
||||||
|
expect(securityIndex).toBeLessThan(parametersIndex);
|
||||||
|
expect(parametersIndex).toBeLessThan(responsesIndex);
|
||||||
|
expect(responsesIndex).toBeLessThan(xCustomIndex);
|
||||||
|
|
||||||
|
// Verify $ref parameters come first in parameters array
|
||||||
|
const parametersSection = resultString.substring(resultString.indexOf("parameters:"));
|
||||||
|
const offsetRefIndex = parametersSection.indexOf("Offset");
|
||||||
|
const limitRefIndex = parametersSection.indexOf("Limit");
|
||||||
|
const countRefIndex = parametersSection.indexOf("Count");
|
||||||
|
const customIndex = parametersSection.indexOf("name: custom");
|
||||||
|
const anotherIndex = parametersSection.indexOf("name: another");
|
||||||
|
|
||||||
|
// All $ref items should come before inline parameters
|
||||||
|
expect(offsetRefIndex).toBeLessThan(customIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(customIndex);
|
||||||
|
expect(countRefIndex).toBeLessThan(customIndex);
|
||||||
|
expect(offsetRefIndex).toBeLessThan(anotherIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(anotherIndex);
|
||||||
|
expect(countRefIndex).toBeLessThan(anotherIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle root-level operation file (file with HTTP method at root)", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
// This simulates a file that is referenced via $ref and contains just an operation
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
get: {
|
||||||
|
// Keys intentionally out of order
|
||||||
|
description: "Get a list of Access Model Metadata Attributes",
|
||||||
|
operationId: "listAccessModelMetadataAttribute",
|
||||||
|
summary: "List access model metadata attributes",
|
||||||
|
tags: ["Access Model Metadata"],
|
||||||
|
security: [{ userAuth: ["idn:access-model-metadata:read"] }],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "filters",
|
||||||
|
description: "Filter results",
|
||||||
|
in: "query",
|
||||||
|
required: false,
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/offset.yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$ref: "../../v3/parameters/limit.yaml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sorters",
|
||||||
|
description: "Sort results",
|
||||||
|
in: "query",
|
||||||
|
required: false,
|
||||||
|
schema: { type: "string", format: "comma-separated" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
description: "OK",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
$ref: "../schemas/gov-attributes/AttributeDTO.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
$ref: "../../v3/responses/400.yaml",
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
$ref: "../../v3/responses/401.yaml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-sailpoint-userLevels": ["ORG_ADMIN"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Find the operation keys within the get: block
|
||||||
|
const getBlockStart = resultString.indexOf("get:");
|
||||||
|
const getBlock = resultString.substring(getBlockStart);
|
||||||
|
|
||||||
|
// Check operation keys appear in correct order
|
||||||
|
const operationIdIndex = getBlock.indexOf("operationId");
|
||||||
|
const summaryIndex = getBlock.indexOf("summary");
|
||||||
|
const tagsIndex = getBlock.indexOf("tags");
|
||||||
|
const descriptionIndex = getBlock.indexOf("description");
|
||||||
|
const securityIndex = getBlock.indexOf("security");
|
||||||
|
const parametersIndex = getBlock.indexOf("parameters");
|
||||||
|
const responsesIndex = getBlock.indexOf("responses");
|
||||||
|
const xSailpointIndex = getBlock.indexOf("x-sailpoint-userLevels");
|
||||||
|
|
||||||
|
// Verify operation key order: operationId -> summary -> tags -> description -> security -> parameters -> responses
|
||||||
|
expect(operationIdIndex).toBeLessThan(summaryIndex);
|
||||||
|
expect(summaryIndex).toBeLessThan(tagsIndex);
|
||||||
|
expect(tagsIndex).toBeLessThan(descriptionIndex);
|
||||||
|
expect(descriptionIndex).toBeLessThan(securityIndex);
|
||||||
|
expect(securityIndex).toBeLessThan(parametersIndex);
|
||||||
|
expect(parametersIndex).toBeLessThan(responsesIndex);
|
||||||
|
|
||||||
|
// Custom extensions should come after all standard keys
|
||||||
|
expect(responsesIndex).toBeLessThan(xSailpointIndex);
|
||||||
|
|
||||||
|
// Verify $ref parameters come first in parameters array
|
||||||
|
const parametersSection = getBlock.substring(getBlock.indexOf("parameters:"));
|
||||||
|
const offsetRefIndex = parametersSection.indexOf("offset.yaml");
|
||||||
|
const limitRefIndex = parametersSection.indexOf("limit.yaml");
|
||||||
|
const filtersIndex = parametersSection.indexOf("name: filters");
|
||||||
|
const sortersIndex = parametersSection.indexOf("name: sorters");
|
||||||
|
|
||||||
|
// $ref items should come before inline parameters
|
||||||
|
expect(offsetRefIndex).toBeLessThan(filtersIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(filtersIndex);
|
||||||
|
expect(offsetRefIndex).toBeLessThan(sortersIndex);
|
||||||
|
expect(limitRefIndex).toBeLessThan(sortersIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle direct operation object at root (no HTTP method wrapper)", () => {
|
||||||
|
const printer = printers?.["openapi-ast"];
|
||||||
|
expect(printer).toBeDefined();
|
||||||
|
|
||||||
|
// This simulates a file that contains just the operation object directly
|
||||||
|
const testData = {
|
||||||
|
isOpenAPI: true,
|
||||||
|
format: "yaml",
|
||||||
|
content: {
|
||||||
|
// Direct operation object (no HTTP method wrapper)
|
||||||
|
operationId: "testOperation",
|
||||||
|
description: "Test operation",
|
||||||
|
summary: "Test summary",
|
||||||
|
tags: ["Test"],
|
||||||
|
security: [{ apiKey: [] }],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
$ref: "#/components/parameters/Offset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom",
|
||||||
|
in: "query",
|
||||||
|
schema: { type: "string" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
description: "Success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
originalText: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error We are mocking things here
|
||||||
|
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("Result is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultString = result.toString();
|
||||||
|
|
||||||
|
// Check operation keys appear in correct order
|
||||||
|
const operationIdIndex = resultString.indexOf("operationId");
|
||||||
|
const summaryIndex = resultString.indexOf("summary");
|
||||||
|
const tagsIndex = resultString.indexOf("tags");
|
||||||
|
const descriptionIndex = resultString.indexOf("description");
|
||||||
|
const securityIndex = resultString.indexOf("security");
|
||||||
|
const parametersIndex = resultString.indexOf("parameters");
|
||||||
|
const responsesIndex = resultString.indexOf("responses");
|
||||||
|
|
||||||
|
// Verify operation key order
|
||||||
|
expect(operationIdIndex).toBeLessThan(summaryIndex);
|
||||||
|
expect(summaryIndex).toBeLessThan(tagsIndex);
|
||||||
|
expect(tagsIndex).toBeLessThan(descriptionIndex);
|
||||||
|
expect(descriptionIndex).toBeLessThan(securityIndex);
|
||||||
|
expect(securityIndex).toBeLessThan(parametersIndex);
|
||||||
|
expect(parametersIndex).toBeLessThan(responsesIndex);
|
||||||
|
|
||||||
|
// Verify $ref parameters come first
|
||||||
|
const parametersSection = resultString.substring(resultString.indexOf("parameters:"));
|
||||||
|
const offsetRefIndex = parametersSection.indexOf("Offset");
|
||||||
|
const customIndex = parametersSection.indexOf("name: custom");
|
||||||
|
|
||||||
|
expect(offsetRefIndex).toBeLessThan(customIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user