Skip to content

Commit ada33c5

Browse files
committed
chore: add pkg api shape validation
1 parent 2a1737e commit ada33c5

File tree

5 files changed

+2491
-1
lines changed

5 files changed

+2491
-1
lines changed

clients/client-scheduler/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4",
1212
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
1313
"extract:docs": "api-extractor run --local",
14-
"generate:client": "node ../../scripts/generate-clients/single-service --solo scheduler"
14+
"generate:client": "node ../../scripts/generate-clients/single-service --solo scheduler",
15+
"test:e2e": "yarn g:vitest run -c vitest.config.e2e.mts",
16+
"test:e2e:watch": "yarn g:vitest watch -c vitest.config.e2e.mts"
1517
},
1618
"main": "./dist-cjs/index.js",
1719
"types": "./dist-types/index.d.ts",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { DeleteScheduleInput } from "@aws-sdk/client-scheduler/src/schemas/schemas_0";
2+
import { AwsRestJsonProtocol } from "@aws-sdk/core";
3+
import { ToStringShapeSerializer } from "@smithy/core/protocols";
4+
import { TimestampEpochSecondsSchema } from "@smithy/types";
5+
import { describe, expect, test as it } from "vitest";
6+
7+
describe(
8+
"Scheduler",
9+
{
10+
timeout: 120_000,
11+
retry: 4,
12+
},
13+
() => {
14+
it("should serialize default idempotency token (shape serializer)", async () => {
15+
const restJson = new AwsRestJsonProtocol({ defaultNamespace: "com.amazonaws.scheduler" });
16+
const codec = restJson.getPayloadCodec();
17+
18+
const serializer = codec.createSerializer();
19+
20+
const input = {
21+
Name: "MyScheduleName",
22+
};
23+
24+
serializer.write(DeleteScheduleInput, input);
25+
const serialization = serializer.flush();
26+
27+
expect(serialization).toContain("ClientToken");
28+
});
29+
30+
it("should serializer default idempotency token (query serialization)", async () => {
31+
const serializer = new ToStringShapeSerializer({
32+
httpBindings: true,
33+
timestampFormat: {
34+
default: 7 as const satisfies TimestampEpochSecondsSchema,
35+
useTrait: true,
36+
},
37+
});
38+
39+
const input = {
40+
Name: "MyScheduleName",
41+
};
42+
43+
serializer.write(DeleteScheduleInput, input);
44+
const serialization = serializer.flush();
45+
46+
expect(serialization).toContain("ClientToken");
47+
});
48+
}
49+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
exclude: ["**/*.browser.e2e.spec.ts"],
6+
include: ["**/*.e2e.spec.ts"],
7+
environment: "node",
8+
},
9+
mode: "development",
10+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
This script uses the JSON file api-snapshot/api.json to validate that previously present symbols
5+
are still exported by the packages within the assessed group.
6+
7+
Data may only be deleted from api.json in an intentional backwards-incompatible change.
8+
*/
9+
10+
const fs = require("node:fs");
11+
const path = require("node:path");
12+
13+
const root = path.join(__dirname, "..", "..");
14+
const dataPath = path.join(root, "scripts", "validation", "api.json");
15+
const api = require(dataPath);
16+
17+
api.$schema = "https://json-schema.org/draft/2020-12/schema";
18+
19+
const packageDirs = [
20+
...fs.readdirSync(path.join(root, "packages")).map((f) => path.join(root, "packages", f)),
21+
...fs.readdirSync(path.join(root, "lib")).map((f) => path.join(root, "lib", f)),
22+
path.join(root, "clients", "client-s3"), // rest xml
23+
path.join(root, "clients", "client-s3-control"), // rest xml
24+
path.join(root, "clients", "client-dynamodb"), // json rpc
25+
path.join(root, "clients", "client-cloudwatch"), // query
26+
path.join(root, "clients", "client-sts"), // query
27+
path.join(root, "clients", "client-sagemaker"), // json rpc
28+
path.join(root, "clients", "client-bedrock"), // rest json
29+
];
30+
const errors = [];
31+
32+
for (const packageRoot of packageDirs) {
33+
const pkgJsonPath = path.join(packageRoot, "package.json");
34+
const cjsPath = path.join(packageRoot, "dist-cjs", "index.js");
35+
36+
if (fs.existsSync(pkgJsonPath) && fs.existsSync(cjsPath)) {
37+
const packageJson = require(pkgJsonPath);
38+
const { name, version } = packageJson;
39+
const module = require(cjsPath);
40+
41+
for (const key of Object.keys(module)) {
42+
if (module[key] === undefined) {
43+
console.warn(`symbol ${key} in ${name}@${version} has a value of undefined.`);
44+
}
45+
}
46+
47+
if (!api[name]) {
48+
api[name] = {};
49+
for (const key of Object.keys(module)) {
50+
api[name][key] = [typeof module[key], `<=${version}`].join(", since ");
51+
}
52+
} else {
53+
for (const symbol of [...new Set([...Object.keys(api[name]), ...Object.keys(module)])]) {
54+
if (symbol in module && !(symbol in api[name])) {
55+
errors.push(`You must commit changes in api.json.`);
56+
api[name][symbol] = [typeof module[symbol], version].join(", since ");
57+
}
58+
if (!(symbol in module) && symbol in api[name]) {
59+
errors.push(`Symbol [${symbol}] is missing from ${name}, (${api[name][symbol]}).`);
60+
}
61+
if (symbol in module && symbol in api[name]) {
62+
if (api[name][symbol].split(", ")[0] !== typeof module[symbol]) {
63+
errors.push(
64+
`Symbol [${symbol}] has a different type than expected in ${name}, actual=${typeof module[
65+
symbol
66+
]} expected=${api[name][symbol]}.`
67+
);
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
fs.writeFileSync(dataPath, JSON.stringify(api, null, 2));
76+
77+
if (errors.length) {
78+
throw new Error(errors.join("\n"));
79+
} else {
80+
console.log(`✅ API snapshot test passed.`);
81+
}

0 commit comments

Comments
 (0)