open-swe/infra/test/kebab-naming-aspect.test.ts

110 lines
3.7 KiB
TypeScript
Raw Normal View History

import * as cdk from "aws-cdk-lib";
import { Annotations, Match, Template } from "aws-cdk-lib/assertions";
import * as iam from "aws-cdk-lib/aws-iam";
import { KebabNamingAspect, isKebabCase } from "../lib/aspects/kebab-naming-aspect";
import { OpenSweIamStack } from "../lib/open-swe-iam-stack";
import { OpenSweStack } from "../lib/open-swe-stack";
const ENV = { account: "328440206208", region: "us-east-1" };
describe("isKebabCase", () => {
it.each([
"open-swe-dev",
"open-swe-prod-instance-role",
"githubdeploy-open-swe-infra",
"open-swe-dev/slack-signing", // Secrets Manager path
"/open-swe-dev/feature-flag", // SSM param path
"/open-swe/dev/agent", // log group path
"abc123",
])("accepts conforming name %s", (name) => {
expect(isKebabCase(name)).toBe(true);
});
it.each([
"OpenSweDev",
"open_swe_dev",
"openSweDev",
"Open-Swe-Dev",
"open-swe-dev/SlackSigning",
])("rejects non-conforming name %s", (name) => {
expect(isKebabCase(name)).toBe(false);
});
});
describe("KebabNamingAspect", () => {
it("passes the real app stacks (no errors)", () => {
const app = new cdk.App();
cdk.Aspects.of(app).add(new KebabNamingAspect());
new OpenSweIamStack(app, "OpenSweIamStack", { stackName: "open-swe-iam", env: ENV });
const dev = new OpenSweStack(app, "OpenSweDevStack", {
stackName: "open-swe-dev",
env: ENV,
envName: "dev",
});
const prod = new OpenSweStack(app, "OpenSweProdStack", {
stackName: "open-swe-prod",
env: ENV,
envName: "prod",
});
for (const s of [dev, prod]) {
Annotations.fromStack(s).hasNoError("*", Match.anyValue());
}
});
it("exempts Secrets Manager + SSM names that carry the literal env-var segment", () => {
// The config store names a secret open-swe-dev/ANTHROPIC_API_KEY and a param
// /open-swe-dev/SANDBOX_TYPE — the UPPER_SNAKE last segment is a REQUIRED
// deviation from kebab (fetch-config naming contract). Must NOT be flagged.
const app = new cdk.App();
cdk.Aspects.of(app).add(new KebabNamingAspect());
const dev = new OpenSweStack(app, "OpenSweDevStack", {
stackName: "open-swe-dev",
env: ENV,
envName: "dev",
});
const tpl = Template.fromStack(dev);
// Sanity: the shells actually render with the literal env-var names.
tpl.hasResourceProperties("AWS::SecretsManager::Secret", {
Name: "open-swe-dev/ANTHROPIC_API_KEY",
});
tpl.hasResourceProperties("AWS::SSM::Parameter", {
Name: "/open-swe-dev/SANDBOX_TYPE",
Value: "langsmith",
});
Annotations.fromStack(dev).hasNoError("*", Match.anyValue());
});
it("flags a deliberately non-kebab-case resource name", () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, "ConformingStackId", { stackName: "open-swe-test", env: ENV });
cdk.Aspects.of(stack).add(new KebabNamingAspect());
// Deliberately bad physical name — must be flagged.
new iam.Role(stack, "BadlyNamedRole", {
roleName: "OpenSweBadRole",
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
});
Annotations.fromStack(stack).hasError(
"*",
Match.stringLikeRegexp("not kebab-case"),
);
});
it("flags a deliberately non-kebab-case stack name", () => {
const app = new cdk.App();
// PascalCase stackName — the convention CDK defaults to and that we forbid.
const stack = new cdk.Stack(app, "BadStack", { stackName: "OpenSweBadStack", env: ENV });
cdk.Aspects.of(stack).add(new KebabNamingAspect());
Annotations.fromStack(stack).hasError(
"*",
Match.stringLikeRegexp("Stack name .* is not kebab-case"),
);
});
});