Skip to content

Commit 5ec26e5

Browse files
committed
test(integ): cover the #1430 EventBridgeEnabled pair in the s3-lifecycle fixture
The unit tests pin the translation; this proves it against real AWS, and the expected values are CloudFormation ground truth rather than a guess - a real CFn A/B of this exact shape (stack Cdkd1430EbProbe, us-east-1) returned an EMPTY body for `EventBridgeEnabled: false` and `{"EventBridgeConfiguration": {}}` for `true`. `s3-lifecycle` is the right home: it already carries an L1 `CfnBucket` for precisely this class - CFn spellings the L2 cannot emit that cdkd dropped - and it has no custom resource in the path. L1 is required rather than stylistic, because the L2's `eventBridgeEnabled` routes through a Custom::S3BucketNotifications custom resource that never reaches `S3BucketProvider`'s own NotificationConfiguration handling. The `false` half rides on the existing LegacyBucket; the `true` half is its own bucket, and it is asserted FIRST as the vacuity guard - asserting only that the `false` bucket lacks the block would pass just as happily if cdkd stopped applying NotificationConfiguration altogether. A `cdkd drift` call on the clean stack covers the read side: pre-fix, `readCurrentState` returned the SDK's `{}` against a CFn-shaped state baseline, so the boolean read back as permanently missing. Both captures normalize an empty response body to `{}` before jq sees it. An unconfigured bucket returns an EMPTY body, not `{}` - the exact shape CloudFormation produces for the `false` case - and jq given empty input emits nothing, so the assertion failed on cdkd's CORRECT output. The first real run of this fixture did exactly that. Run: PASS, 3 phases, destroy 3 deleted / 0 errors / 0 orphans.
1 parent 29f680f commit 5ec26e5

3 files changed

Lines changed: 89 additions & 1 deletion

File tree

