From e13b54ff3afa3b23aafa3d3ac76d5968ddae007a Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:08:09 +0000 Subject: [PATCH 01/15] test(endpoints): remove unused namespace --- tests/endpoints-2.0/endpoints-integration.spec.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 1b5c77411e38..fb879df7ea6a 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -20,7 +20,6 @@ for (const client of clientList) { const serviceName = client.slice(7); let defaultEndpointResolver: any; - let namespace: any; let model: any; // this may also work with dynamic async import() in a beforeAll() block, @@ -28,11 +27,9 @@ for (const client of clientList) { try { defaultEndpointResolver = require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver; - namespace = require(`@aws-sdk/client-${serviceName}`); model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); } catch (e) { defaultEndpointResolver = null; - namespace = null; model = null; if (e.code !== "MODULE_NOT_FOUND") { console.error(e); @@ -40,7 +37,7 @@ for (const client of clientList) { } describe(`client-${serviceName} endpoint test cases`, () => { - if (defaultEndpointResolver && namespace && model) { + if (defaultEndpointResolver && model) { const [, service] = Object.entries(model.shapes).find( ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" ) as any; @@ -51,7 +48,7 @@ for (const client of clientList) { it.skip("has no test cases", () => {}); } } else { - it.skip("unable to load endpoint resolver, namespace, or test cases", () => {}); + it.skip("unable to load endpoint resolver, or test cases", () => {}); } }); } From 45a1129e057c9ebdd82e2bf8fcbb39ab1f100091 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:16:17 +0000 Subject: [PATCH 02/15] test(endpoints): move code inside describe --- .../endpoints-integration.spec.ts | 74 +++++++++---------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index fb879df7ea6a..e22241343f29 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -5,53 +5,51 @@ import * as path from "path"; import { EndpointExpectation, EndpointTestCase, ErrorExpectation, ServiceNamespace } from "./integration-test-types"; -const clientList: string[] = []; -const root = path.join(__dirname, "..", ".."); -const clients = fs.readdirSync(path.join(root, "clients")); -clientList.push(...clients); - describe("client list", () => { + const root = path.join(__dirname, "..", ".."); + const clientList = fs.readdirSync(path.join(root, "clients")); + it("should be at least 300 clients", () => { expect(clientList.length).toBeGreaterThan(300); }); -}); -for (const client of clientList) { - const serviceName = client.slice(7); - - let defaultEndpointResolver: any; - let model: any; - - // this may also work with dynamic async import() in a beforeAll() block, - // but needs more effort than using synchronous require(). - try { - defaultEndpointResolver = - require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver; - model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); - } catch (e) { - defaultEndpointResolver = null; - model = null; - if (e.code !== "MODULE_NOT_FOUND") { - console.error(e); + for (const client of clientList) { + const serviceName = client.slice(7); + + let defaultEndpointResolver: any; + let model: any; + + // this may also work with dynamic async import() in a beforeAll() block, + // but needs more effort than using synchronous require(). + try { + defaultEndpointResolver = + require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver; + model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); + } catch (e) { + defaultEndpointResolver = null; + model = null; + if (e.code !== "MODULE_NOT_FOUND") { + console.error(e); + } } - } - describe(`client-${serviceName} endpoint test cases`, () => { - if (defaultEndpointResolver && model) { - const [, service] = Object.entries(model.shapes).find( - ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" - ) as any; - const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; - if (tests?.testCases) { - runTestCases(tests, service, defaultEndpointResolver, ""); + describe(`client-${serviceName} endpoint test cases`, () => { + if (defaultEndpointResolver && model) { + const [, service] = Object.entries(model.shapes).find( + ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" + ) as any; + const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; + if (tests?.testCases) { + runTestCases(tests, service, defaultEndpointResolver, ""); + } else { + it.skip("has no test cases", () => {}); + } } else { - it.skip("has no test cases", () => {}); + it.skip("unable to load endpoint resolver, or test cases", () => {}); } - } else { - it.skip("unable to load endpoint resolver, or test cases", () => {}); - } - }); -} + }); + } +}); function runTestCases( { testCases }: { testCases: EndpointTestCase[] }, From 728b7b96ba81861e86d4b60e0fb548ec4fb96ab6 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:19:22 +0000 Subject: [PATCH 03/15] test(endpoints): use describe.each --- .../endpoints-integration.spec.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index e22241343f29..71fbc5a414c7 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -13,7 +13,7 @@ describe("client list", () => { expect(clientList.length).toBeGreaterThan(300); }); - for (const client of clientList) { + describe.each(clientList)(`%s endpoint test cases`, (client) => { const serviceName = client.slice(7); let defaultEndpointResolver: any; @@ -33,22 +33,20 @@ describe("client list", () => { } } - describe(`client-${serviceName} endpoint test cases`, () => { - if (defaultEndpointResolver && model) { - const [, service] = Object.entries(model.shapes).find( - ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" - ) as any; - const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; - if (tests?.testCases) { - runTestCases(tests, service, defaultEndpointResolver, ""); - } else { - it.skip("has no test cases", () => {}); - } + if (defaultEndpointResolver && model) { + const [, service] = Object.entries(model.shapes).find( + ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" + ) as any; + const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; + if (tests?.testCases) { + runTestCases(tests, service, defaultEndpointResolver, ""); } else { - it.skip("unable to load endpoint resolver, or test cases", () => {}); + it.skip("has no test cases", () => {}); } - }); - } + } else { + it.skip("unable to load endpoint resolver, or test cases", () => {}); + } + }); }); function runTestCases( From 8d6af172e0c23c0d87187be7c4f6f06ca4446c17 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:21:52 +0000 Subject: [PATCH 04/15] test: remove redundant function is(Endpoint|Error)Expectation --- tests/endpoints-2.0/endpoints-integration.spec.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 71fbc5a414c7..69f8ff6b8d91 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -3,7 +3,7 @@ import { EndpointParameters, EndpointV2 } from "@smithy/types"; import * as fs from "fs"; import * as path from "path"; -import { EndpointExpectation, EndpointTestCase, ErrorExpectation, ServiceNamespace } from "./integration-test-types"; +import { EndpointExpectation, EndpointTestCase, ServiceNamespace } from "./integration-test-types"; describe("client list", () => { const root = path.join(__dirname, "..", ".."); @@ -84,7 +84,7 @@ async function runTestCase( params.serviceId = serviceId; it(documentation || "undocumented testcase", async () => { - if (isEndpointExpectation(expectation)) { + if ("endpoint" in expectation) { const { endpoint } = expectation; if (operationInputs) { for (const operationInput of operationInputs) { @@ -99,7 +99,7 @@ async function runTestCase( assertEndpointResolvedCorrectly(endpoint, observed); } } - if (isErrorExpectation(expectation)) { + if ("error" in expectation) { const { error } = expectation; const pass = (err: any) => err; const normalizeQuotes = (s: string) => s.replace(/`/g, ""); @@ -140,11 +140,3 @@ function assertEndpointResolvedCorrectly(expected: EndpointExpectation["endpoint expect(observed.properties?.authSchemes).toEqual(authSchemes); } } - -function isEndpointExpectation(expectation: object): expectation is EndpointExpectation { - return "endpoint" in expectation; -} - -function isErrorExpectation(expectation: object): expectation is ErrorExpectation { - return "error" in expectation; -} From c366769536f4bf7b6122d75ee1a51fd19231bad4 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:24:48 +0000 Subject: [PATCH 05/15] test(endpoints): read tests from serviceModel --- .../endpoints-2.0/endpoints-integration.spec.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 69f8ff6b8d91..e1bfbd3d1efb 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -37,12 +37,7 @@ describe("client list", () => { const [, service] = Object.entries(model.shapes).find( ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" ) as any; - const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; - if (tests?.testCases) { - runTestCases(tests, service, defaultEndpointResolver, ""); - } else { - it.skip("has no test cases", () => {}); - } + runTestCases(service, defaultEndpointResolver, ""); } else { it.skip("unable to load endpoint resolver, or test cases", () => {}); } @@ -50,13 +45,17 @@ describe("client list", () => { }); function runTestCases( - { testCases }: { testCases: EndpointTestCase[] }, service: ServiceNamespace, defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2, serviceId: string ) { - for (const testCase of testCases) { - runTestCase(testCase, service, defaultEndpointResolver, serviceId); + const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; + if (tests?.testCases) { + for (const testCase of tests.testCases) { + runTestCase(testCase, service, defaultEndpointResolver, serviceId); + } + } else { + it.skip("has no test cases", () => {}); } } From 4767f6c4fa8a4253e310112529c7e4e38f0653cc Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:34:19 +0000 Subject: [PATCH 06/15] test(endpoint): move runTestCase inside runTestCases --- .../endpoints-integration.spec.ts | 122 ++++++++---------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index e1bfbd3d1efb..c711624fafaf 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -51,78 +51,70 @@ function runTestCases( ) { const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; if (tests?.testCases) { - for (const testCase of tests.testCases) { - runTestCase(testCase, service, defaultEndpointResolver, serviceId); - } - } else { - it.skip("has no test cases", () => {}); - } -} - -async function runTestCase( - testCase: EndpointTestCase, - service: ServiceNamespace, - defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2, - serviceId: string -) { - const { documentation, params = {}, expect: expectation, operationInputs } = testCase; + const testCases = tests.testCases as EndpointTestCase[]; + for (const testCase of testCases) { + const { documentation, params = {}, expect: expectation, operationInputs } = testCase; - for (const key of Object.keys(params)) { - // e.g. S3Control::UseArnRegion as a param key indicates - // an error with the test case, it will be ignored. - if (key.includes(":")) { - delete params[key]; - } - } + for (const key of Object.keys(params)) { + // e.g. S3Control::UseArnRegion as a param key indicates + // an error with the test case, it will be ignored. + if (key.includes(":")) { + delete params[key]; + } + } - if (params.UseGlobalEndpoint || params.Region === "aws-global") { - it.skip(documentation || "undocumented testcase", () => {}); - return; - } + if (params.UseGlobalEndpoint || params.Region === "aws-global") { + it.skip(documentation || "undocumented testcase", () => {}); + continue; + } - params.serviceId = serviceId; + params.serviceId = serviceId; - it(documentation || "undocumented testcase", async () => { - if ("endpoint" in expectation) { - const { endpoint } = expectation; - if (operationInputs) { - for (const operationInput of operationInputs) { - const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], params); - const observed = defaultEndpointResolver(endpointParams as any); - assertEndpointResolvedCorrectly(endpoint, observed); + it(documentation || "undocumented testcase", async () => { + if ("endpoint" in expectation) { + const { endpoint } = expectation; + if (operationInputs) { + for (const operationInput of operationInputs) { + const { operationName, operationParams = {} } = operationInput; + const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], params); + const observed = defaultEndpointResolver(endpointParams as any); + assertEndpointResolvedCorrectly(endpoint, observed); + } + } else { + const endpointParams = await resolveParams({}, {}, params); + const observed = defaultEndpointResolver(endpointParams as any); + assertEndpointResolvedCorrectly(endpoint, observed); + } } - } else { - const endpointParams = await resolveParams({}, {}, params); - const observed = defaultEndpointResolver(endpointParams as any); - assertEndpointResolvedCorrectly(endpoint, observed); - } - } - if ("error" in expectation) { - const { error } = expectation; - const pass = (err: any) => err; - const normalizeQuotes = (s: string) => s.replace(/`/g, ""); - if (operationInputs) { - for (const operationInput of operationInputs) { - const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], { - ...params, - endpointProvider: defaultEndpointResolver, - }).catch(pass); - const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); - expect(observedError).not.toBeUndefined(); - expect(observedError?.url).toBeUndefined(); - // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + if ("error" in expectation) { + const { error } = expectation; + const pass = (err: any) => err; + const normalizeQuotes = (s: string) => s.replace(/`/g, ""); + if (operationInputs) { + for (const operationInput of operationInputs) { + const { operationName, operationParams = {} } = operationInput; + const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], { + ...params, + endpointProvider: defaultEndpointResolver, + }).catch(pass); + const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); + expect(observedError).not.toBeUndefined(); + expect(observedError?.url).toBeUndefined(); + // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + } + } else { + const endpointParams = await resolveParams({}, {}, params).catch(pass); + const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); + expect(observedError).not.toBeUndefined(); + expect(observedError?.url).toBeUndefined(); + // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + } } - } else { - const endpointParams = await resolveParams({}, {}, params).catch(pass); - const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); - expect(observedError).not.toBeUndefined(); - expect(observedError?.url).toBeUndefined(); - // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); - } + }); } - }); + } else { + it.skip("has no test cases", () => {}); + } } function assertEndpointResolvedCorrectly(expected: EndpointExpectation["endpoint"], observed: EndpointV2) { From a360f3e15e483afec906da74e89db0a8a24535de Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:46:59 +0000 Subject: [PATCH 07/15] test(endpoints): re-introduce namespace Test Suites: 1 failed, 1 total Tests: 22 failed, 124 skipped, 14526 passed, 14672 total Snapshots: 0 total Time: 61.488 s --- .../endpoints-integration.spec.ts | 27 ++++++++++++------- tests/endpoints-2.0/integration-test-types.ts | 13 +++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index c711624fafaf..86c672e8dfda 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -3,7 +3,7 @@ import { EndpointParameters, EndpointV2 } from "@smithy/types"; import * as fs from "fs"; import * as path from "path"; -import { EndpointExpectation, EndpointTestCase, ServiceNamespace } from "./integration-test-types"; +import { EndpointExpectation, EndpointTestCase, ServiceModel, ServiceNamespace } from "./integration-test-types"; describe("client list", () => { const root = path.join(__dirname, "..", ".."); @@ -17,6 +17,7 @@ describe("client list", () => { const serviceName = client.slice(7); let defaultEndpointResolver: any; + let namespace: any; let model: any; // this may also work with dynamic async import() in a beforeAll() block, @@ -24,28 +25,34 @@ describe("client list", () => { try { defaultEndpointResolver = require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver; + namespace = require(`@aws-sdk/client-${serviceName}`); model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); } catch (e) { defaultEndpointResolver = null; + namespace = null; model = null; if (e.code !== "MODULE_NOT_FOUND") { console.error(e); } } - if (defaultEndpointResolver && model) { - const [, service] = Object.entries(model.shapes).find( - ([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service" - ) as any; - runTestCases(service, defaultEndpointResolver, ""); + if (defaultEndpointResolver && namespace && model) { + for (const value of Object.values(model.shapes)) { + if (typeof value === "object" && value !== null && "type" in value && value.type === "service") { + const service = value as ServiceModel; + runTestCases(service, namespace, defaultEndpointResolver, ""); + break; + } + } } else { - it.skip("unable to load endpoint resolver, or test cases", () => {}); + it.skip("unable to load endpoint resolver, namespace, or test cases", () => {}); } }); }); function runTestCases( - service: ServiceNamespace, + service: ServiceModel, + namespace: ServiceNamespace, defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2, serviceId: string ) { @@ -76,7 +83,7 @@ function runTestCases( if (operationInputs) { for (const operationInput of operationInputs) { const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], params); + const endpointParams = await resolveParams(operationParams, namespace[`${operationName}Command`], params); const observed = defaultEndpointResolver(endpointParams as any); assertEndpointResolvedCorrectly(endpoint, observed); } @@ -93,7 +100,7 @@ function runTestCases( if (operationInputs) { for (const operationInput of operationInputs) { const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], { + const endpointParams = await resolveParams(operationParams, namespace[`${operationName}Command`], { ...params, endpointProvider: defaultEndpointResolver, }).catch(pass); diff --git a/tests/endpoints-2.0/integration-test-types.ts b/tests/endpoints-2.0/integration-test-types.ts index 9c9d33b75e29..de9b29d6b06e 100644 --- a/tests/endpoints-2.0/integration-test-types.ts +++ b/tests/endpoints-2.0/integration-test-types.ts @@ -30,3 +30,16 @@ export type ErrorExpectation = { export interface ServiceNamespace { [Command: string]: EndpointParameterInstructionsSupplier; } + +export interface ServiceModel { + type: "service"; + version: string; + traits: { + "aws.api#service": { + serviceId: string; + }; + "smithy.rules#endpointTests": { + testCases: EndpointTestCase[]; + }; + }; +} From a50628b9f5e74de1aac159240d4ac031ceaef91a Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:50:49 +0000 Subject: [PATCH 08/15] test(endpoints): read serviceId from service model --- tests/endpoints-2.0/endpoints-integration.spec.ts | 13 ++++++------- tests/endpoints-2.0/integration-test-types.ts | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 86c672e8dfda..caac767ec049 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -3,7 +3,7 @@ import { EndpointParameters, EndpointV2 } from "@smithy/types"; import * as fs from "fs"; import * as path from "path"; -import { EndpointExpectation, EndpointTestCase, ServiceModel, ServiceNamespace } from "./integration-test-types"; +import { EndpointExpectation, ServiceModel, ServiceNamespace } from "./integration-test-types"; describe("client list", () => { const root = path.join(__dirname, "..", ".."); @@ -40,7 +40,7 @@ describe("client list", () => { for (const value of Object.values(model.shapes)) { if (typeof value === "object" && value !== null && "type" in value && value.type === "service") { const service = value as ServiceModel; - runTestCases(service, namespace, defaultEndpointResolver, ""); + runTestCases(service, namespace, defaultEndpointResolver); break; } } @@ -53,12 +53,11 @@ describe("client list", () => { function runTestCases( service: ServiceModel, namespace: ServiceNamespace, - defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2, - serviceId: string + defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2 ) { - const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any; - if (tests?.testCases) { - const testCases = tests.testCases as EndpointTestCase[]; + const serviceId = service.traits["aws.api#service"].serviceId; + const testCases = service.traits["smithy.rules#endpointTests"]?.testCases; + if (testCases) { for (const testCase of testCases) { const { documentation, params = {}, expect: expectation, operationInputs } = testCase; diff --git a/tests/endpoints-2.0/integration-test-types.ts b/tests/endpoints-2.0/integration-test-types.ts index de9b29d6b06e..1aad9e7109a1 100644 --- a/tests/endpoints-2.0/integration-test-types.ts +++ b/tests/endpoints-2.0/integration-test-types.ts @@ -38,7 +38,7 @@ export interface ServiceModel { "aws.api#service": { serviceId: string; }; - "smithy.rules#endpointTests": { + "smithy.rules#endpointTests"?: { testCases: EndpointTestCase[]; }; }; From 6e5b22d7ed4c43a7f65d70d87a60042bdebf8cab Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:02:09 +0000 Subject: [PATCH 09/15] test(endpoints): skip reading defaultEndpointResolver --- .../endpoints-integration.spec.ts | 25 ++++++++----------- tests/endpoints-2.0/integration-test-types.ts | 2 ++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index caac767ec049..ad5e259ffdca 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -1,5 +1,6 @@ import { resolveParams } from "@smithy/middleware-endpoint"; -import { EndpointParameters, EndpointV2 } from "@smithy/types"; +import { EndpointV2 } from "@smithy/types"; +import { resolveEndpoint, EndpointParams } from "@smithy/util-endpoints"; import * as fs from "fs"; import * as path from "path"; @@ -16,19 +17,15 @@ describe("client list", () => { describe.each(clientList)(`%s endpoint test cases`, (client) => { const serviceName = client.slice(7); - let defaultEndpointResolver: any; let namespace: any; let model: any; // this may also work with dynamic async import() in a beforeAll() block, // but needs more effort than using synchronous require(). try { - defaultEndpointResolver = - require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver; namespace = require(`@aws-sdk/client-${serviceName}`); model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); } catch (e) { - defaultEndpointResolver = null; namespace = null; model = null; if (e.code !== "MODULE_NOT_FOUND") { @@ -36,11 +33,11 @@ describe("client list", () => { } } - if (defaultEndpointResolver && namespace && model) { + if (namespace && model) { for (const value of Object.values(model.shapes)) { if (typeof value === "object" && value !== null && "type" in value && value.type === "service") { const service = value as ServiceModel; - runTestCases(service, namespace, defaultEndpointResolver); + runTestCases(service, namespace); break; } } @@ -50,13 +47,13 @@ describe("client list", () => { }); }); -function runTestCases( - service: ServiceModel, - namespace: ServiceNamespace, - defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2 -) { +function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { const serviceId = service.traits["aws.api#service"].serviceId; const testCases = service.traits["smithy.rules#endpointTests"]?.testCases; + + const ruleSet = service.traits["smithy.rules#endpointRuleSet"]; + const defaultEndpointResolver = (endpointParams: EndpointParams) => resolveEndpoint(ruleSet, { endpointParams }); + if (testCases) { for (const testCase of testCases) { const { documentation, params = {}, expect: expectation, operationInputs } = testCase; @@ -83,12 +80,12 @@ function runTestCases( for (const operationInput of operationInputs) { const { operationName, operationParams = {} } = operationInput; const endpointParams = await resolveParams(operationParams, namespace[`${operationName}Command`], params); - const observed = defaultEndpointResolver(endpointParams as any); + const observed = defaultEndpointResolver(endpointParams as EndpointParams); assertEndpointResolvedCorrectly(endpoint, observed); } } else { const endpointParams = await resolveParams({}, {}, params); - const observed = defaultEndpointResolver(endpointParams as any); + const observed = defaultEndpointResolver(endpointParams as EndpointParams); assertEndpointResolvedCorrectly(endpoint, observed); } } diff --git a/tests/endpoints-2.0/integration-test-types.ts b/tests/endpoints-2.0/integration-test-types.ts index 1aad9e7109a1..1e5949961dd8 100644 --- a/tests/endpoints-2.0/integration-test-types.ts +++ b/tests/endpoints-2.0/integration-test-types.ts @@ -1,4 +1,5 @@ import { EndpointParameterInstructionsSupplier } from "@smithy/middleware-endpoint"; +import { RuleSetObject } from "@smithy/types"; export interface EndpointTestCase { documentation?: string; @@ -38,6 +39,7 @@ export interface ServiceModel { "aws.api#service": { serviceId: string; }; + "smithy.rules#endpointRuleSet": RuleSetObject; "smithy.rules#endpointTests"?: { testCases: EndpointTestCase[]; }; From 5d16bbe497f3d015797fbad6c4f3d47fbe75ecb6 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:31:17 +0000 Subject: [PATCH 10/15] test(endpoints): use names imports from Node.js core --- tests/endpoints-2.0/endpoints-integration.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index ad5e259ffdca..6c90b296fb64 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -1,14 +1,14 @@ import { resolveParams } from "@smithy/middleware-endpoint"; import { EndpointV2 } from "@smithy/types"; import { resolveEndpoint, EndpointParams } from "@smithy/util-endpoints"; -import * as fs from "fs"; -import * as path from "path"; +import { readdirSync } from "fs"; +import { join } from "path"; import { EndpointExpectation, ServiceModel, ServiceNamespace } from "./integration-test-types"; describe("client list", () => { - const root = path.join(__dirname, "..", ".."); - const clientList = fs.readdirSync(path.join(root, "clients")); + const root = join(__dirname, "..", ".."); + const clientList = readdirSync(join(root, "clients")); it("should be at least 300 clients", () => { expect(clientList.length).toBeGreaterThan(300); @@ -24,7 +24,7 @@ describe("client list", () => { // but needs more effort than using synchronous require(). try { namespace = require(`@aws-sdk/client-${serviceName}`); - model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); + model = require(join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); } catch (e) { namespace = null; model = null; From d24bff5125dd3d30b73a71fcb2da547ead0e8fc4 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:42:34 +0000 Subject: [PATCH 11/15] test(endpoints): make namespace and model variables const --- .../endpoints-integration.spec.ts | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 6c90b296fb64..5efa36dafbe6 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -1,39 +1,28 @@ import { resolveParams } from "@smithy/middleware-endpoint"; import { EndpointV2 } from "@smithy/types"; import { resolveEndpoint, EndpointParams } from "@smithy/util-endpoints"; -import { readdirSync } from "fs"; +import { existsSync, readdirSync } from "fs"; import { join } from "path"; import { EndpointExpectation, ServiceModel, ServiceNamespace } from "./integration-test-types"; describe("client list", () => { const root = join(__dirname, "..", ".."); - const clientList = readdirSync(join(root, "clients")); + const clientPackageNameList = readdirSync(join(root, "clients")); it("should be at least 300 clients", () => { - expect(clientList.length).toBeGreaterThan(300); + expect(clientPackageNameList.length).toBeGreaterThan(300); }); - describe.each(clientList)(`%s endpoint test cases`, (client) => { - const serviceName = client.slice(7); + describe.each(clientPackageNameList)(`%s endpoint test cases`, (clientPackageName) => { + const serviceName = clientPackageName.slice(7); - let namespace: any; - let model: any; + // since client package name list is populated from clients folder, we know it exists. + const namespace = require(`@aws-sdk/${clientPackageName}`); + const modelPath = join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json"); - // this may also work with dynamic async import() in a beforeAll() block, - // but needs more effort than using synchronous require(). - try { - namespace = require(`@aws-sdk/client-${serviceName}`); - model = require(join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json")); - } catch (e) { - namespace = null; - model = null; - if (e.code !== "MODULE_NOT_FOUND") { - console.error(e); - } - } - - if (namespace && model) { + if (existsSync(modelPath)) { + const model = require(modelPath); for (const value of Object.values(model.shapes)) { if (typeof value === "object" && value !== null && "type" in value && value.type === "service") { const service = value as ServiceModel; @@ -41,8 +30,6 @@ describe("client list", () => { break; } } - } else { - it.skip("unable to load endpoint resolver, namespace, or test cases", () => {}); } }); }); From caa0e01d34d0f2eede3a769822afbfab6d87ad13 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:45:11 +0000 Subject: [PATCH 12/15] test(endpoints): remove redudnant check for skipping S3Control::UseArnRegion --- tests/endpoints-2.0/endpoints-integration.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 5efa36dafbe6..eae9a9a1ba4a 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -45,14 +45,6 @@ function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { for (const testCase of testCases) { const { documentation, params = {}, expect: expectation, operationInputs } = testCase; - for (const key of Object.keys(params)) { - // e.g. S3Control::UseArnRegion as a param key indicates - // an error with the test case, it will be ignored. - if (key.includes(":")) { - delete params[key]; - } - } - if (params.UseGlobalEndpoint || params.Region === "aws-global") { it.skip(documentation || "undocumented testcase", () => {}); continue; From 9632b56e1f33d6f7b2cf6620d7b532a77b0f3662 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:48:20 +0000 Subject: [PATCH 13/15] test(endpoints): run params.UseGlobalEndpoint and params.Region 'aws-global' tests --- tests/endpoints-2.0/endpoints-integration.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index eae9a9a1ba4a..299e2a749290 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -44,12 +44,6 @@ function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { if (testCases) { for (const testCase of testCases) { const { documentation, params = {}, expect: expectation, operationInputs } = testCase; - - if (params.UseGlobalEndpoint || params.Region === "aws-global") { - it.skip(documentation || "undocumented testcase", () => {}); - continue; - } - params.serviceId = serviceId; it(documentation || "undocumented testcase", async () => { From bb7b535cd17c50b0507ad928cc4870e1a7391aec Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:51:52 +0000 Subject: [PATCH 14/15] test(endpoints): use variable command for storing Command import --- tests/endpoints-2.0/endpoints-integration.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 299e2a749290..29d64d980f58 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -52,7 +52,8 @@ function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { if (operationInputs) { for (const operationInput of operationInputs) { const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, namespace[`${operationName}Command`], params); + const command = namespace[`${operationName}Command`]; + const endpointParams = await resolveParams(operationParams, command, params); const observed = defaultEndpointResolver(endpointParams as EndpointParams); assertEndpointResolvedCorrectly(endpoint, observed); } @@ -69,10 +70,8 @@ function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { if (operationInputs) { for (const operationInput of operationInputs) { const { operationName, operationParams = {} } = operationInput; - const endpointParams = await resolveParams(operationParams, namespace[`${operationName}Command`], { - ...params, - endpointProvider: defaultEndpointResolver, - }).catch(pass); + const command = namespace[`${operationName}Command`]; + const endpointParams = await resolveParams(operationParams, command, params); const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); expect(observedError).not.toBeUndefined(); expect(observedError?.url).toBeUndefined(); From f456c0ee5804fa263710505e88820a55a2cd505c Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:56:39 +0000 Subject: [PATCH 15/15] test(endpoints): enable error validation except s3 empty arn type --- tests/endpoints-2.0/endpoints-integration.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/endpoints-2.0/endpoints-integration.spec.ts b/tests/endpoints-2.0/endpoints-integration.spec.ts index 29d64d980f58..2b6376043245 100644 --- a/tests/endpoints-2.0/endpoints-integration.spec.ts +++ b/tests/endpoints-2.0/endpoints-integration.spec.ts @@ -75,14 +75,17 @@ function runTestCases(service: ServiceModel, namespace: ServiceNamespace) { const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); expect(observedError).not.toBeUndefined(); expect(observedError?.url).toBeUndefined(); - // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); } } else { const endpointParams = await resolveParams({}, {}, params).catch(pass); const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass); expect(observedError).not.toBeUndefined(); expect(observedError?.url).toBeUndefined(); - // expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + // ToDo: debug why 'client-s3 > empty arn type' test case is failing + if (serviceId !== "s3" && documentation !== "empty arn type") { + expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error)); + } } } });