Skip to content

Pass ServiceBusReceivedMessage through ProcessAsync end-to-end#20

Merged
christopherhouse merged 1 commit into
mainfrom
refactor/process-takes-message
May 29, 2026
Merged

Pass ServiceBusReceivedMessage through ProcessAsync end-to-end#20
christopherhouse merged 1 commit into
mainfrom
refactor/process-takes-message

Conversation

@christopherhouse
Copy link
Copy Markdown
Owner

Summary

The previous refactor only got halfway: Run took the ServiceBusReceivedMessage, then immediately unrolled it into a flat parameter list (string body, string? messageId, int deliveryCount, ...) before calling ProcessAsync. The actual work still operated on a string. Same anti-pattern in both functions.

This PR fixes that. Both ProcessAsync methods now take ServiceBusReceivedMessage directly and read every field off the message. Body is deserialised via message.Body.ToObjectFromJson<SharePointListUpdatedEvent>() instead of through the trigger's string binding.

Test notes

Tests now construct real messages via ServiceBusModelFactory.ServiceBusReceivedMessage(...). Two quirks discovered along the way:

  • The factory doesn't expose deadLetterReason / deadLetterErrorDescription as direct parameters. Those properties on ServiceBusReceivedMessage are surfaced from broker-side properties with those exact keys, so the DLQ test helper sets them via the properties dictionary.
  • BinaryData.FromString("") produces a state where .ToString() throws ArgumentNullException. Guard added in the DLQ handler so a zero-byte body returns "" instead of crashing.

Test plan

  • dotnet build — clean.
  • dotnet format --verify-no-changes — clean.
  • dotnet test tests/Function.Tests — 13/13 pass.
  • After merge: function-app workflow ships the new binding. Spot-check a live publisher run still emits DocumentPublished with MessageId, DeliveryCount, and enqueueLatencyMs on the event.

🤖 Generated with Claude Code

Previous refactor stopped at the function boundary: Run took the
message, then immediately unrolled it into a flat parameter list
(string body, string? messageId, int deliveryCount, ...) before
handing to ProcessAsync. That meant the "real" work never had the
message in hand — same anti-pattern in both functions.

Both `ProcessAsync` methods now take `ServiceBusReceivedMessage`
directly and read every field off it. Body is deserialised via
`message.Body.ToObjectFromJson<T>()` instead of going through the
extension's string binding.

Tests construct real `ServiceBusReceivedMessage` instances via
`ServiceBusModelFactory.ServiceBusReceivedMessage(...)`. Two small
notes:

- The factory doesn't expose `deadLetterReason` /
  `deadLetterErrorDescription` as direct parameters — those
  properties on the message are surfaced from broker properties
  with those exact keys, so the DLQ helper sets them via the
  `properties` dictionary.
- `BinaryData.FromString("")` produces a state where `.ToString()`
  throws `ArgumentNullException`. Guard in the DLQ handler so
  zero-length bodies don't crash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@christopherhouse christopherhouse merged commit 84a2768 into main May 29, 2026
4 checks passed
@christopherhouse christopherhouse deleted the refactor/process-takes-message branch May 29, 2026 17:47
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.

1 participant