Skip to content
This repository was archived by the owner on Apr 6, 2026. It is now read-only.

Commit 420f8ca

Browse files
fgueryfabrice guery
andauthored
feat(s3): validate that PutBucketNotificationConfiguration take the f… (#35)
* feat(s3): validate that PutBucketNotificationConfiguration take the filters into account * feat: added s3 notification tests for other SDKs beside Java --------- Co-authored-by: fabrice guery <fabrice@formance.com>
1 parent 91d7c48 commit 420f8ca

6 files changed

Lines changed: 629 additions & 2 deletions

File tree

sdk-test-awscli/test_all.sh

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,11 +874,105 @@ print('true' if 'test-group' in groups else 'false')
874874
aws_cmd cognito-idp delete-user-pool --user-pool-id "$pool_id" >/dev/null 2>&1 || true
875875
}
876876

877+
# ---------------------------------------------------------------------------
878+
# S3 Notification Filter Tests
879+
# ---------------------------------------------------------------------------
880+
881+
run_s3_notifications() {
882+
echo "--- S3 Notification Filter Tests ---"
883+
884+
local bucket="s3-notif-filter-bucket"
885+
local queue="s3-notif-filter-queue"
886+
local topic="s3-notif-filter-topic"
887+
local account_id="000000000000"
888+
local queue_arn="arn:aws:sqs:us-east-1:${account_id}:${queue}"
889+
890+
# Setup
891+
aws_cmd sqs create-queue --queue-name "$queue" > /dev/null 2>&1
892+
local topic_out
893+
topic_out=$(aws_cmd sns create-topic --name "$topic" 2>&1) && rc=0 || rc=1
894+
local topic_arn
895+
topic_arn=$(echo "$topic_out" | python3 -c "import sys,json; print(json.load(sys.stdin)['TopicArn'])" 2>/dev/null || echo "")
896+
if [ -z "$topic_arn" ]; then
897+
check "S3 Notifications setup: CreateTopic" "false" "$topic_out"
898+
return
899+
fi
900+
aws_cmd s3api create-bucket --bucket "$bucket" > /dev/null 2>&1
901+
902+
# PutBucketNotificationConfiguration with filters
903+
local notif_config
904+
notif_config=$(cat <<NOTIF_EOF
905+
{
906+
"QueueConfigurations": [{
907+
"Id": "sqs-filtered",
908+
"QueueArn": "${queue_arn}",
909+
"Events": ["s3:ObjectCreated:*"],
910+
"Filter": {
911+
"Key": {
912+
"FilterRules": [
913+
{"Name": "prefix", "Value": "incoming/"},
914+
{"Name": "suffix", "Value": ".csv"}
915+
]
916+
}
917+
}
918+
}],
919+
"TopicConfigurations": [{
920+
"Id": "sns-filtered",
921+
"TopicArn": "${topic_arn}",
922+
"Events": ["s3:ObjectRemoved:*"],
923+
"Filter": {
924+
"Key": {
925+
"FilterRules": [
926+
{"Name": "prefix", "Value": ""},
927+
{"Name": "suffix", "Value": ".txt"}
928+
]
929+
}
930+
}
931+
}]
932+
}
933+
NOTIF_EOF
934+
)
935+
936+
local out
937+
out=$(aws_cmd s3api put-bucket-notification-configuration \
938+
--bucket "$bucket" \
939+
--notification-configuration "$notif_config" 2>&1) && rc=0 || rc=1
940+
check "S3 PutBucketNotificationConfiguration with Filter" "$( [ $rc -eq 0 ] && echo true || echo false )" "$out"
941+
942+
# GetBucketNotificationConfiguration — verify filter round-trip
943+
out=$(aws_cmd s3api get-bucket-notification-configuration --bucket "$bucket" 2>&1) && rc=0 || rc=1
944+
if [ $rc -eq 0 ]; then
945+
local queue_filter_count topic_filter_count
946+
queue_filter_count=$(echo "$out" | python3 -c "
947+
import sys, json
948+
d = json.load(sys.stdin)
949+
qc = [q for q in d.get('QueueConfigurations', []) if q.get('QueueArn') == '${queue_arn}']
950+
print(len(qc[0]['Filter']['Key']['FilterRules']) if qc and 'Filter' in qc[0] else 0)
951+
" 2>/dev/null || echo "0")
952+
topic_filter_count=$(echo "$out" | python3 -c "
953+
import sys, json
954+
d = json.load(sys.stdin)
955+
tc = [t for t in d.get('TopicConfigurations', []) if t.get('TopicArn') == '${topic_arn}']
956+
print(len(tc[0]['Filter']['Key']['FilterRules']) if tc and 'Filter' in tc[0] else 0)
957+
" 2>/dev/null || echo "0")
958+
check "S3 GetBucketNotificationConfiguration (queue filter round-trip)" "$( [ "$queue_filter_count" = "2" ] && echo true || echo false )" "expected 2 rules, got $queue_filter_count"
959+
check "S3 GetBucketNotificationConfiguration (topic filter round-trip)" "$( [ "$topic_filter_count" = "2" ] && echo true || echo false )" "expected 2 rules, got $topic_filter_count"
960+
else
961+
check "S3 GetBucketNotificationConfiguration (queue filter round-trip)" "false" "$out"
962+
check "S3 GetBucketNotificationConfiguration (topic filter round-trip)" "false" "$out"
963+
fi
964+
965+
# Cleanup
966+
aws_cmd s3api delete-bucket --bucket "$bucket" > /dev/null 2>&1
967+
aws_cmd sqs delete-queue --queue-url "${ENDPOINT}/${account_id}/${queue}" > /dev/null 2>&1
968+
aws_cmd sns delete-topic --topic-arn "$topic_arn" > /dev/null 2>&1
969+
}
970+
877971
# ---------------------------------------------------------------------------
878972
# Group registry and entry point
879973
# ---------------------------------------------------------------------------
880974

881-
ALL_GROUPS=(ssm sqs sns s3 dynamodb dynamodb-gsi dynamodb-scan-filter iam sts secretsmanager kms cognito)
975+
ALL_GROUPS=(ssm sqs sns s3 dynamodb dynamodb-gsi dynamodb-scan-filter iam sts secretsmanager kms cognito s3-notifications)
882976

883977
resolve_enabled() {
884978
local names=()

sdk-test-go/main.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,128 @@ func resolveEnabled(args []string) map[string]bool {
757757
return m
758758
}
759759

760+
// ── S3 Notification Filter ────────────────────────────────────────────────────
761+
762+
func runS3Notifications(cfg aws.Config) {
763+
fmt.Println("--- S3 Notification Filter Tests ---")
764+
765+
s3Svc := s3.NewFromConfig(cfg, func(o *s3.Options) {
766+
o.UsePathStyle = true
767+
})
768+
sqsSvc := sqs.NewFromConfig(cfg)
769+
snsSvc := sns.NewFromConfig(cfg)
770+
771+
queueName := "s3-notif-filter-queue"
772+
topicName := "s3-notif-filter-topic"
773+
bucketName := "s3-notif-filter-bucket"
774+
775+
// Create SQS queue
776+
_, err := sqsSvc.CreateQueue(ctx, &sqs.CreateQueueInput{QueueName: aws.String(queueName)})
777+
check("S3Notif CreateQueue", err)
778+
if err != nil {
779+
return
780+
}
781+
queueArn := "arn:aws:sqs:us-east-1:000000000000:" + queueName
782+
783+
// Create SNS topic
784+
ct, err := snsSvc.CreateTopic(ctx, &sns.CreateTopicInput{Name: aws.String(topicName)})
785+
check("S3Notif CreateTopic", err)
786+
if err != nil {
787+
return
788+
}
789+
topicArn := aws.ToString(ct.TopicArn)
790+
791+
// Create S3 bucket
792+
_, err = s3Svc.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucketName)})
793+
check("S3Notif CreateBucket", err)
794+
if err != nil {
795+
return
796+
}
797+
798+
// PutBucketNotificationConfiguration
799+
_, err = s3Svc.PutBucketNotificationConfiguration(ctx, &s3.PutBucketNotificationConfigurationInput{
800+
Bucket: aws.String(bucketName),
801+
NotificationConfiguration: &s3types.NotificationConfiguration{
802+
QueueConfigurations: []s3types.QueueConfiguration{
803+
{
804+
Id: aws.String("sqs-filtered"),
805+
QueueArn: aws.String(queueArn),
806+
Events: []s3types.Event{s3types.EventS3ObjectCreated},
807+
Filter: &s3types.NotificationConfigurationFilter{
808+
Key: &s3types.S3KeyFilter{
809+
FilterRules: []s3types.FilterRule{
810+
{Name: s3types.FilterRuleNamePrefix, Value: aws.String("incoming/")},
811+
{Name: s3types.FilterRuleNameSuffix, Value: aws.String(".csv")},
812+
},
813+
},
814+
},
815+
},
816+
},
817+
TopicConfigurations: []s3types.TopicConfiguration{
818+
{
819+
Id: aws.String("sns-filtered"),
820+
TopicArn: aws.String(topicArn),
821+
Events: []s3types.Event{s3types.EventS3ObjectRemoved},
822+
Filter: &s3types.NotificationConfigurationFilter{
823+
Key: &s3types.S3KeyFilter{
824+
FilterRules: []s3types.FilterRule{
825+
{Name: s3types.FilterRuleNamePrefix, Value: aws.String("")},
826+
{Name: s3types.FilterRuleNameSuffix, Value: aws.String(".txt")},
827+
},
828+
},
829+
},
830+
},
831+
},
832+
},
833+
})
834+
check("S3Notif PutBucketNotificationConfiguration", err)
835+
if err != nil {
836+
return
837+
}
838+
839+
// GetBucketNotificationConfiguration and assert
840+
gnc, err := s3Svc.GetBucketNotificationConfiguration(ctx, &s3.GetBucketNotificationConfigurationInput{
841+
Bucket: aws.String(bucketName),
842+
})
843+
check("S3Notif GetBucketNotificationConfiguration", err)
844+
if err != nil {
845+
return
846+
}
847+
848+
// Assert queue config
849+
check("S3Notif QueueConfig present",
850+
nil,
851+
len(gnc.QueueConfigurations) > 0 && aws.ToString(gnc.QueueConfigurations[0].QueueArn) == queueArn,
852+
)
853+
check("S3Notif QueueConfig has 2 filter rules",
854+
nil,
855+
len(gnc.QueueConfigurations) > 0 &&
856+
gnc.QueueConfigurations[0].Filter != nil &&
857+
gnc.QueueConfigurations[0].Filter.Key != nil &&
858+
len(gnc.QueueConfigurations[0].Filter.Key.FilterRules) == 2,
859+
)
860+
861+
// Assert topic config
862+
check("S3Notif TopicConfig present",
863+
nil,
864+
len(gnc.TopicConfigurations) > 0 && aws.ToString(gnc.TopicConfigurations[0].TopicArn) == topicArn,
865+
)
866+
check("S3Notif TopicConfig has 2 filter rules",
867+
nil,
868+
len(gnc.TopicConfigurations) > 0 &&
869+
gnc.TopicConfigurations[0].Filter != nil &&
870+
gnc.TopicConfigurations[0].Filter.Key != nil &&
871+
len(gnc.TopicConfigurations[0].Filter.Key.FilterRules) == 2,
872+
)
873+
874+
// Cleanup
875+
s3Svc.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: aws.String(bucketName)})
876+
sqsSvc.DeleteQueue(ctx, &sqs.DeleteQueueInput{
877+
QueueUrl: aws.String("http://localhost:4566/000000000000/" + queueName),
878+
})
879+
snsSvc.DeleteTopic(ctx, &sns.DeleteTopicInput{TopicArn: aws.String(topicArn)})
880+
}
881+
760882
func main() {
761883
endpoint := os.Getenv("FLOCI_ENDPOINT")
762884
if endpoint == "" {
@@ -784,6 +906,7 @@ func main() {
784906
{"kms", runKMS},
785907
{"kinesis", runKinesis},
786908
{"cloudwatch", runCloudWatch},
909+
{"s3-notifications", runS3Notifications},
787910
}
788911

789912
enabled := resolveEnabled(os.Args[1:])

sdk-test-java/src/main/java/com/floci/test/tests/S3NotificationTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,26 @@ public void run(TestContext ctx) {
9696
.id("sqs-created")
9797
.queueArn(createdQueueArn)
9898
.events(Event.S3_OBJECT_CREATED)
99+
.filter(NotificationConfigurationFilter.builder()
100+
.key(S3KeyFilter.builder()
101+
.filterRules(FilterRule.builder()
102+
.name(FilterRuleName.PREFIX).value("").build())
103+
.build())
104+
.build())
99105
.build())
100106
.topicConfigurations(TopicConfiguration.builder()
101107
.id("sns-removed")
102108
.topicArn(topicArn)
103109
.events(Event.S3_OBJECT_REMOVED)
110+
.filter(NotificationConfigurationFilter.builder()
111+
.key(S3KeyFilter.builder()
112+
.filterRules(
113+
FilterRule.builder()
114+
.name(FilterRuleName.PREFIX).value("").build(),
115+
FilterRule.builder()
116+
.name(FilterRuleName.SUFFIX).value(".txt").build())
117+
.build())
118+
.build())
104119
.build())
105120
.build())
106121
.build());
@@ -119,11 +134,23 @@ public void run(TestContext ctx) {
119134
.anyMatch(q -> q.queueArn().equals(createdQueueArn));
120135
boolean hasTopic = nc.topicConfigurations().stream()
121136
.anyMatch(t -> t.topicArn().equals(topicArn));
137+
boolean queueFilterPreserved = nc.queueConfigurations().stream()
138+
.anyMatch(q -> q.queueArn().equals(createdQueueArn)
139+
&& q.filter() != null && q.filter().key() != null
140+
&& q.filter().key().filterRules().size() == 1);
141+
boolean topicFilterPreserved = nc.topicConfigurations().stream()
142+
.anyMatch(t -> t.topicArn().equals(topicArn)
143+
&& t.filter() != null && t.filter().key() != null
144+
&& t.filter().key().filterRules().size() == 2);
122145
ctx.check("S3 GetBucketNotificationConfiguration (queue)", hasQueue);
123146
ctx.check("S3 GetBucketNotificationConfiguration (topic)", hasTopic);
147+
ctx.check("S3 GetBucketNotificationConfiguration (queue filter round-trip)", queueFilterPreserved);
148+
ctx.check("S3 GetBucketNotificationConfiguration (topic filter round-trip)", topicFilterPreserved);
124149
} catch (Exception e) {
125150
ctx.check("S3 GetBucketNotificationConfiguration (queue)", false, e);
126151
ctx.check("S3 GetBucketNotificationConfiguration (topic)", false, e);
152+
ctx.check("S3 GetBucketNotificationConfiguration (queue filter round-trip)", false, e);
153+
ctx.check("S3 GetBucketNotificationConfiguration (topic filter round-trip)", false, e);
127154
}
128155

129156
// 7. PutObject → fires ObjectCreated:Put to createdQueue

0 commit comments

Comments
 (0)