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"), ); }); });