Azure / Azure/azure-sdk-for-cpp
Add an offline test seam for AMQP-backed send and receive paths in Event Hubs
- Dominant language
- C++
- Stars
- 205
- Forks
- 172
- Avg merge
- 1d 15m
- Merged PRs (30d)
- 33
Description
## Summary
Neither the send path nor the receive path in Event Hubs can be tested offline. Both reach a concrete AMQP type that only a live link can produce, so their behavior is unverified in CI. Deleting the whole tracing span block from `PartitionClient::ReceiveEvents` leaves the playback suite green, and it passed 106 of 106 when that was measured.
## Motivation
`PartitionClient`'s constructor is private and friended only to `_detail::PartitionClientFactory` at `sdk/eventhubs/azure-messaging-eventhubs/inc/azure/messaging/eventhubs/partition_client.hpp:92`, and `CreatePartitionClient` opens a live link at `sdk/eventhubs/azure-messaging-eventhubs/src/partition_client.cpp:195`. The send path fails the same way for a different reason. `ProducerClient::CreateBatch` calls `EnsureSender`, which opens the connection, the session, and the link, so an offline send throws `std::runtime_error` with the message "Could not open Claims Based Security object." before it reaches any send logic.
The consequence reaches past tracing. On the receive side the returned-vector message count, the cancellation path, and the exception recording in `ReceiveEvents` are covered only by `_LIVEONLY_` tests, which CI skips in playback. On the send side the constraint that one logical `Send` produces exactly one span cannot be checked offline at all, because `CreateBatch` always throws first, so a correct implementation and one that nests a second span produce identical results in every offline test.
`azure-core-amqp` already solves this problem for itself. It has a mock AMQP server at `sdk/core/azure-core-amqp/test/ut/mock_amqp_server.hpp`, and both concrete types already declare its mocks as friends. `MessageReceiver` friends `MockServiceEndpoint` at `sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_receiver.hpp:194`, and `MessageSender` friends `AmqpServerMock`, `MockServiceEndpoint`, and `MessageListenerEvents` at `sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_sender.hpp:202-204`. No package outside `azure-core-amqp` can use any of it today.
## Proposal
- Publish the existing mock AMQP server as a test-only CMake target that other packages link, rather than writing a second mock. The header already exists and the friend declarations are already in place for both the sender and the receiver.
- Give `azure-messaging-eventhubs` a test-only route to build a `PartitionClient` and to reach a `ProducerClient` sender against that mock, either through a friended test factory or through an existing test hook pattern in this repo.
- Port `ConsumerClientTest.ReceiveEventsSpan_LIVEONLY_` and `ProducerClientTest.SendEventSpan_LIVEONLY_` to run offline once the seam lands, so the span name, the span kind, the attribute set, and the message count are checked in CI rather than only against a real namespace.
- Pin the one-span-per-logical-send constraint offline. Each public `ProducerClient::Send` overload must produce exactly one span, and today no test that CI runs can tell a correct implementation from one that nests a second span.
- Cover the cancellation path in the same pass. `ReceiveEvents` returns an empty vector when the caller's context is already cancelled, and the span records no error, so a cancelled receive is indistinguishable from an empty one in a trace.
## Validation
- The receive gap is measured rather than assumed. Removing the span block from `ReceiveEvents` and rebuilding leaves the playback suite passing.
- The send gap is measured rather than assumed. A probe against a producer built from a fake connection string throws from `EnsureSender` and records zero spans, so no offline test reaches the send logic.
- Every test that calls `ReceiveEvents` or that exercises a real send is `_LIVEONLY_`, so none of them runs in playback.
Contributor guide
Assessment
This issue has not been assessed yet.