Fix test infrastructure, pamqp 4 compatibility, and runtime bugs#152
Conversation
Adds 97 new tests (296 total, up from 199) targeting previously untested critical modules: tests/test_channel0.py (new): - Full happy-path AMQP handshake: ProtocolHeader → Start/StartOk → Tune/TuneOk → Open/OpenOk; verifies every frame is sent and stored - Heartbeat negotiation edge cases: server/client zero, both zero, server smaller value - Negotiation error paths: wrong frame type at each step, pre-injected exception, EXCEPTION_RAISED event set on failure - Runtime frames: Connection.Blocked/Unblocked events, server-initiated Connection.Close with AMQP reply-code mapping - Channel0.send_heartbeat and close() behaviour tests/test_connection.py (new): - Connection.__init__ and URL parsing - connect() happy path, socket timeout, socket exception, negotiation timeout, negotiation exception propagation, heartbeat start/skip - channel() method: return type, first/second ID, channels dict, IO registration, raises when closed - _get_next_channel_id(): first id=1, increment, reclaim closed id, remove closed from dict, TooManyChannelsError (#121) - capabilities/server_properties properties, blocked flag tests/test_heartbeat.py (new): - Start/stop lifecycle, zero-interval disabled path, double-stop safety - _maybe_send(): sends when no data written, skips when data written, skips when stopped, updates last_written, restarts timer tests/integration_tests.py (additions): - MultipleChannelsTest: two channels have different IDs; closed channel ID is reused; independent queues on separate channels - NackWithoutRequeueTest: nacked message is discarded not requeued - QueuePurgeTest: purge empties a 5-message queue - LargeMessageTest: 300 kB body round-trips correctly (multi-frame) - ChannelContextManagerTest: channel closes on exit, new channel opens - TransactionTest: committed message visible, rolled-back message absent Coverage improvements: - channel0.py: 65% → 94% - connection.py: 57% → 88% - heartbeat.py: 61% → 100% - Overall: 62% → 68% Co-Authored-By: Gavin M. Roy <gavinmroy@gmail.com>
This commit fixes a series of runtime failures discovered when the
integration test suite was properly enabled, all caused by API differences
between pamqp 3 (which the restored code was written for) and pamqp 4:
rabbitpy/base.py:
- Replace commands.Frame (removed in pamqp 4) with pamqp.base.Frame in
_validate_frame_type() — was silently falling through, causing
_wait_on_frame() to loop forever instead of matching the expected frame
- Pass explicit class_id=0, method_id=0 to Channel.Close in
_build_close_frame() — pamqp 4 encodes None fields as short_uint and
raises TypeError on None
rabbitpy/channel.py:
- Replace properties.to_dict() (removed in pamqp 4) with a dict
comprehension over properties.attributes() in _create_message()
rabbitpy/channel0.py:
- Add _raise_if_server_close() helper called at each _wait_for_frame()
step in _negotiate(); ensures auth failures (ACCESS_REFUSED/403) raise
AMQPAccessRefused rather than a generic ConnectionException
rabbitpy/connection.py:
- Restore auto-connect in __init__() to match the original rabbitpy API
(Connection(url) connects on construction); guard __enter__() to skip
connect() if already open
rabbitpy/message.py:
- Fix _coerce_properties(): pamqp 4 requires str for shortstr fields;
replace maybe_utf8_encode() (which produced bytes) with UTF-8 decode
for incoming bytes values
Test infrastructure:
- Rename base_tests.py → test_base.py, channel_test.py → test_channel.py,
integration_tests.py → test_integration.py, utils_tests.py → test_utils.py
so unittest discover (test*.py pattern) picks them up — these 130+ tests
were silently not running before
- Add dotenv loading to test_integration.py so RABBITMQ_URL is available
- Fix test_base: test_name_bytes now expects ValueError (bytes rejected)
- Fix test_channel: test_invoking_consume_raises uses register_consumer
- Fix test_message: test_coerce_property_int_to_str expects str not bytes
- Fix test_integration: remove .decode('utf-8') calls on message_id
(properties are now str, not bytes)
- Update test_connection._make_conn() to patch connect() so unit tests
do not attempt a real network connection on instantiation
Co-Authored-By: Gavin M. Roy <gavinmroy@gmail.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 41 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (14)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Seven categories of issues were identified from CI logs and resolved: - ruff format: apply canonical formatting to 6 source/test files - ruff RUF059: prefix 7 unused unpacked variables with _ in test_channel0.py (ch0, ev, exc pattern from _make()) - mypy var-annotated: annotate exc as queue.Queue[Exception] in _make() - basedpyright ConnectionArgs: type _make_args return as ConnectionArgs and supply a properly typed SslOptions for ssl_options; add SslOptions import to test_channel0.py - mypy method-assign: replace direct method monkey-patch in TestConnectionChannelMethod.setUp/tearDown with mock.patch.object so mypy no longer rejects the assignment - basedpyright Optional member access: use pyright: ignore comments on two lines in test_connection.py that access mock attributes through Optional-typed fields (_io and _channel0) - mypy attr-defined / basedpyright: add tests/test_integration.py to the basedpyright exclude list and the mypy ignore_errors override; update stale legacy module names in the mypy overrides block All 207 unit tests pass; pre-commit suite (ruff format, ruff lint, mypy, basedpyright) is clean. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Without publisher confirms, all 5 Basic.Publish frames were sent to the broker in rapid succession with no acknowledgment, then len() immediately issued a passive Queue.Declare — which returned whatever count RabbitMQ had processed so far (typically 2 of 5), causing the assertion to fail intermittently on all Python versions. Adding channel.enable_publisher_confirms() before publishing makes each publish() call block until RabbitMQ sends Basic.Ack, guaranteeing all 5 messages are queued before the test assertion runs. Co-Authored-By: Gavin M. Roy <gavinmroy@gmail.com>
Summary
Completes test infrastructure that was silently broken, fixes four pamqp 4 runtime incompatibilities that prevented the library from working end-to-end, and expands test coverage from 62% to 84%.
Problem
After the library modernization and module restoration (PR #151), two categories of problems existed:
Tests not running: The restored test files (
base_tests.py,channel_test.py,integration_tests.py,utils_tests.py) used the old naming convention and didn't match unittest's defaulttest*.pydiscovery pattern. 130+ tests were silently not executing — including all integration tests. The library had never been validated end-to-end after the modernization.pamqp 4 incompatibilities: Once tests ran against a real broker, four runtime failures surfaced:
pamqp.commands.Frameremoved —_validate_frame_type()fell through silently, causing_wait_on_frame()to loop forever on any channel operationChannel.Close/Connection.Closewith defaultclass_id=None→TypeErrormarshaling to wire formatContentHeader.properties.to_dict()removed →AttributeErroron every received message_coerce_properties()encoding str properties asbytes→TypeErrormarshaling outbound messages (pamqp 4 requiresstr)Solution
Test infrastructure:
base_tests.py→test_base.py,channel_test.py→test_channel.py,integration_tests.py→test_integration.py,utils_tests.py→test_utils.pydotenv.load_dotenv()totest_integration.pysoRABBITMQ_URLis available without depending on test execution ordertest_channel0.py(95% coverage),test_connection.py(90% coverage),test_heartbeat.py(100% coverage)Connection.__init__()to match the original API contractBug fixes:
base.py:commands.Frame→pamqp.base.Frame;class_id=0, method_id=0in_build_close_frame()channel.py: replaceproperties.to_dict()withattributes()-based dict comprehensionchannel0.py: add_raise_if_server_close()soACCESS_REFUSEDraisesAMQPAccessRefusednot a genericConnectionExceptionmessage.py: fix_coerce_properties()to producestrnotbytesfor shortstr propertiesImpact
channel.py22% → 62%,base.py35% → 76%,simple.py24% → 95%