From 473fe612faf08c6e88eec296ab79b4d7a9e3fcbf Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P Date: Sun, 19 Jul 2026 21:48:56 +0530 Subject: [PATCH 1/4] fix: remove undeclared source-map-support import from examples (fixes #28) --- examples/basic-usage.ts | 1 - examples/blueprints-usage.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/examples/basic-usage.ts b/examples/basic-usage.ts index 3448cea..79bc3c3 100644 --- a/examples/basic-usage.ts +++ b/examples/basic-usage.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -import 'source-map-support/register'; import { App, Stack } from 'aws-cdk-lib'; import { SecureBucket, SecurityLevel } from '../src'; diff --git a/examples/blueprints-usage.ts b/examples/blueprints-usage.ts index f756a0a..885a16e 100644 --- a/examples/blueprints-usage.ts +++ b/examples/blueprints-usage.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -import 'source-map-support/register'; import { App, Stack, PropertyInjectors } from 'aws-cdk-lib'; import { Bucket } from 'aws-cdk-lib/aws-s3'; From 2bf8d20cae3f6bba8a5b8cfd8c03b43569558c8d Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P Date: Sun, 19 Jul 2026 21:56:05 +0530 Subject: [PATCH 2/4] feat: expose bucketArn/bucketName and domain name getters on SecureBucket (fixes #41) --- src/resources/s3-bucket/secure-bucket.ts | 20 ++++++++++++++++ .../resources/s3-bucket/secure-bucket.test.ts | 23 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/resources/s3-bucket/secure-bucket.ts b/src/resources/s3-bucket/secure-bucket.ts index a024314..7449e12 100644 --- a/src/resources/s3-bucket/secure-bucket.ts +++ b/src/resources/s3-bucket/secure-bucket.ts @@ -70,6 +70,26 @@ export class SecureBucket extends Construct { /** The underlying hardened bucket. */ public readonly bucket: Bucket; + /** The bucket's ARN. Convenience pass-through for `this.bucket.bucketArn`. */ + public get bucketArn(): string { + return this.bucket.bucketArn; + } + + /** The bucket's name. Convenience pass-through for `this.bucket.bucketName`. */ + public get bucketName(): string { + return this.bucket.bucketName; + } + + /** The bucket's domain name. Convenience pass-through for `this.bucket.bucketDomainName`. */ + public get bucketDomainName(): string { + return this.bucket.bucketDomainName; + } + + /** The bucket's regional domain name. Convenience pass-through for `this.bucket.bucketRegionalDomainName`. */ + public get bucketRegionalDomainName(): string { + return this.bucket.bucketRegionalDomainName; + } + constructor(scope: Construct, id: string, props: SecureBucketProps = {}) { super(scope, id); diff --git a/test/resources/s3-bucket/secure-bucket.test.ts b/test/resources/s3-bucket/secure-bucket.test.ts index d6a02fd..717df35 100644 --- a/test/resources/s3-bucket/secure-bucket.test.ts +++ b/test/resources/s3-bucket/secure-bucket.test.ts @@ -184,4 +184,27 @@ describe('SecureBucket', () => { expect(secureBucket.bucket.node.id).toBe('Bucket'); }); }); + + describe('attribute pass-through getters', () => { + it('exposes bucketArn and bucketName directly on the construct', () => { + const bucket = new SecureBucket(stack, 'TestBucket', { + bucketName: 'my-secure-bucket', + }); + + expect(bucket.bucketArn).toBeDefined(); + expect(bucket.bucketArn).toBe(bucket.bucket.bucketArn); + expect(bucket.bucketName).toBe(bucket.bucket.bucketName); + expect(bucket.bucketDomainName).toBe(bucket.bucket.bucketDomainName); + expect(bucket.bucketRegionalDomainName).toBe(bucket.bucket.bucketRegionalDomainName); + }); + + it('resolves getters to the same values as the wrapped bucket', () => { + const bucket = new SecureBucket(stack, 'TestBucket', { + bucketName: 'direct-access', + }); + + expect(bucket.bucketArn).toBe(bucket.bucket.bucketArn); + expect(bucket.bucketName).toBe(bucket.bucket.bucketName); + }); + }); }); From 4e2ca66208468a7725139d9177df7bf972215757 Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P Date: Sun, 19 Jul 2026 21:58:02 +0530 Subject: [PATCH 3/4] test: add unit tests for secureMaxVersioned / secureMaxRemovalPolicy (fixes #44) --- test/resources/s3-bucket/fields.test.ts | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 test/resources/s3-bucket/fields.test.ts diff --git a/test/resources/s3-bucket/fields.test.ts b/test/resources/s3-bucket/fields.test.ts new file mode 100644 index 0000000..c100171 --- /dev/null +++ b/test/resources/s3-bucket/fields.test.ts @@ -0,0 +1,84 @@ +import { RemovalPolicy } from 'aws-cdk-lib'; + +import { + secureMaxVersioned, + secureMaxRemovalPolicy, +} from '../../../src/resources/s3-bucket/fields'; + +describe('fields: secureMaxVersioned', () => { + it('should enable versioning when the tier value is true', () => { + expect(secureMaxVersioned(true, undefined)).toBe(true); + expect(secureMaxVersioned(true, false)).toBe(true); + expect(secureMaxVersioned(true, true)).toBe(true); + }); + + it('should never weaken an enabled tier value to false', () => { + expect(secureMaxVersioned(true, false)).toBe(true); + }); + + it('should adopt an incoming true value when the tier value is false', () => { + expect(secureMaxVersioned(false, true)).toBe(true); + }); + + it('should stay disabled when both values are false/undefined', () => { + expect(secureMaxVersioned(false, undefined)).toBe(false); + expect(secureMaxVersioned(false, false)).toBe(false); + }); + + it('should treat truthy/incoming coercion correctly', () => { + expect(secureMaxVersioned(false, true)).toBe(true); + expect(secureMaxVersioned(true, false)).toBe(true); + }); +}); + +describe('fields: secureMaxRemovalPolicy', () => { + it('should return the tier value when incoming is undefined', () => { + expect(secureMaxRemovalPolicy(RemovalPolicy.RETAIN, undefined)).toBe(RemovalPolicy.RETAIN); + expect(secureMaxRemovalPolicy(RemovalPolicy.DESTROY, undefined)).toBe(RemovalPolicy.DESTROY); + }); + + it('should never weaken a RETAIN tier', () => { + expect(secureMaxRemovalPolicy(RemovalPolicy.RETAIN, RemovalPolicy.DESTROY)).toBe( + RemovalPolicy.RETAIN + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.RETAIN, RemovalPolicy.SNAPSHOT)).toBe( + RemovalPolicy.RETAIN + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.RETAIN, RemovalPolicy.RETAIN)).toBe( + RemovalPolicy.RETAIN + ); + }); + + it('should adopt an incoming policy that is at least as strong as the tier', () => { + expect(secureMaxRemovalPolicy(RemovalPolicy.DESTROY, RemovalPolicy.SNAPSHOT)).toBe( + RemovalPolicy.SNAPSHOT + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.DESTROY, RemovalPolicy.RETAIN)).toBe( + RemovalPolicy.RETAIN + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.SNAPSHOT, RemovalPolicy.RETAIN)).toBe( + RemovalPolicy.RETAIN + ); + }); + + it('should keep the weaker tier value when incoming is weaker', () => { + expect(secureMaxRemovalPolicy(RemovalPolicy.SNAPSHOT, RemovalPolicy.DESTROY)).toBe( + RemovalPolicy.SNAPSHOT + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.DESTROY, RemovalPolicy.DESTROY)).toBe( + RemovalPolicy.DESTROY + ); + }); + + it('should rank SNAPSHOT between DESTROY and RETAIN', () => { + expect(secureMaxRemovalPolicy(RemovalPolicy.SNAPSHOT, RemovalPolicy.SNAPSHOT)).toBe( + RemovalPolicy.SNAPSHOT + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.DESTROY, RemovalPolicy.SNAPSHOT)).toBe( + RemovalPolicy.SNAPSHOT + ); + expect(secureMaxRemovalPolicy(RemovalPolicy.SNAPSHOT, RemovalPolicy.RETAIN)).toBe( + RemovalPolicy.RETAIN + ); + }); +}); From 07da1fdee724e5a8cc50f53f9f5fbe5fd67c5ebc Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P Date: Sun, 19 Jul 2026 22:02:09 +0530 Subject: [PATCH 4/4] test: cover ComplianceRegistry.all() and stop excluding index.ts from coverage (fixes #45) --- jest.config.js | 1 - test/core/compliance-registry.test.ts | 33 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/core/compliance-registry.test.ts diff --git a/jest.config.js b/jest.config.js index 59418af..d2ed01e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,7 +12,6 @@ module.exports = { collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', - '!src/**/index.ts', ], coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'], diff --git a/test/core/compliance-registry.test.ts b/test/core/compliance-registry.test.ts new file mode 100644 index 0000000..3e7c6de --- /dev/null +++ b/test/core/compliance-registry.test.ts @@ -0,0 +1,33 @@ +import { ComplianceRegistry } from '../../src'; +import { S3BucketCompliance } from '../../src/resources/s3-bucket/compliance'; + +describe('ComplianceRegistry', () => { + it('returns a report for every exposed resource', () => { + const reports = ComplianceRegistry.all(); + + expect(Array.isArray(reports)).toBe(true); + expect(reports.length).toBeGreaterThanOrEqual(1); + }); + + it('stays in sync with the S3 compliance report', () => { + const reports = ComplianceRegistry.all(); + const s3Report = S3BucketCompliance.report(); + + expect(reports).toContainEqual(s3Report); + }); + + it('every report entry has a resource, version, and non-empty controls', () => { + for (const report of ComplianceRegistry.all()) { + expect(report.resourceType).toBeTruthy(); + expect(report.version).toBeTruthy(); + expect(Array.isArray(report.controls)).toBe(true); + expect(report.controls.length).toBeGreaterThan(0); + for (const control of report.controls) { + expect(control.id).toBeTruthy(); + expect(control.title).toBeTruthy(); + expect(control.status).toBeTruthy(); + expect(control.enforcedBy).toBeTruthy(); + } + } + }); +});