diff --git a/bun.lockb b/bun.lockb index 056d9cf1..68e749e4 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 79ea5641..a41b0b7f 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "swagger-cli": "^4.0.4", "typescript": "^5.5.4", "vite-tsconfig-paths": "^5.0.1", - "vitest": "^2.0.5" + "vitest": "^2.0.5", + "xml-js": "^1.6.11" }, "dependencies": { "ajv": "^8.17.1", diff --git a/src/paths/users/get-users.yaml b/src/paths/users/get-users.yaml index c2526cfb..4b0ecf98 100644 --- a/src/paths/users/get-users.yaml +++ b/src/paths/users/get-users.yaml @@ -97,8 +97,8 @@ get: description: "User's email address." example: "zgfuc7krcqfimrmb9lsl5j@protonmail.com" recommendationsPlaylistId: - type: string description: "ID of the user's recommendation playlist." + type: ["null", string] example: "" thumb: type: string @@ -133,19 +133,19 @@ get: - description: "Indicates if the user can manage subtitles." - $ref: "../../models/common/PlexBoolean.yaml" filterAll: - type: string + type: ["null", string] description: "Filters applied for all content." example: "" filterMovies: - type: string + type: ["null", string] description: "Filters applied for movies." example: "" filterMusic: - type: string + type: ["null", string] description: "Filters applied for music." example: "" filterPhotos: - type: string + type: ["null", string] description: "Filters applied for photos." example: "" filterTelevision: diff --git a/tests/utils/import.ts b/tests/utils/import.ts index 723874bc..5e0d688e 100644 --- a/tests/utils/import.ts +++ b/tests/utils/import.ts @@ -3,6 +3,7 @@ import Ajv from "ajv" import addFormats from "ajv-formats" import { expect } from "vitest" import { merge } from "lodash-es" +import { xml2json } from "xml-js" /** * Validate a response against the OpenAPI spec * NOTE: It accounts for the following scenarios: @@ -13,12 +14,14 @@ import { merge } from "lodash-es" * @param type * @param statusCode * @param response + * @param responseType */ export function validateResponseSpec( path: string, type: "get" | "post" | "delete", statusCode: number, - response: any + response: any, + responseType: "json" | "xml" = "json" ): void { const ajv = new Ajv({ allErrors: true, strict: false }) @@ -30,9 +33,28 @@ export function validateResponseSpec( addFormats(ajv) // Convert JSONPath to JSON Pointer - const pathPointer = `/paths/${path.replace(/\//g, `~1`)}/${type}/responses/${statusCode}/content/application~1json/schema` + const pathPointer = `/paths/${path.replace(/\//g, `~1`)}/${type}/responses/${statusCode}/content/application~1${responseType}/schema` - const validate = ajv.validate({ $ref: "API.yaml#" + pathPointer }, response) + let jsonResponse = response + if (responseType === "xml") { + const x = response.replace(/=""/g, '="a"') + jsonResponse = flattenAttributes( + JSON.parse( + xml2json(x, { + compact: true, + ignoreDeclaration: true, + nativeType: true, + // @ts-ignore + nativeTypeAttributes: true + }) + ) + ) + } + + const validate = ajv.validate( + { $ref: "API.yaml#" + pathPointer }, + jsonResponse + ) if (!validate) { console.error(ajv.errors) @@ -78,3 +100,19 @@ function addAdditionalProperties(schema) { } } } + +function flattenAttributes(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(flattenAttributes) + } else if (typeof obj === "object" && obj !== null) { + if ("_attributes" in obj) { + Object.assign(obj, obj._attributes) + delete obj._attributes + } + // Recursively process all properties + for (const key in obj) { + obj[key] = flattenAttributes(obj[key]) + } + } + return obj +}