diff --git a/critterwatch/WebService.Http/CritterWatchConsole/Program.cs b/critterwatch/WebService.Http/CritterWatchConsole/Program.cs index 1c29451..0912c0f 100644 --- a/critterwatch/WebService.Http/CritterWatchConsole/Program.cs +++ b/critterwatch/WebService.Http/CritterWatchConsole/Program.cs @@ -52,9 +52,18 @@ builder.Services.AddWolverineHttp(); // The console can also push operator commands (PauseProjection / RebuildProjection / DLQ ops / …) BACK to -// the OrderService over the same HTTP transport. Sending over the transport needs the transport client; -// register it so those sends work when an operator triggers them. (The named HttpClient that targets a -// specific service's control URL is created on demand from the service's reported control URI.) +// the OrderService over the same HTTP transport. Sending over the transport needs the transport client. +// +// ⚠️ On WolverineFx 6.23.1 (the pin below) this registration is NOT sufficient, and the control channel +// does not work. An earlier version of this comment claimed the named HttpClient for a service's control +// URL is "created on demand from the service's reported control URI" — it is not, and nothing creates it. +// WolverineHttpTransportClient uses the outbound URI purely as an IHttpClientFactory client NAME and then +// posts to that client's BaseAddress; CreateClient returns a default client with a null BaseAddress for an +// unknown name, so the send throws "An invalid request URI was provided…". The console cannot pre-register +// a named client either, because it only learns a service's control URL at runtime from that service's own +// registration. Reported as ProductSupport#34, fixed in wolverine#3681 (GH-3690): the transport falls back +// to its own isolated client and posts to the absolute outbound URI, and AddWolverineHttp() registers both +// of these lines for you. DELETE THEM when this sample bumps past that Wolverine release. builder.Services.AddScoped(); builder.Services.AddHttpClient(); diff --git a/critterwatch/WebService.Http/README.md b/critterwatch/WebService.Http/README.md index 5f65731..1bf335f 100644 --- a/critterwatch/WebService.Http/README.md +++ b/critterwatch/WebService.Http/README.md @@ -44,6 +44,19 @@ receiver's HTTP endpoint.) `IHttpClientFactory.CreateClient(outboundUri)` and POSTs to `client.BaseAddress`). - `MapWolverineHttpTransportEndpoints()` so the console can POST operator commands **back** to this service. +> ⚠️ **The control channel (console → service) does not work on WolverineFx 6.23.1.** Telemetry +> (service → console) is fine. The send side resolves its `HttpClient` by using the outbound URI as an +> `IHttpClientFactory` client *name* and then posts to that client's `BaseAddress`; for a name nobody +> registered, `CreateClient` returns a default client whose `BaseAddress` is `null`, and the send throws +> `"An invalid request URI was provided. Either the request URI must be an absolute URI or BaseAddress +> must be set."` The console **cannot** work around it — it only learns a service's control URL at runtime +> from that service's own registration, so there is no point at which it could register a named client. +> +> Reported as ProductSupport#34, fixed in wolverine#3681 (GH-3690). When this sample bumps past that +> release: delete the two manual registration lines in `CritterWatchConsole/Program.cs` +> (`AddWolverineHttp()` registers them), and un-skip +> `WebServiceHttpSmokeTests.console_control_channel_sends_without_transport_errors`. + **CritterWatchConsole (receiver)** — `CritterWatchConsole/Program.cs`: - `AddCritterWatch(...)` for the dashboard/store, then `app.MapWolverineHttpTransportEndpoints()` to receive envelopes at `/_wolverine/invoke`, executed inline against the console's CritterWatch handlers. diff --git a/critterwatch/WebService.Http/Tests/WebServiceHttpSmokeTests.cs b/critterwatch/WebService.Http/Tests/WebServiceHttpSmokeTests.cs index bf46017..6a39e22 100644 --- a/critterwatch/WebService.Http/Tests/WebServiceHttpSmokeTests.cs +++ b/critterwatch/WebService.Http/Tests/WebServiceHttpSmokeTests.cs @@ -76,4 +76,42 @@ public async Task order_service_registers_with_the_console_over_http_transport() services.Select(s => s.Id).ShouldContain("OrderService"); } + + /// + /// The CONTROL direction of the monitoring link — console → service — which this battery never + /// covered. Everything above exercises telemetry (service → console), which is why ProductSupport#34 + /// shipped: the console's send side throws on every attempt, and nothing here noticed. + /// + /// + /// + /// Asserted through the console's logs rather than by driving an operator command, because operator + /// commands travel CritterWatch's SignalR hub and this harness has no SignalR client. It still pins + /// the defect: the console pushes to a registered service on its own, so a broken send surfaces as + /// BufferedSendingAgent failures in the console log with no operator action at all — exactly + /// how the reporter found it. + /// + /// + /// SKIPPED until this sample bumps past wolverine#3681 (GH-3690). It fails on the pinned WolverineFx + /// 6.23.1 — legitimately, because the control channel really is broken there. Un-skip with the bump; + /// see the warning in README.md. + /// + /// + [Fact(Skip = "Control channel is broken on WolverineFx 6.23.1 — un-skip when this sample bumps past wolverine#3681 (GH-3690).")] + public async Task console_control_channel_sends_without_transport_errors() + { + using var client = _fixture.CreateCritterWatchClient(); + + // Registration first: the console has nothing to send to until OrderService reports its control URI. + await _fixture.WaitForServicesAsync(client, ["OrderService"], timeout: TimeSpan.FromMinutes(2)); + + // Give the console a window to actually push to the freshly-registered service. + await Task.Delay(TimeSpan.FromSeconds(20)); + + var consoleLogs = _fixture.DumpResourceLogs(CritterWatchAppHostFixture.CritterWatchResourceName); + + // The GH-3690 signature. Matched on the exception text rather than the sender type so the assertion + // survives Wolverine renaming BufferedSendingAgent. + consoleLogs.ShouldNotContain("An invalid request URI was provided"); + consoleLogs.ShouldNotContain("Failed to send outgoing envelopes batch"); + } }