"""EventBridge Pipes integration tests.""" import time import pytest from botocore.exceptions import ClientError ACCOUNT_ID = "000000000000" REGION = "us-east-1" ROLE_ARN = f"arn:aws:iam::{ACCOUNT_ID}:role/pipe-role" def sqs_arn(queue_name): return f"arn:aws:sqs:{REGION}:{ACCOUNT_ID}:{queue_name}" class TestPipesCRUD: """Test Pipes create, describe, list, update, delete operations.""" def test_create_pipe(self, pipes_client, sqs_client, unique_name): """Test CreatePipe creates a pipe in STOPPED state.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: response = pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) assert response["CurrentState"] == "STOPPED" assert pipe_name in response["Arn"] finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) def test_describe_pipe(self, pipes_client, sqs_client, unique_name): """Test DescribePipe returns pipe details.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) response = pipes_client.describe_pipe(Name=pipe_name) assert response["Name"] == pipe_name assert response["Source"] == sqs_arn(src_name) assert response["Target"] == sqs_arn(tgt_name) assert response["CurrentState"] == "STOPPED" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) def test_list_pipes(self, pipes_client, sqs_client, unique_name): """Test ListPipes returns created pipe.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) response = pipes_client.list_pipes() names = [p["Name"] for p in response["Pipes"]] assert pipe_name in names finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) def test_update_pipe(self, pipes_client, sqs_client, unique_name): """Test UpdatePipe updates pipe description.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) pipes_client.update_pipe( Name=pipe_name, RoleArn=ROLE_ARN, Description="updated via SDK", DesiredState="STOPPED", ) response = pipes_client.describe_pipe(Name=pipe_name) assert response["Description"] == "updated via SDK" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) def test_delete_pipe(self, pipes_client, sqs_client, unique_name): """Test DeletePipe removes the pipe.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) pipes_client.delete_pipe(Name=pipe_name) with pytest.raises(ClientError) as exc_info: pipes_client.describe_pipe(Name=pipe_name) assert exc_info.value.response["Error"]["Code"] == "NotFoundException" finally: sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) def test_describe_nonexistent_pipe(self, pipes_client): """Test DescribePipe for non-existent pipe returns NotFoundException.""" with pytest.raises(ClientError) as exc_info: pipes_client.describe_pipe(Name="nonexistent-pipe") assert exc_info.value.response["Error"]["Code"] == "NotFoundException" class TestPipesLifecycle: """Test Pipes start/stop operations.""" def test_start_and_stop_pipe(self, pipes_client, sqs_client, unique_name): """Test StartPipe and StopPipe transitions.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" sqs_client.create_queue(QueueName=src_name) sqs_client.create_queue(QueueName=tgt_name) try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) response = pipes_client.start_pipe(Name=pipe_name) assert response["CurrentState"] == "RUNNING" response = pipes_client.stop_pipe(Name=pipe_name) assert response["CurrentState"] == "STOPPED" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, src_name)) sqs_client.delete_queue(QueueUrl=_queue_url(sqs_client, tgt_name)) class TestPipesPolling: """Test Pipes source polling and target invocation.""" def test_sqs_to_sqs_forwarding(self, pipes_client, sqs_client, unique_name): """Test that a RUNNING pipe forwards SQS messages to SQS target.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" src_resp = sqs_client.create_queue(QueueName=src_name) src_url = src_resp["QueueUrl"] tgt_resp = sqs_client.create_queue(QueueName=tgt_name) tgt_url = tgt_resp["QueueUrl"] try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="RUNNING", ) sqs_client.send_message(QueueUrl=src_url, MessageBody="hello from pipes") found = False for _ in range(15): time.sleep(1) resp = sqs_client.receive_message( QueueUrl=tgt_url, MaxNumberOfMessages=1, WaitTimeSeconds=1 ) msgs = resp.get("Messages", []) if msgs and "hello from pipes" in msgs[0]["Body"]: found = True break assert found, "Target queue should receive forwarded message" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=src_url) sqs_client.delete_queue(QueueUrl=tgt_url) def test_filter_criteria_filters_messages(self, pipes_client, sqs_client, unique_name): """Test that FilterCriteria only forwards matching messages.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" src_resp = sqs_client.create_queue(QueueName=src_name) src_url = src_resp["QueueUrl"] tgt_resp = sqs_client.create_queue(QueueName=tgt_name) tgt_url = tgt_resp["QueueUrl"] try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="RUNNING", SourceParameters={ "FilterCriteria": { "Filters": [ {"Pattern": '{"body": {"status": ["active"]}}'}, ], }, }, ) sqs_client.send_message( QueueUrl=src_url, MessageBody='{"status": "active", "id": "match-1"}', ) sqs_client.send_message( QueueUrl=src_url, MessageBody='{"status": "inactive", "id": "no-match"}', ) found = False for _ in range(15): time.sleep(1) resp = sqs_client.receive_message( QueueUrl=tgt_url, MaxNumberOfMessages=10, WaitTimeSeconds=1 ) msgs = resp.get("Messages", []) if any("match-1" in m["Body"] for m in msgs): assert not any("no-match" in m["Body"] for m in msgs), \ "Non-matching message should not be forwarded" found = True break assert found, "Target queue should receive matching message" attrs = sqs_client.get_queue_attributes( QueueUrl=src_url, AttributeNames=["ApproximateNumberOfMessages"] ) assert attrs["Attributes"]["ApproximateNumberOfMessages"] == "0" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=src_url) sqs_client.delete_queue(QueueUrl=tgt_url) def test_batch_size_in_source_parameters(self, pipes_client, sqs_client, unique_name): """Test that BatchSize in SourceParameters is respected.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" src_resp = sqs_client.create_queue(QueueName=src_name) src_url = src_resp["QueueUrl"] tgt_resp = sqs_client.create_queue(QueueName=tgt_name) tgt_url = tgt_resp["QueueUrl"] try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="RUNNING", SourceParameters={ "SqsQueueParameters": { "BatchSize": 1, }, }, ) for i in range(1, 4): sqs_client.send_message(QueueUrl=src_url, MessageBody=f"batch-msg-{i}") found_messages = set() for _ in range(20): if len(found_messages) >= 3: break time.sleep(1) resp = sqs_client.receive_message( QueueUrl=tgt_url, MaxNumberOfMessages=10, WaitTimeSeconds=1 ) for msg in resp.get("Messages", []): for j in range(1, 4): if f"batch-msg-{j}" in msg["Body"]: found_messages.add(f"batch-msg-{j}") assert len(found_messages) == 3, "All 3 messages should arrive at target" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=src_url) sqs_client.delete_queue(QueueUrl=tgt_url) def test_stopped_pipe_does_not_forward(self, pipes_client, sqs_client, unique_name): """Test that a STOPPED pipe does not forward messages.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" src_resp = sqs_client.create_queue(QueueName=src_name) src_url = src_resp["QueueUrl"] tgt_resp = sqs_client.create_queue(QueueName=tgt_name) tgt_url = tgt_resp["QueueUrl"] try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="STOPPED", ) sqs_client.send_message(QueueUrl=src_url, MessageBody="should not forward") time.sleep(3) attrs = sqs_client.get_queue_attributes( QueueUrl=src_url, AttributeNames=["ApproximateNumberOfMessages"] ) assert attrs["Attributes"]["ApproximateNumberOfMessages"] == "1" resp = sqs_client.receive_message( QueueUrl=tgt_url, MaxNumberOfMessages=1, WaitTimeSeconds=1 ) assert len(resp.get("Messages", [])) == 0 finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=src_url) sqs_client.delete_queue(QueueUrl=tgt_url) def test_source_queue_drained_after_forwarding( self, pipes_client, sqs_client, unique_name ): """Test that source queue messages are deleted after forwarding.""" src_name = f"pipe-src-{unique_name}" tgt_name = f"pipe-tgt-{unique_name}" pipe_name = f"pipe-{unique_name}" src_resp = sqs_client.create_queue(QueueName=src_name) src_url = src_resp["QueueUrl"] tgt_resp = sqs_client.create_queue(QueueName=tgt_name) tgt_url = tgt_resp["QueueUrl"] try: pipes_client.create_pipe( Name=pipe_name, Source=sqs_arn(src_name), Target=sqs_arn(tgt_name), RoleArn=ROLE_ARN, DesiredState="RUNNING", ) sqs_client.send_message(QueueUrl=src_url, MessageBody="drain test") drained = False for _ in range(15): time.sleep(1) attrs = sqs_client.get_queue_attributes( QueueUrl=src_url, AttributeNames=["ApproximateNumberOfMessages"] ) if attrs["Attributes"]["ApproximateNumberOfMessages"] == "0": drained = True break assert drained, "Source queue should be drained after pipe forwards messages" finally: pipes_client.delete_pipe(Name=pipe_name) sqs_client.delete_queue(QueueUrl=src_url) sqs_client.delete_queue(QueueUrl=tgt_url) def _queue_url(sqs_client, queue_name): """Get queue URL by name, ignoring errors.""" try: return sqs_client.get_queue_url(QueueName=queue_name)["QueueUrl"] except Exception: return ""