diff --git a/json_schemas/story.schema.yaml b/json_schemas/story.schema.yaml new file mode 100644 index 000000000..1b5a16800 --- /dev/null +++ b/json_schemas/story.schema.yaml @@ -0,0 +1,138 @@ +$schema: http://json-schema.org/draft-07/schema# + +type: object +properties: + $schema: + type: string + skipped: + type: boolean + description: If true, the story will be skipped. + default: false + description: + type: string + prologues: + type: array + items: + $ref: '#/definitions/SupplementalChapter' + epilogues: + type: array + items: + $ref: '#/definitions/SupplementalChapter' + chapters: + type: array + items: + $ref: '#/definitions/Chapter' + minItems: 1 +required: [ description, chapters, prologues, epilogues] +additionalProperties: false + +definitions: + Chapter: + type: object + allOf: + - $ref: '#/definitions/ChapterRequest' + - properties: + synopsis: + type: string + description: A brief description of the chapter. + response: + $ref: '#/definitions/ExpectedResponse' + required: [ synopsis ] + additionalProperties: false + + ReadChapter: + allOf: + - $ref: '#/definitions/Chapter' + - type: object + properties: + response: + $ref: '#/definitions/ActualResponse' + required: [ response ] + additionalProperties: false + + SupplementalChapter: + allOf: + - $ref: '#/definitions/ChapterRequest' + - type: object + properties: + ignore_errors: + description: If true, treat all non-2XX responses as successful. + type: boolean + default: false + + ChapterRequest: + type: object + properties: + path: + type: string + method: + type: string + enum: [ GET, PUT, POST, DELETE, PATCH, HEAD, OPTIONS ] + parameters: + type: object + additionalProperties: + $ref: '#/definitions/Parameter' + requestBody: + $ref: '#/definitions/RequestBody' + required: [ path, method ] + additionalProperties: false + + + RequestBody: + type: object + properties: + content_type: + type: string + payload: + $ref: '#/definitions/Payload' + required: [ content_type, payload ] + additionalProperties: false + + ExpectedResponse: + type: object + properties: + status: + type: integer + description: The expected HTTP status code. Default to 200. + default: 200 + content_type: + type: string + payload: + $ref: '#/definitions/Payload' + required: [ status ] + additionalProperties: false + + ActualResponse: + type: object + properties: + status: + type: integer + content_type: + type: string + payload: + $ref: '#/definitions/Payload' + message: + type: string + description: Error message for non 2XX responses. + required: [ status, content_type, payload ] + additionalProperties: false + + Payload: + anyOf: + - type: object + - type: array + - type: string + - type: number + - type: boolean + + Parameter: + anyOf: + - type: array + items: + oneOf: + - type: string + - type: number + - type: boolean + - type: string + - type: number + - type: boolean \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 14f2fc54d..776371188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/node": "^20.10.3", "ajv": "^8.13.0", "ajv-formats": "^3.0.1", + "axios": "^1.6.8", "commander": "^12.0.0", "lodash": "^4.17.21", "ts-node": "^10.9.1", @@ -33,6 +34,7 @@ "eslint-plugin-promise": "^6.1.1", "globals": "^15.0.0", "jest": "^29.7.0", + "json-schema-to-typescript": "^14.0.4", "ts-jest": "^29.1.2" } }, @@ -898,6 +900,102 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1287,6 +1385,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1428,9 +1536,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.1.tgz", + "integrity": "sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==" }, "node_modules/@types/node": { "version": "20.11.20", @@ -1897,6 +2005,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2046,6 +2160,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2061,6 +2180,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -2417,6 +2546,22 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2465,6 +2610,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", @@ -2525,6 +2681,28 @@ "node": ">= 8" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -2656,6 +2834,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2706,6 +2892,12 @@ "node": ">=6.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/electron-to-chromium": { "version": "1.4.683", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.683.tgz", @@ -2872,6 +3064,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -3461,6 +3705,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", @@ -3544,6 +3803,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3592,6 +3861,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3655,6 +3933,29 @@ "bser": "2.1.1" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3712,6 +4013,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3721,6 +4041,59 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4358,6 +4731,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4569,6 +4948,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -5199,6 +5596,114 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-to-typescript": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-14.0.4.tgz", + "integrity": "sha512-covPOp3hrbD+oEcMvDxP5Rh6xNZj7lOTZkXAeQoDyu1PuEl1A6oRZ3Sy05HN11vXXmdJ6gLh5P3Qz0mgMPTzzw==", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.0", + "cli-color": "^2.0.4", + "glob": "^10.3.12", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "mkdirp": "^3.0.1", + "mz": "^2.7.0", + "node-fetch": "^3.3.2", + "prettier": "^3.2.5" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.1.tgz", + "integrity": "sha512-DxjgKBCoyReu4p5HMvpmgSOfRhhBcuf5V5soDDRgOTZMwsA4KSFzol1abFZgiCTE11L2kKGca5Md9GwDdXVBwQ==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/json-schema-to-typescript/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/json-schema-to-typescript/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/json-schema-to-typescript/node_modules/glob": { + "version": "10.3.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", + "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-schema-to-typescript/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-to-typescript/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -5306,6 +5811,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -5368,6 +5882,22 @@ "tmpl": "1.0.5" } }, + "node_modules/memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5396,6 +5926,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5426,18 +5975,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5471,6 +6098,15 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -5717,6 +6353,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5783,6 +6444,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -5822,6 +6498,11 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6210,6 +6891,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -6271,6 +6967,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -6345,6 +7054,37 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6535,6 +7275,12 @@ "node": ">=4" } }, + "node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6739,6 +7485,15 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6806,6 +7561,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 0aab88335..37494f262 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "lint:spec": "ts-node tools/src/linter/lint.ts", "lint": "eslint .", "lint--fix": "eslint . --fix", - "test": "jest" + "test": "jest", + "test:spec": "ts-node tools/src/tester/start.ts" }, "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", @@ -18,6 +19,7 @@ "@types/node": "^20.10.3", "ajv": "^8.13.0", "ajv-formats": "^3.0.1", + "axios": "^1.6.8", "commander": "^12.0.0", "lodash": "^4.17.21", "ts-node": "^10.9.1", @@ -36,6 +38,7 @@ "eslint-plugin-promise": "^6.1.1", "globals": "^15.0.0", "jest": "^29.7.0", + "json-schema-to-typescript": "^14.0.4", "ts-jest": "^29.1.2" } } diff --git a/tests/index_lifecycle.yaml b/tests/index_lifecycle.yaml new file mode 100644 index 000000000..7ea42963a --- /dev/null +++ b/tests/index_lifecycle.yaml @@ -0,0 +1,85 @@ +$schema: ../../json_schemas/story.schema.yaml + +description: This story tests all endpoints relevant the lifecycle of an index, from creation to deletion. +prologues: [] +epilogues: + - path: /books,movies,games + method: DELETE + ignore_errors: false +chapters: + - synopsis: Create an index named `books` with mappings and settings. + path: /{index} + method: PUT + parameters: + index: books + requestBody: + content_type: application/json + payload: + mappings: + properties: + name: + type: keyword + age: + type: integer + settings: + number_of_shards: 5 + number_of_replicas: 2 + response: + status: 200 + + - synopsis: Create an index named `games` with default settings, + path: /{index} + method: PUT + parameters: + index: games + response: + status: 200 + + - synopsis: Check if the index `books` exists. It should. + path: /{index} + method: HEAD + parameters: + index: books + response: + status: 200 + + - synopsis: Check if the index `movies` exists. It should not. + path: /{index} + method: HEAD + parameters: + index: movies + response: + status: 404 + + - synopsis: Retrieve the mappings and settings of the `books` and `games` indices. + path: /{index} + method: GET + parameters: + index: books,games + flat_settings: true + response: + status: 200 + + - synopsis: Close the `books` index. + path: /{index}/_close + method: POST + parameters: + index: books + response: + status: 200 + + - synopsis: Open the `books` index. + path: /{index}/_open + method: POST + parameters: + index: books + response: + status: 200 + + - synopsis: Delete the `books` and `games` indices. + path: /{index} + method: DELETE + parameters: + index: books,games + response: + status: 200 diff --git a/tools/src/tester/ChapterEvaluator.ts b/tools/src/tester/ChapterEvaluator.ts new file mode 100644 index 000000000..5e018c707 --- /dev/null +++ b/tools/src/tester/ChapterEvaluator.ts @@ -0,0 +1,67 @@ +import { type Chapter, type ActualResponse } from './types/story.types' +import { type ChapterEvaluation, type Evaluation, Result } from './types/eval.types' +import { type ParsedOperation } from './types/spec.types' +import { overall_result } from './helpers' + +export default class ChapterEvaluator { + chapter: Chapter + result: Result = Result.PASSED + skip_payload: boolean = false + + constructor (chapter: Chapter) { + this.chapter = chapter + } + + async evaluate (skipped: boolean): Promise { + try { + if (skipped) return { result: Result.SKIPPED, title: this.chapter.synopsis } + const operation = globalThis.spec_parser.locate_operation(this.chapter) + const response = await globalThis.chapter_reader.read(this.chapter, true) + const params = this.#evaluate_parameters(operation) + const request_body = this.#evaluate_request_body(operation) + const status = this.#evaluate_status(response) + const payload = this.#evaluate_payload(operation, response) + return { + title: this.chapter.synopsis, + request: { parameters: params, requestBody: request_body }, + response: { status, payload }, + result: overall_result(Object.values(params).concat([request_body, status, payload])) + } + } catch (error) { + console.error(error) + return { result: Result.ERROR, title: this.chapter.synopsis, message: error as string } + } + } + + #evaluate_parameters (operation: ParsedOperation): Record { + return Object.fromEntries(Object.entries(this.chapter.parameters ?? {}).map(([name, parameter]) => { + const schema = operation.parameters[name]?.schema + if (schema == null) return [name, { result: Result.FAILED, message: `Schema for "${name}" parameter not found.` }] + const evaluation = globalThis.schema_validator.validate(schema, parameter) + return [name, evaluation] + })) + } + + #evaluate_request_body (operation: ParsedOperation): Evaluation { + if (!this.chapter.requestBody) return { result: Result.PASSED } + const schema = operation.requestBody?.content[this.chapter.requestBody?.content_type ?? '']?.schema + if (schema == null) return { result: Result.FAILED, message: `Schema for "${this.chapter.requestBody.content_type}" request body not found.` } + return globalThis.schema_validator.validate(schema, this.chapter.requestBody?.payload ?? {}) + } + + #evaluate_status (response: ActualResponse): Evaluation { + const expected_status = this.chapter.response?.status ?? 200 + if (response.status === expected_status) return { result: Result.PASSED } + this.skip_payload = true + return { result: Result.FAILED, message: `Expected status ${expected_status}, but received ${response.status}: ${response.message}.` } + } + + #evaluate_payload (operation: ParsedOperation, response: ActualResponse): Evaluation { + if (this.skip_payload) return { result: Result.SKIPPED } + const content = operation.responses[response.status]?.content[response.content_type] + const schema = content?.schema + if (schema == null && content != null) return { result: Result.PASSED } + if (schema == null) return { result: Result.FAILED, message: `Schema for "${response.status}: ${response.content_type}" response not found.` } + return globalThis.schema_validator.validate(schema, response.payload) + } +} diff --git a/tools/src/tester/ChapterReader.ts b/tools/src/tester/ChapterReader.ts new file mode 100644 index 000000000..90635e0a9 --- /dev/null +++ b/tools/src/tester/ChapterReader.ts @@ -0,0 +1,44 @@ +import axios from 'axios' +import { type ChapterRequest, type ActualResponse, type Parameter } from './types/story.types' + +// A lightweight client for testing the API +export default class ChapterReader { + url: string + + constructor (url: string) { + this.url = url + } + + async read (chapter: ChapterRequest, ignore_errors: boolean = false): Promise { + const response: Record = {} + const [url, params] = this.#parse_url(chapter.path, chapter.parameters ?? {}) + await axios.request({ + url, + method: chapter.method, + params, + data: chapter.requestBody?.payload + }).then(r => { + response.status = r.status + response.content_type = r.headers['content-type'].split(';')[0] + response.payload = r.data + }).catch(e => { + if (!ignore_errors) throw new Error(e.response.data.error.reason as string) + response.status = e.response.status + response.content_type = e.response.headers['content-type'].split(';')[0] + response.payload = e.response.data?.error + response.message = e.response.data?.error?.reason + }) + return response as ActualResponse + } + + #parse_url (path: string, parameters: Record): [string, Record] { + const path_params = new Set() + const parsed_path = path.replace(/{(\w+)}/g, (_, key) => { + path_params.add(key as string) + return parameters[key] as string + }) + const query_params = Object.fromEntries(Object.entries(parameters).filter(([key]) => !path_params.has(key))) + const url = this.url + parsed_path + return [url, query_params] + } +} diff --git a/tools/src/tester/ResultsDisplayer.ts b/tools/src/tester/ResultsDisplayer.ts new file mode 100644 index 000000000..9c40aeaac --- /dev/null +++ b/tools/src/tester/ResultsDisplayer.ts @@ -0,0 +1,110 @@ +import { type ChapterEvaluation, type Evaluation, Result, type StoryEvaluation } from './types/eval.types' +import { overall_result } from './helpers' + +function b (text: string): string { return `\x1b[1m${text}\x1b[0m` } +function i (text: string): string { return `\x1b[3m${text}\x1b[0m` } + +function padding (text: string, length: number, prefix: number = 0): string { + const spaces = length - text.length > 0 ? ' '.repeat(length - text.length) : '' + return `${' '.repeat(prefix)}${text}${spaces}` +} + +function green (text: string): string { return `\x1b[32m${text}\x1b[0m` } +function red (text: string): string { return `\x1b[31m${text}\x1b[0m` } +function yellow (text: string): string { return `\x1b[33m${text}\x1b[0m` } +function cyan (text: string): string { return `\x1b[36m${text}\x1b[0m` } +function gray (text: string): string { return `\x1b[90m${text}\x1b[0m` } +function magenta (text: string): string { return `\x1b[35m${text}\x1b[0m` } + +export default class ResultsDisplayer { + gap: number + evaluation: StoryEvaluation + skip_components: boolean + ignore_passed: boolean = true + ignore_skipped: boolean = false + + constructor (evaluation: StoryEvaluation, gap: number = 4) { + this.evaluation = evaluation + this.skip_components = [Result.PASSED, Result.SKIPPED].includes(evaluation.result) + this.gap = gap + } + + display (ignore_skipped = false, ignore_passed = true): void { + this.ignore_passed = ignore_passed + this.ignore_skipped = ignore_skipped + this.#display_story() + this.#display_chapters(this.evaluation.prologues ?? [], 'PROLOGUES') + this.#display_chapters(this.evaluation.chapters ?? [], 'CHAPTERS') + this.#display_chapters(this.evaluation.epilogues ?? [], 'EPILOGUES') + console.log('\n') + } + + #display_story (): void { + const result = this.evaluation.result + const message = this.evaluation.full_path + const title = cyan(b(this.evaluation.display_path)) + this.#display_evaluation({ result, message }, title) + } + + #display_chapters (evaluations: ChapterEvaluation[], title: string): void { + if (this.skip_components || evaluations.length === 0) return + const result = overall_result(evaluations) + this.#display_evaluation({ result }, title, this.gap) + if (result === Result.PASSED) return + for (const evaluation of evaluations) this.#display_chapter(evaluation) + } + + #display_chapter (chapter: ChapterEvaluation): void { + this.#display_evaluation(chapter, i(chapter.title), this.gap * 2) + if (chapter.result === Result.PASSED || chapter.result === Result.SKIPPED) return + + this.#display_parameters(chapter.request?.parameters ?? {}) + this.#display_request_body(chapter.request?.requestBody) + this.#display_status(chapter.response?.status) + this.#display_payload(chapter.response?.payload) + } + + #display_parameters (parameters: Record): void { + if (Object.keys(parameters).length === 0) return + const result = overall_result(Object.values(parameters)) + this.#display_evaluation({ result }, 'PARAMETERS', this.gap * 3) + if (result === Result.PASSED) return + for (const [name, evaluation] of Object.entries(parameters)) { + this.#display_evaluation(evaluation, name, this.gap * 4) + } + } + + #display_request_body (evaluation: Evaluation | undefined): void { + if (evaluation == null) return + this.#display_evaluation(evaluation, 'REQUEST BODY', this.gap * 3) + } + + #display_status (evaluation: Evaluation | undefined): void { + if (evaluation == null) return + this.#display_evaluation(evaluation, 'RESPONSE STATUS', this.gap * 3) + } + + #display_payload (evaluation: Evaluation | undefined): void { + if (evaluation == null) return + this.#display_evaluation(evaluation, 'RESPONSE PAYLOAD', this.gap * 3) + } + + #display_evaluation (evaluation: Evaluation, title: string, prefix: number = 0): void { + if (evaluation.result === Result.PASSED && this.ignore_passed) return + if (evaluation.result === Result.SKIPPED && this.ignore_skipped) return + const result = padding(this.#result(evaluation.result), 0, prefix) + const message = evaluation.message != null ? `${gray('(' + evaluation.message + ')')}` : '' + console.log(`${result} ${title} ${message}`) + } + + #result (r: Result): string { + const text = padding(r, 7) + switch (r) { + case Result.PASSED: return green(text) + case Result.SKIPPED: return yellow(text) + case Result.FAILED: return magenta(text) + case Result.ERROR: return red(text) + default: return gray(text) + } + } +} diff --git a/tools/src/tester/SchemaValidator.ts b/tools/src/tester/SchemaValidator.ts new file mode 100644 index 000000000..4f9c91896 --- /dev/null +++ b/tools/src/tester/SchemaValidator.ts @@ -0,0 +1,24 @@ +import AJV from 'ajv' +import addFormats from 'ajv-formats' +import { type OpenAPIV3 } from 'openapi-types' +import { type Evaluation, Result } from './types/eval.types' + +export default class SchemaValidator { + private readonly ajv: AJV + constructor (spec: OpenAPIV3.Document) { + this.ajv = new AJV() + addFormats(this.ajv) + this.ajv.addKeyword('discriminator') + const schemas = spec.components?.schemas ?? {} + for (const key in schemas) this.ajv.addSchema(schemas[key], `#/components/schemas/${key}`) + } + + validate (schema: OpenAPIV3.SchemaObject, data: any): Evaluation { + const validate = this.ajv.compile(schema) + const valid = validate(data) + return { + result: valid ? Result.PASSED : Result.FAILED, + message: valid ? undefined : this.ajv.errorsText(validate.errors) + } + } +} diff --git a/tools/src/tester/SpecParser.ts b/tools/src/tester/SpecParser.ts new file mode 100644 index 000000000..d97d04a48 --- /dev/null +++ b/tools/src/tester/SpecParser.ts @@ -0,0 +1,36 @@ +import { type OpenAPIV3 } from 'openapi-types' +import { resolve_ref } from '../../helpers' +import { type Chapter } from './types/story.types' +import { type ParsedOperation } from './types/spec.types' +import _ from 'lodash' + +export default class SpecParser { + private readonly spec: OpenAPIV3.Document + private cached_operations: Record = {} + + constructor (spec: OpenAPIV3.Document) { + this.spec = spec + } + + locate_operation (chapter: Chapter): ParsedOperation { + const path = chapter.path + const method = chapter.method.toLowerCase() as OpenAPIV3.HttpMethods + const cache_key = path + method + if (this.cached_operations[cache_key] != null) return this.cached_operations[cache_key] + const operation = this.spec.paths[path]?.[method] + if (operation == null) throw new Error(`Operation "${method.toUpperCase()} ${path}" not found in the spec.`) + this.#deref(operation) + const parameters = _.keyBy(operation.parameters ?? [], 'name') + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + this.cached_operations[cache_key] = { ...operation, parameters } as ParsedOperation + return this.cached_operations[cache_key] + } + + #deref (obj: any): any { + if (obj == null) return obj + else if (obj.$ref != null) return resolve_ref(obj.$ref as string, this.spec) + else if (Array.isArray(obj)) return obj.map((item) => this.#deref(item)) + else if (typeof obj === 'object') for (const key in obj) obj[key] = this.#deref(obj[key]) + return obj + } +} diff --git a/tools/src/tester/StoryEvaluator.ts b/tools/src/tester/StoryEvaluator.ts new file mode 100644 index 000000000..1f2103734 --- /dev/null +++ b/tools/src/tester/StoryEvaluator.ts @@ -0,0 +1,78 @@ +import { type Chapter, type Story, type SupplementalChapter } from './types/story.types' +import { type ChapterEvaluation, Result, type StoryEvaluation } from './types/eval.types' +import ChapterEvaluator from './ChapterEvaluator' + +export interface StoryFile { + display_path: string + full_path: string + story: Story +} + +export default class StoryEvaluator { + story: Story + display_path: string + full_path: string + skipped: boolean = false + result: Result = Result.PASSED + + constructor (story_file: StoryFile) { + this.story = story_file.story + this.display_path = story_file.display_path + this.full_path = story_file.full_path + } + + async evaluate (): Promise { + if (this.story.skipped) { + return { + result: Result.SKIPPED, + display_path: this.display_path, + full_path: this.full_path, + description: this.story.description, + chapters: [] + } + } + return { + display_path: this.display_path, + full_path: this.full_path, + description: this.story.description, + prologues: await this.#evaluate_supplemental_chapters(this.story.prologues), + chapters: await this.#evaluate_chapters(this.story.chapters), + epilogues: await this.#evaluate_supplemental_chapters(this.story.epilogues), + result: this.result + } + } + + async #evaluate_chapters (chapters: Chapter[]): Promise { + let skipped: boolean = this.skipped + if (skipped) return [] + + const evaluations: ChapterEvaluation[] = [] + + for (const chapter of chapters) { + const evaluator = new ChapterEvaluator(chapter) + const evaluation = await evaluator.evaluate(skipped) + skipped = skipped || evaluation.result === Result.ERROR + if (evaluation.result === Result.FAILED) this.result = Result.FAILED + if (evaluation.result === Result.ERROR) this.result = Result.ERROR + evaluations.push(evaluation) + } + + return evaluations + } + + async #evaluate_supplemental_chapters (chapters: SupplementalChapter[]): Promise { + const evaluations: ChapterEvaluation[] = [] + for (const chapter of chapters) { + const title = `${chapter.method} ${chapter.path}` + try { + await globalThis.chapter_reader.read(chapter, chapter.ignore_errors) + evaluations.push({ title, result: Result.PASSED }) + } catch (error) { + this.result = Result.ERROR + this.skipped = true + evaluations.push({ title, result: Result.ERROR, message: (error as Error).message }) + } + } + return evaluations + } +} diff --git a/tools/src/tester/TestsRunner.ts b/tools/src/tester/TestsRunner.ts new file mode 100644 index 000000000..3e0f923ce --- /dev/null +++ b/tools/src/tester/TestsRunner.ts @@ -0,0 +1,65 @@ +import { type OpenAPIV3 } from 'openapi-types' +import SpecParser from './SpecParser' +import ChapterReader from './ChapterReader' +import SchemaValidator from './SchemaValidator' +import StoryEvaluator, { type StoryFile } from './StoryEvaluator' +import fs from 'fs' +import { type Story } from './types/story.types' +import { read_yaml } from '../../helpers' +import { type StoryEvaluation } from './types/eval.types' +import ResultsDisplayer from './ResultsDisplayer' + +declare global { + // eslint-disable-next-line no-var + var chapter_reader: ChapterReader + // eslint-disable-next-line no-var + var schema_validator: SchemaValidator + // eslint-disable-next-line no-var + var spec_parser: SpecParser +} + +export default class TestsRunner { + path: string // Path to a story file or a directory containing story files + + constructor (spec: OpenAPIV3.Document, path: string) { + // TODO: Grab server URL from environment variable and add authentication. + global.chapter_reader = new ChapterReader('http://localhost:9200') + global.spec_parser = new SpecParser(spec) + global.schema_validator = new SchemaValidator(spec) + this.path = path + } + + async run (ignore_skipped = false, ignore_passed = true): Promise { + const evaluations = await this.evaluate() + for (const evaluation of evaluations) { + const displayer = new ResultsDisplayer(evaluation) + displayer.display(ignore_skipped, ignore_passed) + } + } + + async evaluate (): Promise { + const story_files = this.#collect_story_files(`${process.cwd()}/${this.path}`, '', '') + const promises = story_files.map(async story_file => { + const evaluator = new StoryEvaluator(story_file) + return await evaluator.evaluate() + }) + return (await Promise.all(promises)).sort((a, b) => a.display_path.localeCompare(b.display_path)) + } + + #collect_story_files (folder: string, file: string, prefix: string): StoryFile[] { + const path = file === '' ? folder : `${folder}/${file}` + const next_prefix = prefix === '' ? file : `${prefix}/${file}` + if (fs.statSync(path).isFile()) { + const story = read_yaml(path) as Story + return [{ + display_path: next_prefix === '' ? (path.split('/').reverse()[0]) : next_prefix, + full_path: path, + story + }] + } else { + return fs.readdirSync(path).flatMap(next_file => { + return this.#collect_story_files(path, next_file, next_prefix) + }) + } + } +} diff --git a/tools/src/tester/_generate_story_types.ts b/tools/src/tester/_generate_story_types.ts new file mode 100644 index 000000000..53d94a93c --- /dev/null +++ b/tools/src/tester/_generate_story_types.ts @@ -0,0 +1,27 @@ +import * as js2ts from 'json-schema-to-typescript' +import fs from 'fs' + +void js2ts.compileFromFile('json_schemas/story.schema.yaml', + { + cwd: 'json_schemas/', + additionalProperties: false, + ignoreMinAndMaxItems: true, + unreachableDefinitions: true, + declareExternallyReferenced: true, + unknownAny: false, + style: { + singleQuote: true, + singleAttributePerLine: true + }, + // multiline comment + bannerComment: `/* eslint-disable */ + +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file by running: + * "npx ts-node tools/src/tester/_generate_story_types.ts" in a terminal. + */` + + }) + .then(ts => { fs.writeFileSync('tools/src/tester/types/story.types.ts', ts) }) diff --git a/tools/src/tester/helpers.ts b/tools/src/tester/helpers.ts new file mode 100644 index 000000000..49e6f4baa --- /dev/null +++ b/tools/src/tester/helpers.ts @@ -0,0 +1,9 @@ +import { type Evaluation, Result } from './types/eval.types' + +export function overall_result (evaluations: Evaluation[]): Result { + if (evaluations.length === 0) return Result.PASSED + if (evaluations.some(e => e.result === Result.ERROR)) return Result.ERROR + if (evaluations.some(e => e.result === Result.FAILED)) return Result.FAILED + if (evaluations.every(e => e.result === Result.SKIPPED)) return Result.SKIPPED + return Result.PASSED +} diff --git a/tools/src/tester/start.ts b/tools/src/tester/start.ts new file mode 100644 index 000000000..29e6f2a48 --- /dev/null +++ b/tools/src/tester/start.ts @@ -0,0 +1,21 @@ +import OpenApiMerger from '../merger/OpenApiMerger' +import { LogLevel } from '../Logger' +import TestsRunner from './TestsRunner' +import { Command, Option } from '@commander-js/extra-typings' + +const command = new Command() + .description('Run test stories against the OpenSearch spec.') + .addOption(new Option('--spec_path ', 'path to the root folder of the multi-file spec').default('./spec')) + .addOption(new Option('--tests_path ', 'path to the root folder of the tests').default('./tests')) + .addOption(new Option('--ignore_skipped ', 'whether to ignore SKIPPED result').default('N')) + .addOption(new Option('--ignore_passed ', 'whether to ignore PASSED result').default('Y')) + .allowExcessArguments(false) + .parse() +const opts = command.opts() +const ignore_passed = opts.ignore_passed === 'Y' || opts.ignore_passed === 'y' +const ignore_skipped = opts.ignore_skipped === 'Y' || opts.ignore_skipped === 'y' +const spec = (new OpenApiMerger(opts.spec_path, LogLevel.error)).merge() +const runner = new TestsRunner(spec, opts.tests_path) +void runner.run(ignore_skipped, ignore_passed).then(() => { + console.log('Tests run successfully.') +}) diff --git a/tools/src/tester/types/eval.types.ts b/tools/src/tester/types/eval.types.ts new file mode 100644 index 000000000..e47e11276 --- /dev/null +++ b/tools/src/tester/types/eval.types.ts @@ -0,0 +1,38 @@ +export type LibraryEvaluation = StoryEvaluation[] + +export interface StoryEvaluation { + result: Result + display_path: string + full_path: string + description: string + message?: string + chapters: ChapterEvaluation[] + epilogues?: ChapterEvaluation[] + prologues?: ChapterEvaluation[] +} + +export interface ChapterEvaluation { + title: string + result: Result + message?: string + request?: { + parameters?: Record + requestBody?: Evaluation + } + response?: { + status: Evaluation + payload: Evaluation + } +} + +export interface Evaluation { + result: Result + message?: string +} + +export enum Result { + PASSED = 'PASSED', + FAILED = 'FAILED', + SKIPPED = 'SKIPPED', + ERROR = 'ERROR', +} diff --git a/tools/src/tester/types/spec.types.ts b/tools/src/tester/types/spec.types.ts new file mode 100644 index 000000000..6a34db327 --- /dev/null +++ b/tools/src/tester/types/spec.types.ts @@ -0,0 +1,26 @@ +import { type OpenAPIV3 } from 'openapi-types' + +export type ParsedOperation = OpenAPIV3.OperationObject & { + parameters: Record + requestBody: ParsedRequestBody + responses: Record +} + +export type ParsedRequestBody = OpenAPIV3.RequestBodyObject & { + content: Record +} + +export type ParsedResponse = OpenAPIV3.ResponseObject & { + content: Record +} + +export type ParsedMediaType = OpenAPIV3.MediaTypeObject & { + schema: OpenAPIV3.SchemaObject +} + +export interface ParsedParameter { + name: string + in: string + required: boolean + schema: OpenAPIV3.SchemaObject +} diff --git a/tools/src/tester/types/story.types.ts b/tools/src/tester/types/story.types.ts new file mode 100644 index 000000000..309d07c42 --- /dev/null +++ b/tools/src/tester/types/story.types.ts @@ -0,0 +1,107 @@ +/* eslint-disable */ + +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file by running: + * "npx ts-node tools/src/tester/_generate_story_types.ts" in a terminal. + */ + +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "SupplementalChapter". + */ +export type SupplementalChapter = ChapterRequest & { + /** + * If true, treat all non-2XX responses as successful. + */ + ignore_errors?: boolean; +}; +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "Parameter". + */ +export type Parameter = (string | number | boolean)[] | string | number | boolean; +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "Payload". + */ +export type Payload = {} | any[] | string | number | boolean; +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "Chapter". + */ +export type Chapter = ChapterRequest & { + /** + * A brief description of the chapter. + */ + synopsis: string; + response?: ExpectedResponse; +}; +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "ReadChapter". + */ +export type ReadChapter = Chapter & { + response: ActualResponse; +}; + +export interface Story { + $schema?: string; + /** + * If true, the story will be skipped. + */ + skipped?: boolean; + description: string; + prologues: SupplementalChapter[]; + epilogues: SupplementalChapter[]; + /** + * @minItems 1 + */ + chapters: Chapter[]; +} +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "ChapterRequest". + */ +export interface ChapterRequest { + path: string; + method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; + parameters?: { + [k: string]: Parameter; + }; + requestBody?: RequestBody; +} +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "RequestBody". + */ +export interface RequestBody { + content_type: string; + payload: Payload; +} +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "ExpectedResponse". + */ +export interface ExpectedResponse { + /** + * The expected HTTP status code. Default to 200. + */ + status: number; + content_type?: string; + payload?: Payload; +} +/** + * This interface was referenced by `Story`'s JSON-Schema + * via the `definition` "ActualResponse". + */ +export interface ActualResponse { + status: number; + content_type: string; + payload: Payload; + /** + * Error message for non 2XX responses. + */ + message?: string; +}