shoc-frontend-new/infra/cdk/test/frontend-stack.test.mjs

204 lines
8.4 KiB
JavaScript

import assert from "node:assert/strict";
import { createRequire } from "node:module";
import test from "node:test";
const require = createRequire(import.meta.url);
const { App } = require("aws-cdk-lib");
const { Template } = require("aws-cdk-lib/assertions");
const { FrontendStack } = require("../lib/frontend-stack.js");
const { TfPocCertificateStack, TfPocZoneStack } = require("../lib/tf-poc-shared-stack.js");
const account = "396287094661";
const region = "us-east-1";
function frontendTemplate(retainForTerraformAdoption) {
const app = new App();
const stack = new FrontendStack(app, "shoc-frontend-tf-poc", {
envName: "tf-poc",
githubRepo: "Sea-Haven-Industries/shoc-frontend-new",
deployBranch: "tf-poc",
githubEnvironment: "tf-poc",
domainNames: ["frontend-tf-poc.seahaven.com"],
certificateArn: `arn:aws:acm:${region}:${account}:certificate/test`,
hostedZoneId: "ZTESTPOC",
hostedZoneName: "frontend-tf-poc.seahaven.com",
retainForTerraformAdoption,
env: { account, region },
});
return Template.fromStack(stack).toJSON();
}
function entriesByType(template, type) {
return Object.entries(template.Resources).filter(([, resource]) => resource.Type === type);
}
test("tf-poc has fixed production-shaped resources and adoption metadata", () => {
const template = frontendTemplate(true);
const bucket = entriesByType(template, "AWS::S3::Bucket")[0][1];
assert.equal(bucket.Properties.BucketName, "seahaven-shoc-frontend-tf-poc");
assert.equal(bucket.Properties.VersioningConfiguration.Status, "Enabled");
assert.ok(
bucket.Properties.Tags.some(
(tag) => tag.Key === "aws-cdk:auto-delete-objects" && tag.Value === "true",
),
);
const [deployRole] = entriesByType(template, "AWS::IAM::Role").filter(
([, resource]) => resource.Properties.RoleName === "githubdeploy-shoc-frontend-new-tf-poc",
);
assert.ok(deployRole);
assert.equal(
deployRole[1].Properties.PermissionsBoundary,
`arn:aws:iam::${account}:policy/shoc-frontend-new-tf-poc-deploy-boundary`,
);
assert.ok(
deployRole[1].Properties.Tags.some(
(tag) => tag.Key === "HcpTerraformWorkspace" && tag.Value === "shoc-frontend-new-tf-poc",
),
);
assert.equal(entriesByType(template, "AWS::CloudFront::Distribution").length, 1);
assert.equal(
entriesByType(template, "AWS::CloudFront::Distribution")[0][1].Properties.DistributionConfig
.Origins[0].Id,
"shoc-frontend-tf-poc-origin",
);
assert.equal(entriesByType(template, "AWS::CloudFront::OriginAccessControl").length, 1);
assert.equal(entriesByType(template, "AWS::CloudFront::Function").length, 1);
assert.equal(entriesByType(template, "AWS::Route53::RecordSet").length, 2);
assert.equal(entriesByType(template, "Custom::S3AutoDeleteObjects").length, 1);
for (const output of [
"TerraformWorkspaceTag",
"TerraformDeployBoundaryArn",
"TerraformImportBucket",
"TerraformImportBucketPolicy",
"TerraformImportDistribution",
"TerraformImportOriginAccessControl",
"TerraformOriginAccessControlName",
"TerraformOriginAccessControlDescription",
"TerraformDistributionOriginId",
"TerraformImportSpaRewriteFunction",
"TerraformImportAliasA",
"TerraformImportAliasAAAA",
"TerraformImportDeployRole",
"TerraformImportDeployRolePolicy",
"TerraformDeployInlinePolicyName",
"TerraformBucketAutoDeleteHelperRoleArn",
"TerraformRetainedAutoDeleteCustomResource",
]) {
assert.ok(template.Outputs[output], `missing output ${output}`);
}
});
test("adoption mode retains exactly the transferred resources", () => {
const template = frontendTemplate(true);
const retainedTypes = new Set([
"AWS::S3::Bucket",
"AWS::S3::BucketPolicy",
"AWS::CloudFront::Distribution",
"AWS::CloudFront::Function",
"AWS::CloudFront::OriginAccessControl",
"AWS::Route53::RecordSet",
"Custom::S3AutoDeleteObjects",
]);
for (const [logicalId, resource] of Object.entries(template.Resources)) {
const isDeployRoleResource =
(resource.Type === "AWS::IAM::Role" &&
resource.Properties.RoleName === "githubdeploy-shoc-frontend-new-tf-poc") ||
(resource.Type === "AWS::IAM::Policy" && logicalId.startsWith("GithubDeployRole"));
const shouldRetain = retainedTypes.has(resource.Type) || isDeployRoleResource;
if (shouldRetain) {
assert.equal(resource.DeletionPolicy, "Retain", logicalId);
assert.equal(resource.UpdateReplacePolicy, "Retain", logicalId);
} else {
assert.notEqual(resource.DeletionPolicy, "Retain", logicalId);
assert.notEqual(resource.UpdateReplacePolicy, "Retain", logicalId);
}
}
const customResource = entriesByType(template, "Custom::S3AutoDeleteObjects")[0][1];
assert.equal(customResource.DeletionPolicy, "Retain");
for (const type of ["AWS::Lambda::Function", "AWS::Logs::LogGroup"]) {
for (const [, resource] of entriesByType(template, type)) {
assert.notEqual(resource.DeletionPolicy, "Retain");
}
}
});
test("normal mode preserves destructive cleanup and has no adoption boundary or tag", () => {
const template = frontendTemplate(false);
const bucket = entriesByType(template, "AWS::S3::Bucket")[0][1];
assert.equal(bucket.DeletionPolicy, "Delete");
assert.equal(bucket.UpdateReplacePolicy, "Delete");
const customResource = entriesByType(template, "Custom::S3AutoDeleteObjects")[0][1];
assert.notEqual(customResource.DeletionPolicy, "Retain");
const deployRole = entriesByType(template, "AWS::IAM::Role").find(
([, resource]) => resource.Properties.RoleName === "githubdeploy-shoc-frontend-new-tf-poc",
)[1];
assert.equal(deployRole.Properties.PermissionsBoundary, undefined);
assert.ok(!deployRole.Properties.Tags?.some((tag) => tag.Key === "HcpTerraformWorkspace"));
assert.equal(template.Outputs.TerraformWorkspaceTag, undefined);
});
test("dev adoption prerequisite preserves origin ID and narrows exact trust", () => {
const app = new App();
const stack = new FrontendStack(app, "shoc-frontend-dev", {
envName: "dev",
githubRepo: "Sea-Haven-Industries/shoc-frontend-new",
deployBranch: "dev",
domainNames: ["dev.seahaven.com"],
certificateArn: `arn:aws:acm:${region}:${account}:certificate/test`,
hostedZoneId: "Z07671212N75U4YLPWZR8",
hostedZoneName: "dev.seahaven.com",
retainForTerraformAdoption: true,
env: { account, region },
});
const template = Template.fromStack(stack).toJSON();
const distribution = entriesByType(template, "AWS::CloudFront::Distribution")[0][1];
assert.equal(
distribution.Properties.DistributionConfig.Origins[0].Id,
"shocfrontenddevDistributionOrigin10CCD0EE1",
);
const deployRole = entriesByType(template, "AWS::IAM::Role").find(
([, resource]) => resource.Properties.RoleName === "githubdeploy-shoc-frontend-new-dev",
)[1];
const condition = deployRole.Properties.AssumeRolePolicyDocument.Statement[0].Condition;
assert.equal(
condition.StringEquals["token.actions.githubusercontent.com:sub"],
"repo:Sea-Haven-Industries/shoc-frontend-new:ref:refs/heads/dev",
);
assert.equal(condition.StringLike, undefined);
});
test("zone stack never owns a certificate or parent delegation", () => {
const app = new App();
const stack = new TfPocZoneStack(app, "shoc-frontend-tf-poc-shared", {
env: { account, region },
});
const template = Template.fromStack(stack).toJSON();
assert.equal(entriesByType(template, "AWS::Route53::HostedZone").length, 1);
assert.equal(entriesByType(template, "AWS::CertificateManager::Certificate").length, 0);
assert.equal(entriesByType(template, "AWS::Route53::RecordSet").length, 0);
assert.ok(template.Outputs.DelegationNameServers);
assert.equal(template.Outputs.CertificateArn, undefined);
assert.match(template.Outputs.DelegationRequiredAction.Value, /SEPARATE APPROVAL REQUIRED/);
});
test("environment phase creates its certificate in a separate stack", () => {
const app = new App();
const zoneStack = new TfPocZoneStack(app, "shoc-frontend-tf-poc-shared", {
env: { account, region },
});
const stack = new TfPocCertificateStack(app, "shoc-frontend-tf-poc-certificate", {
env: { account, region },
hostedZone: zoneStack.hostedZone,
});
const template = Template.fromStack(stack).toJSON();
assert.equal(entriesByType(template, "AWS::Route53::HostedZone").length, 0);
assert.equal(entriesByType(template, "AWS::CertificateManager::Certificate").length, 1);
assert.ok(template.Outputs.CertificateArn);
});