Skip to content

Commit 88d384c

Browse files
authored
test: simplify endpoint integration tests (#6373)
1 parent 7867ee1 commit 88d384c

File tree

2 files changed

+94
-125
lines changed

2 files changed

+94
-125
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,98 @@
11
import { resolveParams } from "@smithy/middleware-endpoint";
2-
import { EndpointParameters, EndpointV2 } from "@smithy/types";
3-
import * as fs from "fs";
4-
import * as path from "path";
2+
import { EndpointV2 } from "@smithy/types";
3+
import { resolveEndpoint, EndpointParams } from "@smithy/util-endpoints";
4+
import { existsSync, readdirSync } from "fs";
5+
import { join } from "path";
56

6-
import { EndpointExpectation, EndpointTestCase, ErrorExpectation, ServiceNamespace } from "./integration-test-types";
7-
8-
const clientList: string[] = [];
9-
const root = path.join(__dirname, "..", "..");
10-
const clients = fs.readdirSync(path.join(root, "clients"));
11-
clientList.push(...clients);
7+
import { EndpointExpectation, ServiceModel, ServiceNamespace } from "./integration-test-types";
128

139
describe("client list", () => {
10+
const root = join(__dirname, "..", "..");
11+
const clientPackageNameList = readdirSync(join(root, "clients"));
12+
1413
it("should be at least 300 clients", () => {
15-
expect(clientList.length).toBeGreaterThan(300);
14+
expect(clientPackageNameList.length).toBeGreaterThan(300);
1615
});
17-
});
1816

19-
for (const client of clientList) {
20-
const serviceName = client.slice(7);
17+
describe.each(clientPackageNameList)(`%s endpoint test cases`, (clientPackageName) => {
18+
const serviceName = clientPackageName.slice(7);
2119

22-
let defaultEndpointResolver: any;
23-
let namespace: any;
24-
let model: any;
20+
// since client package name list is populated from clients folder, we know it exists.
21+
const namespace = require(`@aws-sdk/${clientPackageName}`);
22+
const modelPath = join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json");
2523

26-
// this may also work with dynamic async import() in a beforeAll() block,
27-
// but needs more effort than using synchronous require().
28-
try {
29-
defaultEndpointResolver =
30-
require(`@aws-sdk/client-${serviceName}/src/endpoint/endpointResolver`).defaultEndpointResolver;
31-
namespace = require(`@aws-sdk/client-${serviceName}`);
32-
model = require(path.join(root, "codegen", "sdk-codegen", "aws-models", serviceName + ".json"));
33-
} catch (e) {
34-
defaultEndpointResolver = null;
35-
namespace = null;
36-
model = null;
37-
if (e.code !== "MODULE_NOT_FOUND") {
38-
console.error(e);
39-
}
40-
}
41-
42-
describe(`client-${serviceName} endpoint test cases`, () => {
43-
if (defaultEndpointResolver && namespace && model) {
44-
const [, service] = Object.entries(model.shapes).find(
45-
([k, v]) => typeof v === "object" && v !== null && "type" in v && v.type === "service"
46-
) as any;
47-
const [, tests] = Object.entries(service.traits).find(([k, v]) => k === "smithy.rules#endpointTests") as any;
48-
if (tests?.testCases) {
49-
runTestCases(tests, service, defaultEndpointResolver, "");
50-
} else {
51-
it.skip("has no test cases", () => {});
24+
if (existsSync(modelPath)) {
25+
const model = require(modelPath);
26+
for (const value of Object.values(model.shapes)) {
27+
if (typeof value === "object" && value !== null && "type" in value && value.type === "service") {
28+
const service = value as ServiceModel;
29+
runTestCases(service, namespace);
30+
break;
31+
}
5232
}
53-
} else {
54-
it.skip("unable to load endpoint resolver, namespace, or test cases", () => {});
5533
}
5634
});
57-
}
58-
59-
function runTestCases(
60-
{ testCases }: { testCases: EndpointTestCase[] },
61-
service: ServiceNamespace,
62-
defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2,
63-
serviceId: string
64-
) {
65-
for (const testCase of testCases) {
66-
runTestCase(testCase, service, defaultEndpointResolver, serviceId);
67-
}
68-
}
69-
70-
async function runTestCase(
71-
testCase: EndpointTestCase,
72-
service: ServiceNamespace,
73-
defaultEndpointResolver: (endpointParams: EndpointParameters) => EndpointV2,
74-
serviceId: string
75-
) {
76-
const { documentation, params = {}, expect: expectation, operationInputs } = testCase;
77-
78-
for (const key of Object.keys(params)) {
79-
// e.g. S3Control::UseArnRegion as a param key indicates
80-
// an error with the test case, it will be ignored.
81-
if (key.includes(":")) {
82-
delete params[key];
83-
}
84-
}
85-
86-
if (params.UseGlobalEndpoint || params.Region === "aws-global") {
87-
it.skip(documentation || "undocumented testcase", () => {});
88-
return;
89-
}
90-
91-
params.serviceId = serviceId;
35+
});
9236

93-
it(documentation || "undocumented testcase", async () => {
94-
if (isEndpointExpectation(expectation)) {
95-
const { endpoint } = expectation;
96-
if (operationInputs) {
97-
for (const operationInput of operationInputs) {
98-
const { operationName, operationParams = {} } = operationInput;
99-
const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], params);
100-
const observed = defaultEndpointResolver(endpointParams as any);
101-
assertEndpointResolvedCorrectly(endpoint, observed);
37+
function runTestCases(service: ServiceModel, namespace: ServiceNamespace) {
38+
const serviceId = service.traits["aws.api#service"].serviceId;
39+
const testCases = service.traits["smithy.rules#endpointTests"]?.testCases;
40+
41+
const ruleSet = service.traits["smithy.rules#endpointRuleSet"];
42+
const defaultEndpointResolver = (endpointParams: EndpointParams) => resolveEndpoint(ruleSet, { endpointParams });
43+
44+
if (testCases) {
45+
for (const testCase of testCases) {
46+
const { documentation, params = {}, expect: expectation, operationInputs } = testCase;
47+
params.serviceId = serviceId;
48+
49+
it(documentation || "undocumented testcase", async () => {
50+
if ("endpoint" in expectation) {
51+
const { endpoint } = expectation;
52+
if (operationInputs) {
53+
for (const operationInput of operationInputs) {
54+
const { operationName, operationParams = {} } = operationInput;
55+
const command = namespace[`${operationName}Command`];
56+
const endpointParams = await resolveParams(operationParams, command, params);
57+
const observed = defaultEndpointResolver(endpointParams as EndpointParams);
58+
assertEndpointResolvedCorrectly(endpoint, observed);
59+
}
60+
} else {
61+
const endpointParams = await resolveParams({}, {}, params);
62+
const observed = defaultEndpointResolver(endpointParams as EndpointParams);
63+
assertEndpointResolvedCorrectly(endpoint, observed);
64+
}
10265
}
103-
} else {
104-
const endpointParams = await resolveParams({}, {}, params);
105-
const observed = defaultEndpointResolver(endpointParams as any);
106-
assertEndpointResolvedCorrectly(endpoint, observed);
107-
}
108-
}
109-
if (isErrorExpectation(expectation)) {
110-
const { error } = expectation;
111-
const pass = (err: any) => err;
112-
const normalizeQuotes = (s: string) => s.replace(/`/g, "");
113-
if (operationInputs) {
114-
for (const operationInput of operationInputs) {
115-
const { operationName, operationParams = {} } = operationInput;
116-
const endpointParams = await resolveParams(operationParams, service[`${operationName}Command`], {
117-
...params,
118-
endpointProvider: defaultEndpointResolver,
119-
}).catch(pass);
120-
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
121-
expect(observedError).not.toBeUndefined();
122-
expect(observedError?.url).toBeUndefined();
123-
// expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
66+
if ("error" in expectation) {
67+
const { error } = expectation;
68+
const pass = (err: any) => err;
69+
const normalizeQuotes = (s: string) => s.replace(/`/g, "");
70+
if (operationInputs) {
71+
for (const operationInput of operationInputs) {
72+
const { operationName, operationParams = {} } = operationInput;
73+
const command = namespace[`${operationName}Command`];
74+
const endpointParams = await resolveParams(operationParams, command, params);
75+
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
76+
expect(observedError).not.toBeUndefined();
77+
expect(observedError?.url).toBeUndefined();
78+
expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
79+
}
80+
} else {
81+
const endpointParams = await resolveParams({}, {}, params).catch(pass);
82+
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
83+
expect(observedError).not.toBeUndefined();
84+
expect(observedError?.url).toBeUndefined();
85+
// ToDo: debug why 'client-s3 > empty arn type' test case is failing
86+
if (serviceId !== "s3" && documentation !== "empty arn type") {
87+
expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
88+
}
89+
}
12490
}
125-
} else {
126-
const endpointParams = await resolveParams({}, {}, params).catch(pass);
127-
const observedError = await (async () => defaultEndpointResolver(endpointParams as any))().catch(pass);
128-
expect(observedError).not.toBeUndefined();
129-
expect(observedError?.url).toBeUndefined();
130-
// expect(normalizeQuotes(String(observedError))).toContain(normalizeQuotes(error));
131-
}
91+
});
13292
}
133-
});
93+
} else {
94+
it.skip("has no test cases", () => {});
95+
}
13496
}
13597

13698
function assertEndpointResolvedCorrectly(expected: EndpointExpectation["endpoint"], observed: EndpointV2) {
@@ -147,11 +109,3 @@ function assertEndpointResolvedCorrectly(expected: EndpointExpectation["endpoint
147109
expect(observed.properties?.authSchemes).toEqual(authSchemes);
148110
}
149111
}
150-
151-
function isEndpointExpectation(expectation: object): expectation is EndpointExpectation {
152-
return "endpoint" in expectation;
153-
}
154-
155-
function isErrorExpectation(expectation: object): expectation is ErrorExpectation {
156-
return "error" in expectation;
157-
}

tests/endpoints-2.0/integration-test-types.ts

+15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EndpointParameterInstructionsSupplier } from "@smithy/middleware-endpoint";
2+
import { RuleSetObject } from "@smithy/types";
23

34
export interface EndpointTestCase {
45
documentation?: string;
@@ -30,3 +31,17 @@ export type ErrorExpectation = {
3031
export interface ServiceNamespace {
3132
[Command: string]: EndpointParameterInstructionsSupplier;
3233
}
34+
35+
export interface ServiceModel {
36+
type: "service";
37+
version: string;
38+
traits: {
39+
"aws.api#service": {
40+
serviceId: string;
41+
};
42+
"smithy.rules#endpointRuleSet": RuleSetObject;
43+
"smithy.rules#endpointTests"?: {
44+
testCases: EndpointTestCase[];
45+
};
46+
};
47+
}

0 commit comments

Comments
 (0)