test(quality): add contract, integration, property, fuzz, and security tests#35
Conversation
- Enable cargo-semver-checks in lint workflow - Add InMemoryProvider contract tests (27 tests, no Docker) covering all major assertions from docs/spec/assertions.md - Add NATS JetStream integration tests (8 tests, testcontainers 0.23) - Add RabbitMQ integration tests (9 tests, testcontainers 0.23) - Add proptest property tests for QueueName, SessionId, MessageId - Fix secret redaction: AwsSqsConfig and AzureServiceBusConfig now use custom Debug impls that emit REDACTED for secret_access_key and connection_string; add security tests confirming the redaction - Fill error.rs coverage gaps (11 previously untested variants + retry_after) - Add fuzz targets for QueueName::new and SessionId::new - Add CI jobs for contract tests, integration tests (matrix), and fuzz - Fix pre-existing assert!(true) clippy warnings in aws_tests.rs
|
Claude finished @pvandervelde's task in 3m 35s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Overall AssessmentThis is a solid, well-structured testing PR that significantly increases confidence in the library's correctness. The test architecture is clean: contract tests drive the Security🔴
|
| Area | Finding | Severity |
|---|---|---|
| Security | AzureServiceBusConfig::connection_string serializable as plaintext |
Medium |
| Empty tests | test_canonical_request_format, test_message_deduplication_id_generation |
Low |
| Contract test | Weak batch-receive assertion allows returning fewer messages than requested | Low |
| Integration | Per-test container startup (slow CI, compounds over time) | Low |
| Integration | RabbitMQ DLX disabled → dead_letter_message untested against real broker |
Low |
| Integration | NATS abandon test missing delivery count assertion | Low |
| CI | cargo-semver-checks-action@v2 not pinned to SHA |
Low |
| Fuzz | Minor: redundant call for invalid UTF-8 inputs | Nit |
The security serialization gap for Azure config is the only item I'd consider blocking — everything else is polish. The overall test architecture is well-designed and follows the patterns in docs/standards/testing.md.
Security: - AzureServiceBusConfig::connection_string now has #[serde(skip_serializing)] to prevent secrets leaking through JSON/TOML serialization (spec Assertion 17) - Add azure_config_serde_does_not_serialize_connection_string test CI fixes: - Remove --all-features from cargo llvm-cov in the test job; the only feature is integration-tests which gates Docker-dependent binaries that have their own dedicated CI job and must not run during coverage collection - Pin cargo-semver-checks-action to SHA 7076d3b5...ec7e3a37 (v2.6) for supply-chain hygiene, consistent with all other pinned actions in the file - Add baseline-rev: master so semver checks work before crate is published to crates.io Test quality: - Tighten receive_messages_respects_max_count assertion from <= 4 to == 4 so it enforces the provider honours the requested count, not just a ceiling - Add delivery_count == 2 assertion to nats_abandon_requeues_message - Remove two empty aws_tests bodies (test_canonical_request_format, test_message_deduplication_id_generation) that gave false coverage confidence - Fix session_id property-test strategy: remove space from base regex to avoid confusing readers; values with spaces are handled by the control-char and non-ASCII test cases - Add comment in queue_name_new fuzz target explaining the lossy path always rejects non-UTF-8 input (U+FFFD replacement) and why we still call it
|
Claude finished @pvandervelde's task in 3m 4s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Overall AssessmentThe second commit ( What Was Fixed (commit
|
| Area | Finding | Status |
|---|---|---|
| Security — Azure serde | connection_string serializable as plaintext |
✅ Fixed (#[serde(skip_serializing)] + test) |
| Security — Debug redaction | Both Debug impls correct |
✅ Confirmed |
| Contract tests | Weak batch-receive assertion | ✅ Fixed (exact == 4 assertion) |
| Contract tests | Spec assertion coverage | ✅ Confirmed (Assertions 1, 3–7, 9, 13–14, 18, 20) |
| Integration — NATS | Missing delivery count assertion on abandon | ✅ Fixed |
| Integration — RabbitMQ | DLX disabled, no real-broker dead-letter test | |
| Integration — both | Per-test container startup overhead | |
| Empty tests | assert!(true, …) placeholder bodies |
✅ Removed |
| Property tests | SessionId strategy included space | ✅ Fixed |
| Fuzz targets | Redundant lossy call lacked explanation | ✅ Fixed (comment added) |
| CI — semver action | Not SHA-pinned | ✅ Fixed (v2.6 SHA + baseline-rev: master) |
| CI — coverage | --all-features triggered Docker containers |
✅ Fixed (removed with comment) |
All blocking issues are resolved. The two remaining items (per-test container cost, RabbitMQ DLX gap) are pre-existing constraints documented in code comments and do not affect correctness. This PR is in good shape to merge.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #35 +/- ##
==========================================
+ Coverage 50.18% 51.05% +0.86%
==========================================
Files 10 10
Lines 3553 3612 +59
Branches 3553 3612 +59
==========================================
+ Hits 1783 1844 +61
+ Misses 1720 1718 -2
Partials 50 50 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…startup semver-checks (ci.log): - Replace fabricated SHA with the real v2 tag SHA (6b69fcf40e9b5fb17adeb57e4b6ecd020649a239) obtained via git ls-remote cargo-deny advisories (cargo-deny.log): - rustls-webpki 0.103.12 RUSTSEC-2026-0104: already resolved via Cargo.lock update to 0.103.13 (cargo update -p rustls-webpki) - rand 0.7.3 RUSTSEC-2026-0097: pinned by azure_core -> http-types; unsoundness only triggers with a custom logger calling rand::rng() during reseeding, which this crate does not do. Documented in deny.toml ignore list. - rustls-pemfile RUSTSEC-2025-0134: transitive dev-dep via testcontainers/bollard; no safe upgrade. Documented in deny.toml ignore list. - tokio-tar RUSTSEC-2025-0111: transitive dev-dep via testcontainers/bollard; no safe upgrade. Documented in deny.toml ignore list. integration tests: - Share a single Docker container per integration test binary instead of starting and stopping one per test function (17 lifecycles -> 2) - Use std::sync::OnceLock + a dedicated single-threaded Tokio runtime for async container startup; the ContainerAsync handle is leaked intentionally to keep the broker alive for the whole process lifetime - Each test still uses a unique queue name so there is no state sharing
|
Claude finished @pvandervelde's task in 6m 43s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Area | Finding | Status |
|---|---|---|
| Integration tests | Duplicate function definitions — compilation error | ✅ Fixed in commit 32fd38a |
CI — semver-checks |
Shallow checkout cannot resolve master ref |
🔴 Needs manual fetch-depth: 0 fix in lint.yml |
deny.toml advisories |
Three new RUSTSEC ignores, all well-justified | ✅ |
semver-checks SHA |
Corrected from fabricated to real v2 SHA | ✅ |
| Container startup overhead | 17 lifecycle → 2 via OnceLock |
✅ Addressed (once compilation fix is in) |
cargo-deny, rustfmt, clippy |
All passing | ✅ |
The only remaining blocker before merge is the fetch-depth: 0 change in lint.yml. The RabbitMQ DLX integration test gap (flagged in previous reviews) remains a known non-blocking item documented in the code.
…st files The shared-container refactoring in the previous commit prepended new shared-startup code and test functions but left the original per-test container implementations in place, producing duplicate definitions for every helper function and test in both files. Rust does not allow duplicate item names in the same module, so the integration test binary would not compile. Remove the stale per-test-container section (helpers + tests) from the bottom of each file; the shared-container versions at the top are the correct implementations. Co-authored-by: Patrick van der Velde <undefined@users.noreply.github.com>
…kout RabbitMQ/NATS integration tests (ci.log): - Replace std::sync::OnceLock + block_on with tokio::sync::OnceCell and async initialization. The previous approach panicked with "Cannot start a runtime from within a runtime" because #[tokio::test] already drives a runtime on the calling thread; block_on cannot be nested inside an existing Tokio context. tokio::sync::OnceCell::get_or_init accepts an async closure and resolves within the existing runtime, eliminating the panic. - nats_port() and rabbitmq_port() are now async fn; all call sites updated to nats_port().await / rabbitmq_port().await - Remove unused SessionProvider imports surfaced by the recompile semver-checks (cargo-deny.log / pr-comments.md): - Add fetch-depth: 0 to the actions/checkout step in verify-semver so the full git history is available and --baseline-rev master can resolve the ref
|
Claude finished @pvandervelde's task in 2m 55s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Area | Finding | Status |
|---|---|---|
| Runtime panic | OnceLock + block_on nested runtime panic |
✅ Fixed (tokio::sync::OnceCell) |
CI — semver-checks |
Shallow checkout couldn't resolve master ref |
✅ Fixed (fetch-depth: 0) |
| RabbitMQ abandon | Missing delivery_count == 2 assertion |
|
| RabbitMQ batch receive | Weak <= 4 assertion vs exact == 4 in contract test |
|
| RabbitMQ DLX | No real-broker dead-letter test |
The two blockers from the previous review are resolved. The two remaining items are minor and do not affect correctness of the existing tests — they're coverage gaps. The PR is otherwise clean and ready to merge once the author decides whether to address those two gaps.
NATS integration test failures (integration-tests.log): - nats_abandon_requeues_message and nats_complete_removes_message failed with JetStream error 10100 "filtered consumer not unique on workqueue stream". WorkQueue retention streams allow only one active consumer per filter subject; when cargo test runs all integration tests concurrently, multiple NatsProvider instances may create consumers simultaneously on the same stream, triggering the constraint. - Fix: pass -- --test-threads=1 to the integration_tests CI step so all integration tests run serially within a single binary invocation. This eliminates concurrent consumer creation while the shared container still avoids the per-test Docker overhead. RabbitMQ PR review comments (pr-comments.md): - rabbitmq_abandon_requeues_message: add delivery_count == 2 assertion to match the NATS and contract-test equivalents - rabbitmq_receive_messages_respects_max_count: tighten assertion from <= 4 to == 4, consistent with the contract test and NATS equivalent
|
Claude finished @pvandervelde's task in 1m 34s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Item | Status |
|---|---|
RabbitMQ DLX — enable_dead_letter: false, no real-broker dead-letter test |
tests/rabbitmq_integration.rs:79), accepted as follow-up |
TESTCONTAINERS_RYUK_DISABLED: "true" — cleanup on panic depends on Drop |
Overall Summary
All issues raised across four review rounds are now resolved:
| Area | Finding | Status |
|---|---|---|
| Security — Azure serde | connection_string serializable as plaintext |
✅ Fixed |
| Security — Debug redaction | Both Debug impls + tests correct |
✅ Confirmed |
| Contract tests | Weak batch-receive assertion | ✅ Fixed |
| Integration — NATS | Missing delivery_count assertion on abandon |
✅ Fixed |
| Integration — NATS | JetStream consumer concurrency conflict | ✅ Fixed (--test-threads=1) |
| Integration — RabbitMQ | Missing delivery_count == 2 assertion |
✅ Fixed |
| Integration — RabbitMQ | Weak <= 4 batch-receive assertion |
✅ Fixed |
| Integration — RabbitMQ | DLX not tested against real broker | |
| Empty test bodies | assert!(true, …) placeholders |
✅ Removed |
| Property tests | SessionId strategy included space |
✅ Fixed |
| Fuzz targets | Redundant lossy-path call unexplained | ✅ Comment added |
| CI — semver action | Not SHA-pinned | ✅ Fixed (v2.6 SHA) |
| CI — semver checkout | Shallow fetch couldn't resolve master |
✅ Fixed (fetch-depth: 0) |
| CI — coverage | --all-features spun up Docker in coverage job |
✅ Fixed |
| Integration startup overhead | 17 container lifecycles → 2 via OnceCell |
✅ Resolved |
All blocking issues are closed. The one remaining gap (RabbitMQ DLX not tested against a real broker) is a known limitation documented in the code and does not affect correctness of the existing tests. This PR is ready to merge.
…consumer conflict semver-checks (ci.log from 47d2942): - Change baseline-rev from master to origin/master. In a PR checkout the workspace is in detached-HEAD mode; the local "master" branch does not exist so git cannot resolve "master^{tree}". The remote-tracking ref origin/master is always present after the full-depth fetch and resolves correctly. NATS integration test failures (integration-tests.log from 47d2942): - --test-threads=1 fixed the inter-test consumer conflict but the two failing tests each call receive_message twice on the same stream within a single test. Each call creates a new ephemeral pull consumer; the server keeps the previous consumer alive until its inactive_threshold expires (NATS default: 5 s). A second consumer with an overlapping filter subject on a WorkQueue stream is rejected with JetStream error 10100. - Fix: add a 6-second sleep between the first and second receive_message in nats_complete_removes_message and nats_abandon_requeues_message so the server has time to garbage-collect the first consumer. The root cause is that NatsProvider creates a new ephemeral consumer per receive_message call rather than reusing a durable one; a provider-level fix would be the permanent solution, but that is out of scope for the test layer.
|
Claude finished @pvandervelde's task in 3m 6s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Item | Status |
|---|---|
RabbitMQ DLX — enable_dead_letter: false, no real-broker dead-letter test |
tests/rabbitmq_integration.rs:79), accepted as follow-up |
TESTCONTAINERS_RYUK_DISABLED: "true" — cleanup on panic depends on Drop |
Overall Summary
All issues raised across five review rounds are now resolved. The only new observation is the tight 1-second sleep margin, which is a flakiness risk worth addressing before this PR accumulates more integration tests that add noise to CI.
| Area | Finding | Status |
|---|---|---|
| Security — Azure serde | connection_string serializable as plaintext |
✅ Fixed |
| Security — Debug redaction | Both Debug impls + tests correct |
✅ Confirmed |
| Contract tests | Weak batch-receive assertion | ✅ Fixed |
| Integration — NATS | Missing delivery_count assertion on abandon |
✅ Fixed |
| Integration — NATS | JetStream consumer concurrency conflict (--test-threads=1) |
✅ Fixed |
| Integration — NATS | Intra-test consumer conflict (6 s sleep) | ✅ Fixed ( |
| Integration — RabbitMQ | Missing delivery_count == 2 assertion |
✅ Fixed |
| Integration — RabbitMQ | Weak <= 4 batch-receive assertion |
✅ Fixed |
| Integration — RabbitMQ | DLX not tested against real broker | |
| Empty test bodies | assert!(true, …) placeholders |
✅ Removed |
| Property tests | SessionId strategy included space |
✅ Fixed |
| Fuzz targets | Redundant lossy-path call unexplained | ✅ Comment added |
| CI — semver action | Not SHA-pinned | ✅ Fixed (v2.6 SHA) |
| CI — semver checkout | Shallow fetch couldn't resolve master |
✅ Fixed (fetch-depth: 0) |
| CI — semver baseline-rev | master → origin/master for detached-HEAD PR checkout |
✅ Fixed |
| CI — coverage | --all-features spun up Docker in coverage job |
✅ Fixed |
| Integration startup overhead | 17 container lifecycles → 2 via OnceCell |
✅ Fixed |
All blocking issues are resolved. The 1-second sleep margin is a low-severity flakiness risk worth a follow-up, but does not block merging.
RabbitMQ Classic Queues do not set the x-delivery-count header on nack+requeue — only the AMQP redelivered boolean in the delivery frame is set. Extract it in register_delivery / register_session_delivery and pass it to extract_delivery_count so that a redelivered message without the x-delivery-count header returns 2 rather than 1. Fixes the rabbitmq_abandon_requeues_message integration test assertion.
|
Claude finished @pvandervelde's task in 2m 29s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Item | Status |
|---|---|
RabbitMQ DLX — enable_dead_letter: false, no real-broker dead-letter test |
tests/rabbitmq_integration.rs:79), accepted as follow-up |
NATS sleep margin (6 s with 1 s buffer over 5 s inactive_threshold) |
Overall Summary
All five review rounds of blocking issues remain resolved. This commit correctly fixes a real protocol-level behaviour difference between RabbitMQ Classic Queues and Quorum Queues: the redelivered boolean is the only delivery-tracking signal Classic Queues set on nack+requeue.
| Area | Finding | Status |
|---|---|---|
RabbitMQ delivery_count — first nack |
Returned 1 instead of 2 for redelivered Classic Queue messages | ✅ Fixed |
| All three delivery paths | register_delivery, batch loop, register_session_delivery |
✅ All updated |
| Unit tests — new paths | First delivery and redelivered fallbacks | ✅ Added |
| Unit tests — Quorum Queue scenario | Header present AND redelivered == true |
|
| Spec compliance — multi-nack on Classic Queues | delivery_count capped at 2 indefinitely |
|
| RabbitMQ DLX | No real-broker dead-letter test | |
| NATS sleep margin | Tight 1 s buffer |
The missing Quorum Queue test and the spec documentation note are both low-effort improvements. The PR is otherwise in good shape.
Ephemeral pull consumers reset the NATS JetStream num_delivered counter on every receive_message call, so delivery_count was always 1 after an abandon+redeliver cycle (nats_abandon_requeues_message failure). Replace create_consumer (ephemeral) with get_or_create_consumer using a stable durable name derived from the queue name (consumer_name helper). With a persistent named consumer the server tracks num_delivered across successive fetches, so Info::delivered correctly increments to 2 after one abandon. Side effects: - The 6 s sleep between receive_message calls is no longer needed and has been removed from nats_complete_removes_message and nats_abandon_requeues_message, cutting test runtime by ~12 s. - The inactive_threshold/JetStream-10100 error cannot occur because successive calls share the same server-side consumer rather than racing on overlapping ephemeral consumers. Additional changes in this commit: - ci.yml: add fail-fast: false to the integration_tests matrix so that a NATS failure no longer auto-cancels the RabbitMQ job (and vice versa) - rabbitmq_tests.rs: add test_delivery_count_header_takes_precedence_over_redelivered_flag to cover the Quorum Queue scenario (both x-delivery-count and redelivered set) - docs/spec/providers.md: document the Classic Queue delivery_count limitation (count saturates at 2 after the first abandon)
|
Claude finished @pvandervelde's task in 3m 26s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commits
|
| Caller | filter_subject |
|---|---|
receive_message (line 644) |
{prefix}.{queue} |
receive_messages (line 686) |
{prefix}.{queue} |
create_session_client (line 910) |
{prefix}.{queue}.session.{session_id} |
The NATS server enforces config consistency on named durable consumers. If receive_message is called on a queue first, it creates {prefix}-{queue}-consumer with filter_subject = "{prefix}.{queue}". When create_session_client is later called on the same queue, get_or_create_consumer submits the same name with filter_subject = "{prefix}.{queue}.session.{sid}". The server returns an error (consumer already exists with different config) or — depending on the async-nats version — silently returns the existing consumer with the wrong filter (causing the session provider to receive all queue messages instead of only session messages).
The reverse is also true: if a session client is created first and a regular receive follows, the regular consumer gets the session filter and misses all non-session messages.
A third scenario: two concurrent sessions (A and B) on the same queue both try to get_or_create_consumer("{prefix}-{queue}-consumer") with different session subjects. One will fail or get the other's filter.
Why the integration tests don't catch this: every test in tests/nats_integration.rs uses a unique queue name (e.g., nats-session-fifo, nats-receive). Because the consumer name is scoped to the queue, there is no cross-test consumer collision — but in production, a single queue commonly serves both regular receives and session-based receives.
Fix: The session consumer name must incorporate the session ID (or the filter subject). The simplest safe fix:
fn session_consumer_name(config: &NatsConfig, queue: &QueueName, session_id: &SessionId) -> String {
let safe_sid = session_id.as_str().replace(['-', '/', ' '], "_");
format!(
"{}-{}-session-{}-consumer",
nats_safe(&config.stream_prefix),
nats_safe(queue.as_str()),
safe_sid
)
}Then create_session_client passes session_consumer_name(...) instead of consumer_name(...) to get_or_create_consumer. Regular receive_message and receive_messages keep using consumer_name(...) as today.
⚠️ Stale doc comment on create_consumer
File: src/providers/nats.rs line 407
/// Create an ephemeral pull consumer for the given subject filter.
async fn create_consumer(The method now creates (or retrieves) a named durable consumer. The doc comment still says "ephemeral". This should be updated to avoid confusing future readers.
Pre-existing non-blocking items (unchanged)
| Item | Status |
|---|---|
RabbitMQ DLX — enable_dead_letter: false, no real-broker dead-letter test |
tests/rabbitmq_integration.rs:79), accepted as follow-up |
TESTCONTAINERS_RYUK_DISABLED: "true" — cleanup on panic depends on Drop |
Summary
| Area | Finding | Status |
|---|---|---|
Formatting fix (291e90a) |
rustfmt expansion of if/else |
✅ Clean |
| RabbitMQ Quorum Queue test | test_delivery_count_header_takes_precedence_over_redelivered_flag |
✅ Added |
| Classic Queue limitation | delivery_count saturation documented in providers.md |
✅ Added |
CI fail-fast: false |
NATS failure no longer cancels RabbitMQ job | ✅ Correct |
| 6 s sleep removed | No longer needed with durable consumer | ✅ Correct |
| NATS session consumer name | Same name as queue consumer → config conflict on mixed use | 🔴 Bug |
create_consumer doc comment |
Still says "ephemeral" |
The session/queue consumer name collision is the only new blocking item. The fix is a small addition of a session_consumer_name helper and updating the one call site in create_session_client.
create_consumer used a single queue-scoped name for all consumers on a
queue regardless of their filter_subject. NATS enforces config
consistency on named durable consumers: reusing the same name with a
different filter causes the server to reject the create call or return
the existing consumer with the wrong filter.
Consequences of the bug:
- receive_message followed by create_session_client on the same queue
would collide: the session consumer would inherit the whole-queue
filter and deliver non-session messages to session callers.
- Two concurrent sessions on the same queue would race to own the
single consumer name and one would get the other's filter.
Fix: add session_consumer_name() which embeds the session ID in the
durable name ({prefix}-{queue}-session-{sid}-consumer). Pass the
consumer name explicitly to create_consumer so queue-level and session-
level consumers are always distinct server-side objects.
Also fix the stale doc comment on create_consumer (previously said
'ephemeral'; now accurately describes named durable consumers).
|
Claude finished @pvandervelde's task in 3m 32s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Area | Finding | Status |
|---|---|---|
| Core fix — consumer name collision | session_consumer_name() with session ID embedded |
✅ Correct |
create_consumer API |
Explicit name parameter; doc comment updated |
✅ Correct |
Unit tests for session_consumer_name |
None — no format or distinctness assertions | |
| Integration test for mixed queue+session | No regression test for the fixed scenario | |
| Session ID sanitization (pre-existing) | ., *, > survive into consumer name / subject |
|
| Durable consumer lifecycle (pre-existing) | No inactive_threshold, no cleanup on drop |
|
| CI — lint | Passing | ✅ |
| CI — tests | In progress | ⏳ |
The fix is architecturally correct and closes the blocking bug. The two new items (missing unit tests for session_consumer_name, missing regression integration test) are low-effort and worth addressing before merge. The pre-existing sanitization and lifecycle gaps are separate concerns that should be tracked as follow-up issues.
…ve_threshold; add tests Four issues from PR review addressed: 1. Incomplete session ID sanitisation Replace() only covered '-', '/', ' ' leaving dots and other NATS-invalid characters (e.g. '*', '>') in subject and consumer names. Add nats_safe_session_id() which allows only ASCII alphanumeric and '_', replacing everything else. Both session_subject() and session_consumer_name() now use this helper. 2. Named durable consumers accumulate without expiry ConsumerConfig now sets inactive_threshold to 2x session_lock_duration so the NATS server automatically removes idle consumers. This prevents unbounded consumer accumulation for applications with many short-lived sessions. 3. Missing unit tests for session_consumer_name Add test_session_consumer_name_format, _distinct_from_queue_consumer, _different_sessions_different_consumer_names, and _sanitises_dots_and_special_chars. 4. Missing integration test for queue/session coexistence Add nats_queue_and_session_consumers_coexist_on_same_queue which sends a regular message and a session-tagged message to the same queue, then verifies that each consumer type receives exactly its own message.
|
Claude finished @pvandervelde's task in 2m 30s —— View job PR Review: test(quality): add contract, integration, property, fuzz, and security tests
Review of commit
|
| Item | Status |
|---|---|
RabbitMQ DLX — enable_dead_letter: false, no real-broker dead-letter test |
tests/rabbitmq_integration.rs:79), accepted as follow-up |
nats_safe doesn't sanitize dots in stream prefix or queue names |
|
TESTCONTAINERS_RYUK_DISABLED: "true" — cleanup on panic depends on Drop |
Summary
| Area | Finding | Status |
|---|---|---|
| Session ID sanitization | ., *, >, /, etc. survived into subject/consumer names |
✅ Fixed (nats_safe_session_id helper) |
| Consumer accumulation | Named durable consumers never expired | ✅ Fixed (inactive_threshold = 2x session_lock_duration) |
inactive_threshold semantic |
Queue-level consumers use session lifetime as expiry basis — resets delivery_count after idle > 10 min | |
Unit tests for session_consumer_name |
None | ✅ Fixed (4 tests added) |
| Integration regression test | Queue + session on same queue not tested end-to-end | ✅ Fixed (nats_queue_and_session_consumers_coexist_on_same_queue) |
| CI | All 5 jobs in progress | ⏳ |
| RabbitMQ DLX | No real-broker dead-letter test |
All items from the previous six review rounds are resolved. The inactive_threshold semantic mismatch for queue-level consumers is the only new concern — it's low-severity now (with the 10-minute default) but could become a correctness issue if session_lock_duration is configured short (e.g., 1 minute), or in any service with idle periods longer than 2 * session_lock_duration.
Adds a comprehensive test layer across contract, integration, property-based, fuzz, and security dimensions to increase confidence in the library's correctness and operational safety.
What Changed
tests/contract_tests.rs): 27 tests againstInMemoryProviderverifying every major assertion fromdocs/spec/assertions.md— payload integrity, receipt semantics, abandon/re-queue, dead-letter, session FIFO ordering, exclusive session locks, and concurrent sends. No Docker required.tests/nats_integration.rs): 8 tests using testcontainers 0.23 against a real NATS JetStream container (send/receive, attribute round-trips, session ordering, batch sends).tests/rabbitmq_integration.rs): 9 tests using testcontainers 0.23 against a real RabbitMQ container (same coverage, plus batch receive max-count).src/message_tests.rs): 9 proptest cases forQueueName,SessionId, andMessageIdcovering boundary lengths, illegal characters, and uniqueness.fuzz/fuzz_targets/): Two libFuzzer harnesses forQueueName::newandSessionId::new.src/provider.rs):AzureServiceBusConfigandAwsSqsConfignow have customfmt::Debugimpls that emit<REDACTED>forconnection_stringandsecret_access_keyrespectively, preventing secret leakage in logs and panic output.Debugoutput.src/error_tests.rs): 11 previously untestedQueueErrorvariants now have transience andretry_afterassertions..github/workflows/ci.yml): Newcontract_tests,integration_tests(NATS/RabbitMQ matrix withTESTCONTAINERS_RYUK_DISABLED), andfuzz_tests(nightly, 60 s campaign) jobs..github/workflows/lint.yml):cargo-semver-checks-action@v2job activated.Why
The library had unit tests for individual types but no end-to-end behavioral verification against the published spec, no validation against real broker implementations, no fuzz hardening of input-validation paths, and two config structs that leaked secrets through their
Debugrepresentations.How
InMemoryProviderdirectly via the publicQueueProvider/SessionProvidertraits, keeping them fast and dependency-free.integration-testsfeature flag (--features integration-tests), so they are opt-in locally and isolated to a dedicated CI matrix job.fuzz/as a standalone cargo workspace (standardcargo-fuzzconvention) so it does not affect normalcargo build/cargo testinvocations.fmt::Debugwhile keeping#[derive(Clone, Serialize, Deserialize)]intact, so serialisation and cloning behaviour is unchanged.Testing Evidence
cargo fuzzcargo clippy --all-targets -- -D warningsclean;cargo fmtcleancargo test --no-run --features integration-testsReviewer Guidance
Debugimpls insrc/provider.rs— confirm the redaction pattern is consistent with howAzureAuthMethod::ClientSecretalready redacts its secret (the existing prior art inproviders/azure.rs).-jsflag to enable JetStream; confirm thewith_cmdcall intests/nats_integration.rsmatches what the project's NATS provider requires.--features integration-tests; the CI matrix passes this flag but localcargo testdoes not run them by default — verify this is the intended developer experience.fuzz/Cargo.tomlhas its own[workspace]entry to prevent adoption by the root workspace. No changes to rootCargo.tomlworkspace membership are needed.cargo-semver-checks-action@v2requires the crate to be published to crates.io to produce a baseline. Until the crate is first published, the job will produce an informational "no published version found" result rather than an error.