docs/_generated/integ-last-run.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ s3-asset-deploy 2026-07-26T19:45:58Z PASS 58 verify.sh 0727b sweep-b12 staleness
230230
s3-cloudfront 2026-08-08T17:42:05Z PASS 228 verify.sh 1373 OriginCustomHeaders create+update-survive asserts green; destroy 0 errors 0 orphans
231231
s3-directory-bucket 2026-08-02T15:38:47Z PASS 50 verify.sh re-run for #1347 wontdo-comment PR; guard + clean destroy, 0 orphans
232232
s3-event-notification 2026-07-21T05:28:05Z PASS 79 verify.sh rc ok, orph clean
233-
s3-lifecycle 2026-08-09T13:59:22Z PASS 57 verify.sh post-delta-review re-run (#1388/#1424); rule-level keys asserted live; 2 del/0 err, 0 orphans
233+
s3-lifecycle 2026-08-09T15:54:29Z PASS 150 verify.sh #1430 EventBridgeEnabled true/false + clean-drift asserts added; destroy 0 err, 0 orph
234234
s3-object-lock 2026-07-21T14:42:20Z PASS 53 verify.sh rc ok, orph clean
235235
s3-replication-and-filter 2026-07-21T14:43:45Z PASS 63 verify.sh rc ok, orph clean
236236
s3-tables 2026-07-27T16:53:47Z PASS 45 verify.sh issue #1270/#1272 post-review re-run; 5 del 0 err, 0 orphans

tests/integration/s3-lifecycle/lib/s3-lifecycle-stack.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,23 @@ export class S3LifecycleStack extends cdk.Stack {
112112
new s3.CfnBucket(this, 'LegacyBucket', {
113113
bucketName: `cdkd-lifecycle-legacy-${cdk.Stack.of(this).account}`,
114114
versioningConfiguration: { status: 'Enabled' },
115+
// Issue #1430, same class as the lifecycle shapes below: a CFn spelling
116+
// with no SDK member behind it. CFn's `EventBridgeConfiguration` carries
117+
// a REQUIRED boolean, while the SDK's block is an EMPTY structure whose
118+
// PRESENCE enables delivery — so the boolean has nothing to map onto and
119+
// has to be translated into presence/absence. cdkd emitted the SDK block
120+
// whenever the CFn block existed, so this `false` came up with
121+
// EventBridge notifications ON: the inverse of the template.
122+
//
123+
// Ground truth is a real CloudFormation A/B of this exact shape (stack
124+
// Cdkd1430EbProbe, us-east-1, 2026-08-10): `false` -> no EventBridge
125+
// block; `true` -> `{}`. `EbEnabledBucket` below is the `true` half, and
126+
// it is what keeps the `false` assertion from passing vacuously.
127+
//
128+
// L1 is required, not a stylistic choice: the L2's `eventBridgeEnabled`
129+
// routes through a Custom::S3BucketNotifications custom resource, which
130+
// never reaches `S3BucketProvider`'s own NotificationConfiguration path.
131+
notificationConfiguration: { eventBridgeConfiguration: { eventBridgeEnabled: false } },
115132
lifecycleConfiguration: {
116133
rules: [
117134
{
@@ -131,5 +148,15 @@ export class S3LifecycleStack extends cdk.Stack {
131148
],
132149
},
133150
});
151+
152+
// The `true` half of the issue #1430 pair. Kept as its own bucket rather
153+
// than folded into LegacyBucket because the two values are mutually
154+
// exclusive on one bucket, and asserting only the `false` side would pass
155+
// just as happily if cdkd stopped applying NotificationConfiguration
156+
// altogether — this bucket is the vacuity guard for that assertion.
157+
new s3.CfnBucket(this, 'EbEnabledBucket', {
158+
bucketName: `cdkd-lifecycle-ebtrue-${cdk.Stack.of(this).account}`,
159+
notificationConfiguration: { eventBridgeConfiguration: { eventBridgeEnabled: true } },
160+
});
134161
}
135162
}

tests/integration/s3-lifecycle/verify.sh

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ STATE_KEY="cdkd/${STACK}/${REGION}/state.json"
8383
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
8484
BUCKET_NAME="cdkd-lifecycle-test-${ACCOUNT_ID}"
8585
LEGACY_BUCKET="cdkd-lifecycle-legacy-${ACCOUNT_ID}"
86+
# The EventBridgeEnabled: true half of the issue #1430 pair (the `false` half
87+
# rides on LEGACY_BUCKET).
88+
EB_TRUE_BUCKET="cdkd-lifecycle-ebtrue-${ACCOUNT_ID}"
8689

8790
# Resolve the built CLI path without a `cd` into dist/ that fails cryptically
8891
# (aborting under `set -e`) when dist/ is unbuilt -- the friendly guard below
@@ -97,6 +100,7 @@ cleanup() {
97100
fi
98101
aws s3api delete-bucket --bucket "${BUCKET_NAME}" --region "${REGION}" >/dev/null 2>&1 || true
99102
aws s3api delete-bucket --bucket "${LEGACY_BUCKET}" --region "${REGION}" >/dev/null 2>&1 || true
103+
aws s3api delete-bucket --bucket "${EB_TRUE_BUCKET}" --region "${REGION}" >/dev/null 2>&1 || true
100104
if [ -n "${STATE_BUCKET:-}" ]; then
101105
aws s3 rm "s3://${STATE_BUCKET}/${STATE_KEY}" >/dev/null 2>&1 || true
102106
aws s3 rm "s3://${STATE_BUCKET}/cdkd/${STACK}/${REGION}/lock.json" >/dev/null 2>&1 || true
@@ -204,6 +208,62 @@ if [ "${LEG_RULES}" != "2" ] || [ "${LEG_T}" != "90" ] || [ "${LEG_NVT}" != "30"
204208
fi
205209
echo " legacy singular Transition + NoncurrentVersionTransition + NoncurrentVersionExpirationInDays + rule-level ExpiredObjectDeleteMarker all applied"
206210

211+
# --- issue #1430: NotificationConfiguration.EventBridgeConfiguration --------
212+
# Same class as the legacy lifecycle keys above -- a CFn spelling with no SDK
213+
# member behind it. CFn carries a REQUIRED boolean `EventBridgeEnabled`; the
214+
# SDK's block is an EMPTY structure whose PRESENCE enables delivery. cdkd
215+
# emitted the block whenever the CFn block existed, so an explicit `false`
216+
# ENABLED notifications.
217+
#
218+
# Expected values are CloudFormation ground truth, not a guess: a real CFn A/B
219+
# of this exact shape (stack Cdkd1430EbProbe, us-east-1, 2026-08-10) returned
220+
# an EMPTY body for `false` and `{"EventBridgeConfiguration": {}}` for `true`.
221+
#
222+
# An unconfigured bucket returns an EMPTY body, not `{}`, so both captures are
223+
# normalized before jq sees them -- without that the `false` assertion fails on
224+
# cdkd's CORRECT output. (The first real run of this assertion did exactly
225+
# that.) The captures are unguarded on purpose: `set -e` aborts on a genuine
226+
# API failure, so reaching the normalization with an empty string means a
227+
# successful call on an unconfigured bucket.
228+
EB_TRUE_JSON="$(aws s3api get-bucket-notification-configuration \
229+
--bucket "${EB_TRUE_BUCKET}" --region "${REGION}" --output json)"
230+
EB_FALSE_JSON="$(aws s3api get-bucket-notification-configuration \
231+
--bucket "${LEGACY_BUCKET}" --region "${REGION}" --output json)"
232+
[ -n "${EB_TRUE_JSON//[[:space:]]/}" ] || EB_TRUE_JSON='{}'
233+
[ -n "${EB_FALSE_JSON//[[:space:]]/}" ] || EB_FALSE_JSON='{}'
234+
235+
EB_TRUE_HAS="$(printf '%s' "${EB_TRUE_JSON}" | jq -r 'has("EventBridgeConfiguration")')"
236+
EB_FALSE_HAS="$(printf '%s' "${EB_FALSE_JSON}" | jq -r 'has("EventBridgeConfiguration")')"
237+
238+
# The `true` side runs FIRST and is the vacuity guard: asserting only that the
239+
# `false` bucket lacks the block would pass just as happily if cdkd stopped
240+
# applying NotificationConfiguration altogether.
241+
if [ "${EB_TRUE_HAS}" != "true" ]; then
242+
echo "FAIL: ${EB_TRUE_BUCKET} (EventBridgeEnabled: true) has NO EventBridgeConfiguration" >&2
243+
echo " response: ${EB_TRUE_JSON}" >&2
244+
exit 1
245+
fi
246+
if [ "${EB_FALSE_HAS}" != "false" ]; then
247+
echo "FAIL: ${LEGACY_BUCKET} (EventBridgeEnabled: false) HAS an EventBridgeConfiguration" >&2
248+
echo " this is the issue #1430 inversion: an explicit false enabled delivery" >&2
249+
echo " response: ${EB_FALSE_JSON}" >&2
250+
exit 1
251+
fi
252+
echo " EventBridgeEnabled true -> block present, false -> block absent (matches CloudFormation)"
253+
254+
# Read side: `readCurrentState` must return the CFn shape
255+
# (`{EventBridgeEnabled: <bool>}`), not the SDK's `{}` -- the state baseline
256+
# holds the CFn spelling and drift-calculator only descends into keys present
257+
# in state, so the SDK shape reported permanent phantom drift on every
258+
# EventBridge-enabled bucket. `cdkd drift` exits 0 only when it finds none.
259+
if ! node "${LOCAL_DIST}" drift "${STACK}" \
260+
--state-bucket "${STATE_BUCKET}" --region "${REGION}"; then
261+
echo "FAIL: cdkd drift reported drift on a clean, freshly-deployed stack" >&2
262+
echo " (pre-#1430 the EventBridge boolean read back as permanently missing)" >&2
263+
exit 1
264+
fi
265+
echo " no drift on a clean stack (#1430 read side)"
266+
207267
CREATION_P1="$(aws s3api list-buckets \
208268
--query "Buckets[?Name=='${BUCKET_NAME}'].CreationDate | [0]" --output text)"
209269
echo " baseline bucket CreationDate=${CREATION_P1}"
@@ -241,6 +301,7 @@ assert_gone_eventually "bucket ${BUCKET_NAME} still exists after destroy" aws s3
241301
echo " bucket deleted"
242302

243303
assert_gone_eventually "legacy bucket ${LEGACY_BUCKET} still exists after destroy" aws s3api head-bucket --bucket "${LEGACY_BUCKET}" --region "${REGION}"
304+
assert_gone_eventually "EventBridge bucket ${EB_TRUE_BUCKET} still exists after destroy" aws s3api head-bucket --bucket "${EB_TRUE_BUCKET}" --region "${REGION}"
244305
echo " legacy bucket deleted"
245306

246307
assert_gone "state file ${STATE_KEY} still exists after destroy" aws s3api head-object --bucket "${STATE_BUCKET}" --key "${STATE_KEY}"

0 commit comments

Comments
 (0)