|
9 | 9 | """ |
10 | 10 |
|
11 | 11 | import boto3 |
| 12 | +import json |
12 | 13 | import requests |
13 | 14 | import secrets |
| 15 | +import time |
| 16 | + |
| 17 | +from util import flatten |
| 18 | +from util import setup_notification_endpoint |
14 | 19 |
|
15 | 20 | from urllib.parse import urlparse |
16 | 21 |
|
@@ -59,3 +64,90 @@ def test_bucket_urls(objects_endpoint, access_key, secret_key): |
59 | 64 | response = requests.get(url) |
60 | 65 | assert response.status_code == 200 |
61 | 66 | assert response.text == "test" |
| 67 | + |
| 68 | + |
| 69 | +def test_notifications( |
| 70 | + bucket, |
| 71 | + access_key, |
| 72 | + secret_key, |
| 73 | + objects_endpoint, |
| 74 | + server, |
| 75 | + region, |
| 76 | +): |
| 77 | + """ Using S3 SNS (Simple Notification Service) we can be informed via |
| 78 | + webhooks, when something changes on a bucket. |
| 79 | +
|
| 80 | + """ |
| 81 | + |
| 82 | + # Run a service that can act as a webhook endpoint |
| 83 | + setup_notification_endpoint(server) |
| 84 | + |
| 85 | + # Get an SNS client (Simple Notification Service) |
| 86 | + sns = boto3.client( |
| 87 | + 'sns', |
| 88 | + endpoint_url=objects_endpoint, |
| 89 | + aws_access_key_id=access_key, |
| 90 | + aws_secret_access_key=secret_key, |
| 91 | + region_name='default', |
| 92 | + ) |
| 93 | + |
| 94 | + # Create a test topic |
| 95 | + name = f"at-{secrets.token_hex(8)}" |
| 96 | + |
| 97 | + topic = sns.create_topic(Name=name, Attributes={ |
| 98 | + "push-endpoint": f"http://{server.ip('public', 4)}:8000", |
| 99 | + }) |
| 100 | + |
| 101 | + # Get notified whenever an object is created |
| 102 | + bucket.Notification().put(NotificationConfiguration={ |
| 103 | + "TopicConfigurations": [ |
| 104 | + { |
| 105 | + "Id": name, |
| 106 | + "TopicArn": topic['TopicArn'], |
| 107 | + "Events": ["s3:ObjectCreated:*"] |
| 108 | + } |
| 109 | + ] |
| 110 | + }) |
| 111 | + |
| 112 | + # We have to wait a moment for the configuration to propagate |
| 113 | + timeout = time.monotonic() + 30 |
| 114 | + |
| 115 | + while time.monotonic() < timeout: |
| 116 | + bucket.put_object(Key='pre-check', Body=b'') |
| 117 | + |
| 118 | + found = False |
| 119 | + with server.get_file_handle('notification-body.log') as notifications: |
| 120 | + for line in notifications: |
| 121 | + n = json.loads(line) |
| 122 | + for r in n['Records']: |
| 123 | + if r['s3']['object']['key'] == 'pre-check': |
| 124 | + found = True |
| 125 | + |
| 126 | + if found: |
| 127 | + break |
| 128 | + |
| 129 | + time.sleep(1) |
| 130 | + |
| 131 | + # Generate multiple notifications |
| 132 | + for i in range(3): |
| 133 | + bucket.put_object(Key=f'count-{i}', Body=b'') |
| 134 | + |
| 135 | + # Ensure they were received (excluding the pre-check objects from above) |
| 136 | + with server.get_file_handle('notification-body.log') as notification_log: |
| 137 | + notifications = [json.loads(line) for line in notification_log |
| 138 | + if 'pre-check' not in line] |
| 139 | + |
| 140 | + # A single message may contain multiple records |
| 141 | + records = flatten(m['Records'] for m in notifications) |
| 142 | + assert len(records) == 3 |
| 143 | + |
| 144 | + # The records are sent in order |
| 145 | + assert records[0]['s3']['object']['key'] == 'count-0' |
| 146 | + assert records[1]['s3']['object']['key'] == 'count-1' |
| 147 | + assert records[2]['s3']['object']['key'] == 'count-2' |
| 148 | + |
| 149 | + # They all share some properties |
| 150 | + for record in records: |
| 151 | + assert record['eventName'] == 'ObjectCreated:Put' |
| 152 | + assert record['awsRegion'] == region |
| 153 | + assert record['s3']['bucket']['name'] == bucket.name |
0 commit comments