diff --git a/package-lock.json b/package-lock.json index efb49a279..0d5f95ce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@eslint/eslintrc": "^3.0.2", "@eslint/js": "^9.1.1", "@types/jest": "^29.5.12", + "@types/qs": "^6.9.15", "@typescript-eslint/eslint-plugin": "^6.21.0", "ajv-errors": "^3.0.0", "eslint": "^8.57.0", @@ -41,6 +42,7 @@ "jest": "^29.7.0", "json-diff-ts": "^4.0.1", "json-schema-to-typescript": "^14.0.4", + "qs": "^6.12.1", "ts-jest": "^29.1.2" } }, @@ -2035,6 +2037,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -7069,6 +7077,21 @@ } ] }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index ddcd4a510..9d3ce6a00 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@eslint/eslintrc": "^3.0.2", "@eslint/js": "^9.1.1", "@types/jest": "^29.5.12", + "@types/qs": "^6.9.15", "@typescript-eslint/eslint-plugin": "^6.21.0", "ajv-errors": "^3.0.0", "eslint": "^8.57.0", @@ -50,6 +51,7 @@ "jest": "^29.7.0", "json-diff-ts": "^4.0.1", "json-schema-to-typescript": "^14.0.4", + "qs": "^6.12.1", "ts-jest": "^29.1.2" } } diff --git a/spec/namespaces/cat.yaml b/spec/namespaces/cat.yaml index 4d0ec61a4..316fd086c 100644 --- a/spec/namespaces/cat.yaml +++ b/spec/namespaces/cat.yaml @@ -789,6 +789,8 @@ components: cat.aliases@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -797,6 +799,8 @@ components: cat.all_pit_segments@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -805,6 +809,8 @@ components: cat.allocation@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -815,6 +821,8 @@ components: cat.count@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -823,6 +831,8 @@ components: cat.fielddata@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -831,6 +841,8 @@ components: cat.health@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -845,6 +857,8 @@ components: cat.indices@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -853,6 +867,8 @@ components: cat.master@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -861,6 +877,8 @@ components: cat.nodeattrs@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -869,6 +887,8 @@ components: cat.nodes@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -877,6 +897,8 @@ components: cat.pending_tasks@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -885,6 +907,8 @@ components: cat.pit_segments@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -893,6 +917,8 @@ components: cat.plugins@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -901,6 +927,8 @@ components: cat.recovery@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -909,6 +937,8 @@ components: cat.repositories@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -917,6 +947,8 @@ components: cat.segment_replication@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -925,6 +957,8 @@ components: cat.segments@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -933,6 +967,8 @@ components: cat.shards@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -941,6 +977,8 @@ components: cat.snapshots@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -949,6 +987,8 @@ components: cat.tasks@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -957,6 +997,8 @@ components: cat.templates@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -965,6 +1007,8 @@ components: cat.thread_pool@200: description: '' content: + text/plain: + type: string application/json: schema: type: array @@ -1009,9 +1053,9 @@ components: in: query description: Return help information. schema: + description: Return help information. type: boolean default: false - description: Return help information. cat.aliases::query.local: name: local in: query diff --git a/tests/_core/bulk.yaml b/tests/_core/bulk.yaml index 9bd3091a4..04e03e241 100644 --- a/tests/_core/bulk.yaml +++ b/tests/_core/bulk.yaml @@ -1,6 +1,5 @@ $schema: ../../json_schemas/test_story.schema.yaml -skip: false description: Test bulk endpoint. epilogues: - path: /books,movies diff --git a/tests/_core/search.yaml b/tests/_core/search.yaml index f92613c7c..0e4492b83 100644 --- a/tests/_core/search.yaml +++ b/tests/_core/search.yaml @@ -1,6 +1,5 @@ $schema: ../../json_schemas/test_story.schema.yaml -skip: false description: Test search endpoint. prologues: - path: /movies/_doc diff --git a/tests/cat/aliases.yaml b/tests/cat/aliases.yaml new file mode 100644 index 000000000..d589354a4 --- /dev/null +++ b/tests/cat/aliases.yaml @@ -0,0 +1,19 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/aliases endpoints. +chapters: + - synopsis: Cat with a text response. + path: /_cat/aliases + method: GET + response: + status: 200 + content_type: text/plain + - synopsis: Cat with a json response. + path: /_cat/aliases + parameters: + format: json + method: GET + response: + status: 200 + content_type: application/json + payload: [] diff --git a/tests/cat/health.yaml b/tests/cat/health.yaml new file mode 100644 index 000000000..0a462fe9a --- /dev/null +++ b/tests/cat/health.yaml @@ -0,0 +1,58 @@ +$schema: ../../json_schemas/test_story.schema.yaml + +description: Test cat/health endpoints. +chapters: + - synopsis: Cat with a default text response. + method: GET + path: /_cat/health + response: + status: 200 + content_type: text/plain + - synopsis: Cat with verbose output (v=true). + method: GET + path: /_cat/health + parameters: + v: true + response: + status: 200 + content_type: text/plain + - synopsis: Cat with headers (h=header1,header2). + method: GET + path: /_cat/health + parameters: + h: + - status + - timestamp + response: + status: 200 + content_type: text/plain + - synopsis: Cat displaying all available headers (help=true). + method: GET + path: /_cat/health + parameters: + help: true + response: + status: 200 + content_type: text/plain + - synopsis: Cat with sorted results. + method: GET + path: /_cat/health + parameters: + s: + - status + response: + status: 200 + content_type: text/plain + - synopsis: Cat with a json response. + method: GET + path: /_cat/health + parameters: + format: json + response: + status: 200 + content_type: application/json + payload: + - node.total: '1' + status: yellow + node.data: '1' + discovered_cluster_manager: 'true' diff --git a/tests/cat/index.yaml b/tests/cat/index.yaml index cbe2e228f..7127fe574 100644 --- a/tests/cat/index.yaml +++ b/tests/cat/index.yaml @@ -1,6 +1,5 @@ $schema: ../../json_schemas/test_story.schema.yaml -skip: false description: Test cat endpoints. chapters: - synopsis: Cat with a json response. diff --git a/tools/src/tester/ChapterReader.ts b/tools/src/tester/ChapterReader.ts index fd9dd6acf..40ed5d1eb 100644 --- a/tools/src/tester/ChapterReader.ts +++ b/tools/src/tester/ChapterReader.ts @@ -12,6 +12,7 @@ import { type OpenSearchHttpClient } from '../OpenSearchHttpClient' import { type StoryOutputs } from './StoryOutputs' import { Logger } from 'Logger' import { to_json, to_ndjson } from '../helpers' +import qs from 'qs' export default class ChapterReader { private readonly _client: OpenSearchHttpClient @@ -37,7 +38,10 @@ export default class ChapterReader { method: chapter.method, headers: { 'Content-Type' : content_type }, params, - data: request_data + data: request_data, + paramsSerializer: (params) => { // eslint-disable-line @typescript-eslint/naming-convention + return qs.stringify(params, { arrayFormat: 'comma' }) + } }).then(r => { this.logger.info(`<= ${r.status} (${r.headers['content-type']}) | ${to_json(r.data)}`) response.status = r.status diff --git a/tools/tests/tester/ChapterReader.test.ts b/tools/tests/tester/ChapterReader.test.ts index 241676a3f..d01fef07a 100644 --- a/tools/tests/tester/ChapterReader.test.ts +++ b/tools/tests/tester/ChapterReader.test.ts @@ -48,7 +48,14 @@ describe('ChapterReader', () => { expect(result).toEqual({ status: 200, content_type: 'application/json', payload: undefined }) expect(mocked_axios.request.mock.calls).toEqual([ - [{ url: 'path', method: 'GET', headers: { 'Content-Type': 'application/json' }, params: {}, data: undefined }] + [{ + url: 'path', + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + params: {}, + paramsSerializer: expect.any(Function), + data: undefined + }] ]) }) @@ -64,10 +71,43 @@ describe('ChapterReader', () => { expect(result).toEqual({ status: 200, content_type: 'application/json', payload: undefined }) expect(mocked_axios.request.mock.calls).toEqual([ - [{ url: 'books/path', method: 'GET', headers: { 'Content-Type': 'application/json' }, params: {}, data: undefined }] + [{ + url: 'books/path', + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + params: {}, + paramsSerializer: expect.any(Function), + data: undefined + }] ]) }) + it('resolves array parameters', async () => { + const result = await reader.read({ + id: 'id', + path: '/path', + method: 'GET', + parameters: { indexes: ['book1', 'book2'] }, + request_body: undefined, + output: undefined + }, new StoryOutputs()) + + expect(result).toEqual({ status: 200, content_type: 'application/json', payload: undefined }) + expect(mocked_axios.request.mock.calls).toEqual([ + [{ + url: '/path', + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + params: { indexes: ['book1', 'book2'] }, + paramsSerializer: expect.any(Function), + data: undefined + }] + ]) + + const call = mocked_axios.request.mock.calls[0][0] as any + expect(call.paramsSerializer(call.params)).toEqual(`indexes=${encodeURIComponent('book1,book2')}`) + }) + it('sends a POST request', async () => { const result = await reader.read({ id: 'id', @@ -80,7 +120,14 @@ describe('ChapterReader', () => { expect(result).toEqual({ status: 200, content_type: 'application/json', payload: undefined }) expect(mocked_axios.request.mock.calls).toEqual([ - [{ url: 'path', method: 'POST', headers: { 'Content-Type': 'application/json' }, params: { 'x': 1 }, data: { 'body': 'present' } }] + [{ + url: 'path', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + params: { 'x': 1 }, + paramsSerializer: expect.any(Function), + data: { 'body': 'present' } + }] ]) }) @@ -99,7 +146,14 @@ describe('ChapterReader', () => { expect(result).toEqual({ status: 200, content_type: 'application/json', payload: undefined }) expect(mocked_axios.request.mock.calls).toEqual([ - [{ url: 'path', method: 'POST', headers: { 'Content-Type': 'application/x-ndjson' }, params: { 'x': 1 }, data: "{\"body\":\"present\"}\n"}] + [{ + url: 'path', + method: 'POST', + headers: { 'Content-Type': 'application/x-ndjson' }, + params: { 'x': 1 }, + paramsSerializer: expect.any(Function), + data: "{\"body\":\"present\"}\n" + }] ]) }) })