// ExpirationDate (date-based, not Days) integration scenario. package lifecycle import ( "context" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/stretchr/testify/require" ) // TestLifecycleExpirationDateInThePast: an Expiration{Date: } rule // routes through the engine's ScanAtDate mode rather than the // EventDriven delay-group path. The worker's dispatcher must still fire // when the date has already passed at the time the worker runs. Pinning // this path because most tests use Days-based rules; ScanAtDate is a // separate compile + dispatch branch (engine.decideMode case // ActionKindExpirationDate) that wouldn't be exercised otherwise. func TestLifecycleExpirationDateInThePast(t *testing.T) { c := s3Client(t) fc, fcClose := filerClient(t) defer fcClose() bucket := uniqueBucket("expdate") mustCreateBucket(t, c, bucket) // Date in the past: AWS rejects ExpirationDate in the future from // being processed early but a past date is the natural integration // test — every object hit by the rule is immediately eligible. pastDate := time.Now().Add(-7 * 24 * time.Hour).UTC().Truncate(24 * time.Hour) _, err := c.PutBucketLifecycleConfiguration(context.Background(), &s3.PutBucketLifecycleConfigurationInput{ Bucket: aws.String(bucket), LifecycleConfiguration: &types.BucketLifecycleConfiguration{ Rules: []types.LifecycleRule{{ ID: aws.String("expire-by-date"), Status: types.ExpirationStatusEnabled, Filter: &types.LifecycleRuleFilter{Prefix: aws.String("d/")}, Expiration: &types.LifecycleExpiration{ Date: aws.Time(pastDate), }, }}, }, }) require.NoError(t, err) const oldKey = "d/in-prefix.txt" const otherKey = "other/skip.txt" putObject(t, c, bucket, oldKey, "old") putObject(t, c, bucket, otherKey, "other") // Backdate oldKey too — defense-in-depth so the test doesn't // depend on whether the worker also considers MinTriggerAge for // date kinds; a fresh object with a past date should still expire, // but aging it locks the assertion either way. backdateMtime(t, fc, bucket, oldKey, 30) out := runLifecycleShard(t) t.Logf("shell output:\n%s", out) require.Eventuallyf(t, func() bool { _, err := c.HeadObject(context.Background(), &s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(oldKey), }) return isS3NotFound(err) }, 30*time.Second, 500*time.Millisecond, "%s/%s must be expired by past-date rule", bucket, oldKey) // Out-of-prefix object stays — the rule's Filter.Prefix gates this. _, err = c.HeadObject(context.Background(), &s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(otherKey), }) require.NoError(t, err, "object outside the rule's prefix must remain") }