From 795035b13317dcf52cdfbbac9611bbea6833bdc3 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 26 Jan 2022 12:08:17 +0100 Subject: [PATCH] chore: add client tests (#88) * chore: add client testing * chore: skip when template is missing * chore: skip when tests for client don't exist * test region WIP * fix tests * remove unnecessary url from echo requester * fix: update template to check region * fix: add hasReigonalHost to generators * fix: make regional optional in client testing * fix: test asynchronous errors * update tests * fix: remove duplicated import statements * chore: remove unused part * chore: format cts output * fix: remove createIndex * chore: do not use post- script in package.json * fix: type issues * Update tests/CTS/client/templates/javascript/suite.mustache Co-authored-by: Pierre Millot * chore: add eslint to tests * chore: update output * run java cts on the CI * Revert "run java cts on the CI" This reverts commit 7a0358d6853c2f93cacf2ac58280085e848150a4. Co-authored-by: Pierre Millot --- .../client-abtesting/src/abtestingApi.ts | 12 +- .../client-analytics/src/analyticsApi.ts | 7 + .../client-insights/src/insightsApi.ts | 7 + .../src/personalizationApi.ts | 12 +- .../src/querySuggestionsApi.ts | 12 +- .../client-search/src/searchApi.ts | 7 + .../recommend/src/recommendApi.ts | 7 + openapitools.json | 1 + templates/javascript/api-single.mustache | 16 +- tests/CTS/client/analytics/basic.json | 33 ++++ tests/CTS/client/search/basic.json | 17 ++ .../javascript/createClient.mustache | 8 + .../templates/javascript/expected.mustache | 3 + .../templates/javascript/method.mustache | 1 + .../client/templates/javascript/step.mustache | 12 ++ .../templates/javascript/suite.mustache | 65 +++++++ .../templates/javascript/variable.mustache | 1 + .../{integration => integrations}/.gitkeep | 0 .../javascript/tests/client/analytics.test.ts | 50 ++++++ .../javascript/tests/client/search.test.ts | 36 ++++ tests/package.json | 7 +- tests/src/client/.gitkeep | 0 tests/src/client/generate.ts | 170 ++++++++++++++++++ tests/src/client/main.ts | 20 +++ tests/src/client/types.ts | 54 ++++++ tests/src/methods/requests/generate.ts | 14 +- tests/src/methods/requests/main.ts | 42 ++--- tests/src/utils.ts | 46 +++++ 28 files changed, 614 insertions(+), 46 deletions(-) create mode 100644 tests/CTS/client/analytics/basic.json create mode 100644 tests/CTS/client/search/basic.json create mode 100644 tests/CTS/client/templates/javascript/createClient.mustache create mode 100644 tests/CTS/client/templates/javascript/expected.mustache create mode 100644 tests/CTS/client/templates/javascript/method.mustache create mode 100644 tests/CTS/client/templates/javascript/step.mustache create mode 100644 tests/CTS/client/templates/javascript/suite.mustache create mode 100644 tests/CTS/client/templates/javascript/variable.mustache rename tests/CTS/{integration => integrations}/.gitkeep (100%) create mode 100644 tests/output/javascript/tests/client/analytics.test.ts create mode 100644 tests/output/javascript/tests/client/search.test.ts delete mode 100644 tests/src/client/.gitkeep create mode 100644 tests/src/client/generate.ts create mode 100644 tests/src/client/main.ts create mode 100644 tests/src/client/types.ts diff --git a/clients/algoliasearch-client-javascript/client-abtesting/src/abtestingApi.ts b/clients/algoliasearch-client-javascript/client-abtesting/src/abtestingApi.ts index bbdb91880b..1a89e077ac 100644 --- a/clients/algoliasearch-client-javascript/client-abtesting/src/abtestingApi.ts +++ b/clients/algoliasearch-client-javascript/client-abtesting/src/abtestingApi.ts @@ -55,6 +55,16 @@ export class AbtestingApi { region: 'de' | 'us', options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + if (!region) { + throw new Error('`region` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ @@ -72,7 +82,7 @@ export class AbtestingApi { }); } - getDefaultHosts(region: 'de' | 'us' = 'us'): Host[] { + getDefaultHosts(region: 'de' | 'us'): Host[] { return [ { url: `analytics.${region}.algolia.com`, diff --git a/clients/algoliasearch-client-javascript/client-analytics/src/analyticsApi.ts b/clients/algoliasearch-client-javascript/client-analytics/src/analyticsApi.ts index 4ad0aab590..c00490ab8d 100644 --- a/clients/algoliasearch-client-javascript/client-analytics/src/analyticsApi.ts +++ b/clients/algoliasearch-client-javascript/client-analytics/src/analyticsApi.ts @@ -70,6 +70,13 @@ export class AnalyticsApi { region: 'de' | 'us', options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ diff --git a/clients/algoliasearch-client-javascript/client-insights/src/insightsApi.ts b/clients/algoliasearch-client-javascript/client-insights/src/insightsApi.ts index f5b7cf299f..208fc062d6 100644 --- a/clients/algoliasearch-client-javascript/client-insights/src/insightsApi.ts +++ b/clients/algoliasearch-client-javascript/client-insights/src/insightsApi.ts @@ -52,6 +52,13 @@ export class InsightsApi { apiKey: string, options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ diff --git a/clients/algoliasearch-client-javascript/client-personalization/src/personalizationApi.ts b/clients/algoliasearch-client-javascript/client-personalization/src/personalizationApi.ts index 251fde7b43..e7e9336553 100644 --- a/clients/algoliasearch-client-javascript/client-personalization/src/personalizationApi.ts +++ b/clients/algoliasearch-client-javascript/client-personalization/src/personalizationApi.ts @@ -55,6 +55,16 @@ export class PersonalizationApi { region: 'eu' | 'us', options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + if (!region) { + throw new Error('`region` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ @@ -72,7 +82,7 @@ export class PersonalizationApi { }); } - getDefaultHosts(region: 'eu' | 'us' = 'us'): Host[] { + getDefaultHosts(region: 'eu' | 'us'): Host[] { return [ { url: `personalization.${region}.algolia.com`, diff --git a/clients/algoliasearch-client-javascript/client-query-suggestions/src/querySuggestionsApi.ts b/clients/algoliasearch-client-javascript/client-query-suggestions/src/querySuggestionsApi.ts index 9eba63df34..c5cc944a96 100644 --- a/clients/algoliasearch-client-javascript/client-query-suggestions/src/querySuggestionsApi.ts +++ b/clients/algoliasearch-client-javascript/client-query-suggestions/src/querySuggestionsApi.ts @@ -57,6 +57,16 @@ export class QuerySuggestionsApi { region: 'eu' | 'us', options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + if (!region) { + throw new Error('`region` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ @@ -74,7 +84,7 @@ export class QuerySuggestionsApi { }); } - getDefaultHosts(region: 'eu' | 'us' = 'us'): Host[] { + getDefaultHosts(region: 'eu' | 'us'): Host[] { return [ { url: `query-suggestions.${region}.algolia.com`, diff --git a/clients/algoliasearch-client-javascript/client-search/src/searchApi.ts b/clients/algoliasearch-client-javascript/client-search/src/searchApi.ts index f4111c74c8..3232e89315 100644 --- a/clients/algoliasearch-client-javascript/client-search/src/searchApi.ts +++ b/clients/algoliasearch-client-javascript/client-search/src/searchApi.ts @@ -106,6 +106,13 @@ export class SearchApi { apiKey: string, options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ diff --git a/clients/algoliasearch-client-javascript/recommend/src/recommendApi.ts b/clients/algoliasearch-client-javascript/recommend/src/recommendApi.ts index 4be4156be2..d52af3ceca 100644 --- a/clients/algoliasearch-client-javascript/recommend/src/recommendApi.ts +++ b/clients/algoliasearch-client-javascript/recommend/src/recommendApi.ts @@ -52,6 +52,13 @@ export class RecommendApi { apiKey: string, options?: { requester?: Requester; hosts?: Host[] } ) { + if (!appId) { + throw new Error('`appId` is missing.'); + } + if (!apiKey) { + throw new Error('`apiKey` is missing.'); + } + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ diff --git a/openapitools.json b/openapitools.json index 88ac1ba4d6..4c4eba0dba 100644 --- a/openapitools.json +++ b/openapitools.json @@ -86,6 +86,7 @@ "packageName": "@algolia/client-analytics", "hasRegionalHost": true, "isDeHost": true, + "fallbackToUS": true, "host": "analytics" } }, diff --git a/templates/javascript/api-single.mustache b/templates/javascript/api-single.mustache index 0009e2a1a2..23e55d62c6 100644 --- a/templates/javascript/api-single.mustache +++ b/templates/javascript/api-single.mustache @@ -57,6 +57,20 @@ export class {{classname}} { {{/hasRegionalHost}} options?: {requester?: Requester, hosts?: Host[]} ) { + if (!appId) { + throw new Error("`appId` is missing."); + } + if (!apiKey) { + throw new Error("`apiKey` is missing."); + } + {{#hasRegionalHost}} + {{^fallbackToUS}} + if (!region) { + throw new Error("`region` is missing."); + } + {{/fallbackToUS}} + {{/hasRegionalHost}} + this.setAuthentication({ appId, apiKey }); this.transporter = new Transporter({ @@ -96,7 +110,7 @@ export class {{classname}} { {{^isSearchHost}} {{#hasRegionalHost}} - public getDefaultHosts(region: {{#isDeHost}}'de'{{/isDeHost}}{{#isEuHost}}'eu'{{/isEuHost}} | 'us' = 'us'): Host[] { + public getDefaultHosts(region: {{#isDeHost}}'de'{{/isDeHost}}{{#isEuHost}}'eu'{{/isEuHost}} | 'us'{{#fallbackToUS}} = 'us'{{/fallbackToUS}}): Host[] { return [{ url: `{{{host}}}.${region}.algolia.com`, accept: 'readWrite', protocol: 'https' }]; } {{/hasRegionalHost}} diff --git a/tests/CTS/client/analytics/basic.json b/tests/CTS/client/analytics/basic.json new file mode 100644 index 0000000000..7d539ef3c3 --- /dev/null +++ b/tests/CTS/client/analytics/basic.json @@ -0,0 +1,33 @@ +[ + { + "testName": "does not throw when region is not given", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "my-app-id", + "apiKey": "my-api-key", + "region": "" + }, + "expected": { + "error": false + } + } + ] + }, + { + "testName": "getAverageClickPosition throws without index", + "steps": [ + { + "type": "method", + "object": "$client", + "path": "getClickPositions", + "parameters": [{}], + "expected": { + "error": "Parameter `index` is required when calling `getClickPositions`." + } + } + ] + } +] diff --git a/tests/CTS/client/search/basic.json b/tests/CTS/client/search/basic.json new file mode 100644 index 0000000000..79a8e709d2 --- /dev/null +++ b/tests/CTS/client/search/basic.json @@ -0,0 +1,17 @@ +[ + { + "testName": "client throws with invalid parameters", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "apiKey": "blah" + }, + "expected": { + "error": "`appId` is missing." + } + } + ] + } +] diff --git a/tests/CTS/client/templates/javascript/createClient.mustache b/tests/CTS/client/templates/javascript/createClient.mustache new file mode 100644 index 0000000000..bcb79ba239 --- /dev/null +++ b/tests/CTS/client/templates/javascript/createClient.mustache @@ -0,0 +1,8 @@ +new {{client}}( + '{{parameters.appId}}', + '{{parameters.apiKey}}', + {{#hasRegionalHost}}'{{parameters.region}}',{{/hasRegionalHost}} + { + requester: new EchoRequester() + } +) \ No newline at end of file diff --git a/tests/CTS/client/templates/javascript/expected.mustache b/tests/CTS/client/templates/javascript/expected.mustache new file mode 100644 index 0000000000..8313604125 --- /dev/null +++ b/tests/CTS/client/templates/javascript/expected.mustache @@ -0,0 +1,3 @@ +{{#length}} + expect(actual).toHaveLength({{length}}); +{{/length}} \ No newline at end of file diff --git a/tests/CTS/client/templates/javascript/method.mustache b/tests/CTS/client/templates/javascript/method.mustache new file mode 100644 index 0000000000..fdf5a349e2 --- /dev/null +++ b/tests/CTS/client/templates/javascript/method.mustache @@ -0,0 +1 @@ +{{object}}{{#path}}.{{.}}{{/path}}({{{parameters}}}); \ No newline at end of file diff --git a/tests/CTS/client/templates/javascript/step.mustache b/tests/CTS/client/templates/javascript/step.mustache new file mode 100644 index 0000000000..7b1854b3d4 --- /dev/null +++ b/tests/CTS/client/templates/javascript/step.mustache @@ -0,0 +1,12 @@ +{{#isCreateClient}} + const $client = {{> createClient}} + actual = $client; +{{/isCreateClient}} + +{{#isVariable}} + actual = {{> variable}} +{{/isVariable}} + +{{#isMethod}} + actual = {{> method}} +{{/isMethod}} \ No newline at end of file diff --git a/tests/CTS/client/templates/javascript/suite.mustache b/tests/CTS/client/templates/javascript/suite.mustache new file mode 100644 index 0000000000..db0d85b295 --- /dev/null +++ b/tests/CTS/client/templates/javascript/suite.mustache @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable require-await */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +// @ts-nocheck +import { {{client}}, EchoRequester } from '{{{import}}}'; + +const appId = process.env.ALGOLIA_APPLICATION_ID || 'Algolia-API-Key'; +const apiKey = process.env.ALGOLIA_SEARCH_KEY || 'Algolia-Application-Id'; + +function createClient(): {{client}} { + return new {{client}}(appId, apiKey, {{#hasRegionalHost}}'us', {{/hasRegionalHost}}{ requester: new EchoRequester() }); +} + +{{#blocks}} +describe('{{operationId}}', () => { + {{#tests}} + test('{{testName}}', async () => { + {{#autoCreateClient}} + const $client = createClient(); + {{/autoCreateClient}} + + let actual; + {{#steps}} + {{#expectedError}} + await expect(new Promise((resolve, reject) => { + {{> step}} + if (actual instanceof Promise) { + actual.then(resolve).catch(reject); + } else { + resolve(); + } + })).rejects.toThrow("{{{expectedError}}}") + {{/expectedError}} + + {{^expectedError}} + {{#expectedNoError}} + await expect(new Promise((resolve, reject) => { + {{> step}} + if (actual instanceof Promise) { + actual.then(resolve).catch(reject); + } else { + resolve(); + } + })).resolves.not.toThrow(); + {{/expectedNoError}} + + {{^expectedNoError}} + {{> step}} + + if (actual instanceof Promise) { + actual = await actual; + } + + {{#expected}} + {{> expected}} + {{/expected}} + {{/expectedNoError}} + {{/expectedError}} + {{/steps}} + }); + + {{/tests}} +}) + +{{/blocks}} diff --git a/tests/CTS/client/templates/javascript/variable.mustache b/tests/CTS/client/templates/javascript/variable.mustache new file mode 100644 index 0000000000..7b079937d0 --- /dev/null +++ b/tests/CTS/client/templates/javascript/variable.mustache @@ -0,0 +1 @@ +{{object}}{{#path}}.{{.}}{{/path}}; \ No newline at end of file diff --git a/tests/CTS/integration/.gitkeep b/tests/CTS/integrations/.gitkeep similarity index 100% rename from tests/CTS/integration/.gitkeep rename to tests/CTS/integrations/.gitkeep diff --git a/tests/output/javascript/tests/client/analytics.test.ts b/tests/output/javascript/tests/client/analytics.test.ts new file mode 100644 index 0000000000..bb53b0a94f --- /dev/null +++ b/tests/output/javascript/tests/client/analytics.test.ts @@ -0,0 +1,50 @@ +// @ts-nocheck +import { AnalyticsApi, EchoRequester } from '@algolia/client-analytics'; + +const appId = process.env.ALGOLIA_APPLICATION_ID || 'Algolia-API-Key'; +const apiKey = process.env.ALGOLIA_SEARCH_KEY || 'Algolia-Application-Id'; + +function createClient(): AnalyticsApi { + return new AnalyticsApi(appId, apiKey, 'us', { + requester: new EchoRequester(), + }); +} + +describe('basic', () => { + test('does not throw when region is not given', async () => { + let actual; + + await expect( + new Promise((resolve, reject) => { + const $client = new AnalyticsApi('my-app-id', 'my-api-key', '', { + requester: new EchoRequester(), + }); + actual = $client; + + if (actual instanceof Promise) { + actual.then(resolve).catch(reject); + } else { + resolve(); + } + }) + ).resolves.not.toThrow(); + }); + + test('getAverageClickPosition throws without index', async () => { + const $client = createClient(); + + let actual; + await expect( + new Promise((resolve, reject) => { + actual = $client.getClickPositions({}); + if (actual instanceof Promise) { + actual.then(resolve).catch(reject); + } else { + resolve(); + } + }) + ).rejects.toThrow( + 'Parameter `index` is required when calling `getClickPositions`.' + ); + }); +}); diff --git a/tests/output/javascript/tests/client/search.test.ts b/tests/output/javascript/tests/client/search.test.ts new file mode 100644 index 0000000000..0c69c11479 --- /dev/null +++ b/tests/output/javascript/tests/client/search.test.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +// @ts-nocheck +import { SearchApi, EchoRequester } from '@algolia/client-search'; + +const appId = process.env.ALGOLIA_APPLICATION_ID || 'Algolia-API-Key'; +const apiKey = process.env.ALGOLIA_SEARCH_KEY || 'Algolia-Application-Id'; + +function createClient(): SearchApi { + return new SearchApi(appId, apiKey, { requester: new EchoRequester() }); +} + +describe('basic', () => { + test('client throws with invalid parameters', async () => { + let actual; + await expect( + new Promise((resolve, reject) => { + const $client = new SearchApi( + '', + 'blah', + + { + requester: new EchoRequester(), + } + ); + actual = $client; + + if (actual instanceof Promise) { + actual.then(resolve).catch(reject); + } else { + resolve(); + } + }) + ).rejects.toThrow('`appId` is missing.'); + }); +}); diff --git a/tests/package.json b/tests/package.json index a5ea73a8f6..7db4140673 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,10 +6,11 @@ ], "scripts": { "build": "tsc", + "generate": "yarn generate:methods:requets ${0:-javascript} ${1:-search} && yarn generate:client ${0:-javascript} ${1:-search} && yarn format ${0:-javascript}", "generate:methods:requets": "node dist/tests/src/methods/requests/main.js ${0:-javascript} ${1:-search}", - "format": "../scripts/formatter.sh ${0:-javascript} tests/output/${0:-javascript}", - "generate": "yarn generate:methods:requets ${0:-javascript} ${1:-search}", - "start": "yarn build && yarn generate ${0:-javascript} ${1:-search}", + "generate:client": "node dist/tests/src/client/main.js ${0:-javascript} ${1:-search}", + "format": "../scripts/formatter.sh ${0:-javascript} tests/output/${0:-javascript} && yarn lint", + "lint": "eslint --ext=ts ./src", "test:scripts": "jest" }, "devDependencies": { diff --git a/tests/src/client/.gitkeep b/tests/src/client/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/src/client/generate.ts b/tests/src/client/generate.ts new file mode 100644 index 0000000000..c5d05b18ff --- /dev/null +++ b/tests/src/client/generate.ts @@ -0,0 +1,170 @@ +import fsp from 'fs/promises'; + +import Mustache from 'mustache'; + +import openapitools from '../../../openapitools.json'; +import { + walk, + extensionForLanguage, + packageNames, + createClientName, + exists, +} from '../utils'; + +import type { TestsBlock, Test, ModifiedStepForMustache } from './types'; + +async function loadTests(client: string): Promise { + const testsBlocks: TestsBlock[] = []; + const clientPath = `./CTS/client/${client}`; + + if (!(await exists(clientPath))) { + return []; + } + + for await (const file of walk(clientPath)) { + if (!file.name.endsWith('.json')) { + continue; + } + const fileName = file.name.replace('.json', ''); + const fileContent = (await fsp.readFile(file.path)).toString(); + + if (!fileContent) { + throw new Error(`cannot read empty file ${fileName} - ${client} client`); + } + + const tests: Test[] = JSON.parse(fileContent).map((testCase) => { + if (!testCase.testName) { + throw new Error( + `Cannot have a test with no name ${fileName} - ${client} client` + ); + } + return { + autoCreateClient: true, + ...testCase, + }; + }); + + testsBlocks.push({ + operationId: fileName, + tests, + }); + } + + return testsBlocks; +} + +async function loadTemplates( + language: string +): Promise> { + const templates: Record = {}; + const templatePath = `./CTS/client/templates/${language}`; + + await exists(`./CTS/client/templates/javascript`); + if (!(await exists(templatePath))) { + return {}; + } + + for await (const file of walk(templatePath)) { + if (!file.name.endsWith('.mustache')) { + continue; + } + const type = file.name.replace('.mustache', ''); + const fileContent = (await fsp.readFile(file.path)).toString(); + templates[type] = fileContent; + } + return templates; +} + +export async function generateTests( + language: string, + client: string +): Promise { + const testsBlocks = await loadTests(client); + + if (testsBlocks.length === 0) { + // eslint-disable-next-line no-console + console.warn( + `Skipping because tests dont't exist for CTS > generate:client for ${language}-${client}` + ); + return; + } + + const outputPath = `output/${language}/tests/client/`; + await fsp.mkdir(outputPath, { recursive: true }); + const { suite: template, ...partialTemplates } = await loadTemplates( + language + ); + + if (!template) { + // eslint-disable-next-line no-console + console.warn( + `Skipping because template doesn't exist for CTS > generate:client for ${language}-${client}` + ); + return; + } + + const code = Mustache.render( + template, + { + import: packageNames[language][client], + client: createClientName(client), + blocks: modifyForMustache(testsBlocks), + hasRegionalHost: openapitools['generator-cli'].generators[ + `${language}-${client}` + ].additionalProperties.hasRegionalHost + ? true + : undefined, + }, + partialTemplates + ); + await fsp.writeFile( + `${outputPath}/${client}.${extensionForLanguage[language]}`, + code + ); +} + +function serializeParameters(parameters: any): string { + const serialized = JSON.stringify(parameters); + return serialized.slice(1, serialized.length - 1); // remove array bracket surrounding the parameters +} + +function modifyForMustache( + blocks: TestsBlock[] +): Array> { + return blocks.map(({ tests, ...blockRest }) => ({ + ...blockRest, + tests: tests.map(({ steps, ...testRest }) => ({ + ...testRest, + steps: steps.map((step) => { + const base = { + isCreateClient: step.type === 'createClient', + isVariable: step.type === 'variable', + isMethod: step.type === 'method', + }; + + let modified: ModifiedStepForMustache; + if (step.type === 'method') { + modified = { + type: step.type, + object: step.object, + path: step.path, + parameters: step.parameters && serializeParameters(step.parameters), + ...base, + }; + } else { + modified = { ...step, ...base }; + } + + if (step.expected?.error) { + modified.expectedError = step.expected.error; + } + + if (step.expected?.error === false) { + modified.expectedNoError = true; + } + + return modified; + }), + })), + })); +} diff --git a/tests/src/client/main.ts b/tests/src/client/main.ts new file mode 100644 index 0000000000..1906d420ac --- /dev/null +++ b/tests/src/client/main.ts @@ -0,0 +1,20 @@ +import { parseCLI } from '../utils'; + +import { generateTests } from './generate'; + +async function main(): Promise { + const { lang, client } = parseCLI(process.argv, 'generate:client'); + // eslint-disable-next-line no-console + console.log(`Generating CTS > generate:client for ${lang}-${client}`); + + try { + await generateTests(lang, client); + } catch (e) { + if (e instanceof Error) { + // eslint-disable-next-line no-console + console.error(e); + } + } +} + +main(); diff --git a/tests/src/client/types.ts b/tests/src/client/types.ts new file mode 100644 index 0000000000..109969f94f --- /dev/null +++ b/tests/src/client/types.ts @@ -0,0 +1,54 @@ +export type Test = { + testName: string; + autoCreateClient?: boolean; // `true` by default + steps: TStep[]; +}; + +export type Step = CreateClientStep | MethodStep | VariableStep; + +export type ModifiedStepForMustache = { + isCreateClient: boolean; + isVariable: boolean; + isMethod: boolean; + expectedError?: string; + expectedNoError?: true; +} & ( + | CreateClientStep + | VariableStep + | (Omit & { parameters: string }) +); + +export type CreateClientStep = { + type: 'createClient'; + parameters: { + appId: string; + apiKey: string; + }; + expected?: Expected; +}; + +type VariableStep = { + type: 'variable'; + object: string; + path: string[]; + expected?: Expected; +}; + +type MethodStep = { + type: 'method'; + object: string; + path: string[]; + parameters?: any; + expected?: Expected; +}; + +type Expected = { + length?: number; + error?: string | false; + match?: any | { objectContaining: Record }; +}; + +export type TestsBlock = { + operationId: string; + tests: Array>; +}; diff --git a/tests/src/methods/requests/generate.ts b/tests/src/methods/requests/generate.ts index dbc34081e5..3070ba6f2d 100644 --- a/tests/src/methods/requests/generate.ts +++ b/tests/src/methods/requests/generate.ts @@ -2,6 +2,7 @@ import fsp from 'fs/promises'; import Mustache from 'mustache'; +import openapitools from '../../../../openapitools.json'; import { createClientName, packageNames, @@ -37,13 +38,11 @@ async function generateRequestsTests( import: packageNames[language][client], client: createClientName(client), blocks: cts, - hasRegionalHost: [ - 'personalization', - 'analytics', - 'abtesting', - 'query-suggestions', - 'sources', - ].includes(client), + hasRegionalHost: openapitools['generator-cli'].generators[ + `${language}-${client}` + ].additionalProperties.hasRegionalHost + ? true + : undefined, capitalize() { return function (text: string, render: (string) => string): string { return capitalize(render(text)); @@ -57,6 +56,7 @@ async function generateRequestsTests( }, partials ); + await fsp.writeFile( `output/${language}/${sourcePathForLanguage[language]}/${client}.${extensionForLanguage[language]}`, code diff --git a/tests/src/methods/requests/main.ts b/tests/src/methods/requests/main.ts index 8973444b5c..bf3c9c1b20 100644 --- a/tests/src/methods/requests/main.ts +++ b/tests/src/methods/requests/main.ts @@ -1,44 +1,22 @@ -/* eslint-disable no-console */ - -import { packageNames } from '../../utils'; +import { parseCLI } from '../../utils'; import { generateTests } from './generate'; -function printUsage(): void { - console.log(`usage: generateCTS language client`); - // eslint-disable-next-line no-process-exit - process.exit(1); -} - -async function parseCLI(args: string[]): Promise { - if (args.length < 3) { - console.log('not enough arguments'); - printUsage(); - } - - const lang = args[2]; - const client = args[3]; - - if (!(lang in packageNames)) { - console.log('Unknown language', lang); - // eslint-disable-next-line no-process-exit - process.exit(1); - } - if (!(client in packageNames[lang])) { - console.log('Unknown client', client); - // eslint-disable-next-line no-process-exit - process.exit(1); - } - - console.log(`Generating CTS for ${lang}-${client}`); +async function main(): Promise { + const { lang, client } = parseCLI(process.argv, 'generate:methods:requests'); + // eslint-disable-next-line no-console + console.log( + `Generating CTS > generate:methods:requests for ${lang}-${client}` + ); try { - await generateTests(args[2], args[3]); + await generateTests(lang, client); } catch (e) { if (e instanceof Error) { + // eslint-disable-next-line no-console console.error(e); } } } -parseCLI(process.argv); +main(); diff --git a/tests/src/utils.ts b/tests/src/utils.ts index 67688924e9..816eca112c 100644 --- a/tests/src/utils.ts +++ b/tests/src/utils.ts @@ -35,6 +35,15 @@ export async function* walk( } } +export async function exists(filePath: string): Promise { + try { + await fsp.stat(filePath); + return true; + } catch (err) { + return false; + } +} + export function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } @@ -91,3 +100,40 @@ export const sourcePathForLanguage: Record = { javascript: 'tests/methods/requests', java: 'src/test/java/com/algolia', }; + +/* eslint-disable no-console */ +function printUsage(commandName: string): void { + console.log(`usage: ${commandName} language client`); + // eslint-disable-next-line no-process-exit + process.exit(1); +} + +export function parseCLI( + args: string[], + commandName: string +): { lang: string; client: string } { + if (args.length < 3) { + console.log('not enough arguments'); + printUsage(commandName); + } + + const lang = args[2]; + const client = args[3]; + + if (!(lang in packageNames)) { + console.log('Unknown language', lang); + // eslint-disable-next-line no-process-exit + process.exit(1); + } + if (!(client in packageNames[lang])) { + console.log('Unknown client', client); + // eslint-disable-next-line no-process-exit + process.exit(1); + } + + return { + lang, + client, + }; +} +/* eslint-enable no-console */