Skip to content

Fix xncp_set_manual_source_route keyword arguments#715

Open
TheJulianJES wants to merge 1 commit into
zigpy:devfrom
TheJulianJES:tjj/fix_manual_source_route_kwargs
Open

Fix xncp_set_manual_source_route keyword arguments#715
TheJulianJES wants to merge 1 commit into
zigpy:devfrom
TheJulianJES:tjj/fix_manual_source_route_kwargs

Conversation

@TheJulianJES
Copy link
Copy Markdown
Contributor

@TheJulianJES TheJulianJES commented May 4, 2026

Proposed change

This makes manual source routing* work for the first time ever...? 😅 (by fixing the names of the provided kwargs).

(*: It's only supported on certain firmware builds and requires the manual_source_routing option to be turned on in the bellows section. This is not really related to the normal source_routing option.)

Additional information

Firmware extensions (as well as the bug) were added with:

TODO

We should probably test the firmware side? Was that ever tested then?

AI summary

The send-packet path called xncp_set_manual_source_route with nwk=/ relays= (the kwargs of the sibling set_source_route), but the XNCP wrapper takes destination=/route=. On firmware that advertises the MANUAL_SOURCE_ROUTE feature with manual source routing enabled, this raised TypeError instead of installing the route.

The existing test mocked the method against the wrong spec (set_source_route), so the bad kwargs validated and the bug went unnoticed. Re-spec the mock against xncp_set_manual_source_route so the assertion checks the real signature.

The send-packet path called `xncp_set_manual_source_route` with `nwk=`/
`relays=` (the kwargs of the sibling `set_source_route`), but the XNCP
wrapper takes `destination=`/`route=`. On firmware that advertises the
`MANUAL_SOURCE_ROUTE` feature with manual source routing enabled, this
raised `TypeError` instead of installing the route.

The existing test mocked the method against the wrong spec
(`set_source_route`), so the bad kwargs validated and the bug went
unnoticed. Re-spec the mock against `xncp_set_manual_source_route` so
the assertion checks the real signature.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (4b97a6d) to head (b41c9f4).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #715   +/-   ##
=======================================
  Coverage   99.54%   99.54%           
=======================================
  Files          61       61           
  Lines        4147     4147           
=======================================
  Hits         4128     4128           
  Misses         19       19           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@zigpy-review-bot zigpy-review-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — confirmed root cause and fix.

The xncp_set_manual_source_route wrapper (bellows/ezsp/__init__.py:776) has the signature (self, destination: t.NWK, route: list[t.NWK]), while the call site in send_packet was passing nwk= / relays= — those are the kwargs of the sibling set_source_route EZSP command three lines below. On any firmware advertising MANUAL_SOURCE_ROUTE with manual_source_routing enabled in the bellows config, this raised TypeError: missing a required argument: 'destination' (verified locally via inspect.signature(...).bind(...)). So the PR body is correct: manual source routing has been broken since it was introduced in #611 (merged 2024-11-09, 06eb3d92), where both the wrapper and the wrong call site landed in the same commit.

The test fix is the load-bearing half of this PR. The previous spec, app._ezsp._protocol.set_source_route, accepts nwk/relays, so assert_called_once_with(nwk=..., relays=...) passed against the bad code. Reverting bellows/zigbee/application.py against the new test gives the expected Expected: mock(nwk=...) / Actual: mock(destination=...) failure — i.e. the test now actually guards the bug.

Wire format unchanged: SetSourceRouteReq(destination=destination, source_route=route) serializes the same two NWK fields regardless of Python kwarg names, and matches the NabuCasa firmware decoder in xncp_common_extension/src/xncp_common_commands.c (payload[0..1] = destination NWK, then relay NWKs).

No external callers: GitHub code search for xncp_set_manual_source_route returns only zigpy/bellows itself, so renaming the call-site kwargs isn't a behavior change for anyone downstream — the wrapper's public signature is unchanged.

Full test suite (424 tests) passes locally on Py 3.14. Re: the TODO in the description — yes, the firmware side ought to be tested end-to-end at some point, but that's out of scope for a minimal one-line fix.

Comment thread tests/test_application.py

app._ezsp.xncp_set_manual_source_route = AsyncMock(
return_value=None, spec=app._ezsp._protocol.set_source_route
return_value=None, spec=app._ezsp.xncp_set_manual_source_route
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — this spec swap is what makes the rest of the diff meaningful. _protocol.set_source_route accepts nwk/relays, so assert_called_once_with(nwk=..., relays=...) validated cleanly against the bad code and the bug slipped through review of #611. With the spec now bound to the real wrapper signature (destination/route), the assertion below actually exercises the kwarg-passing pathway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants