Skip to content

Commit 43b3724

Browse files
waldekmastykarzCopilotCopilot
authored
Flush active recording when Dev Proxy shuts down. Closes #1945 (#1946)
* Flush active recording when Dev Proxy shuts down. Closes #1945 Restores v3 behavior: stopping the proxy (Ctrl+C, devproxy stop, API) while recording now runs AfterRecordingStopAsync so plugins like HarGeneratorPlugin still write their output. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 545e4fa5-3816-4970-a2d0-8f8083d93163 * Update summary comment for StopAsync method Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: 545e4fa5-3816-4970-a2d0-8f8083d93163
1 parent e1a7ff2 commit 43b3724

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using DevProxy.Proxy;
6+
using Microsoft.AspNetCore.Hosting.Server;
7+
using Microsoft.AspNetCore.Http.Features;
8+
using Microsoft.Extensions.Hosting;
9+
using Microsoft.Extensions.Logging.Abstractions;
10+
using Xunit;
11+
12+
namespace DevProxy.Tests;
13+
14+
/// <summary>
15+
/// Verifies that shutting down the proxy (Ctrl+C, <c>devproxy stop</c>) flushes an
16+
/// active recording so recording plugins still run (dotnet/dev-proxy#1945).
17+
/// </summary>
18+
public sealed class InteractiveConsoleServiceTests
19+
{
20+
private static (InteractiveConsoleService service, FakeProxyStateController controller) CreateService()
21+
{
22+
var controller = new FakeProxyStateController();
23+
var service = new InteractiveConsoleService(
24+
controller,
25+
new FakeProxyConfiguration(),
26+
new RecordingConsole { IsInputRedirected = true },
27+
new FakeServer(),
28+
new FakeLifetime(),
29+
NullLogger<InteractiveConsoleService>.Instance);
30+
return (service, controller);
31+
}
32+
33+
[Fact]
34+
public async Task StopAsync_WhileRecording_StopsRecording()
35+
{
36+
var (service, controller) = CreateService();
37+
controller.ProxyState.IsRecording = true;
38+
39+
await service.StopAsync(CancellationToken.None);
40+
41+
Assert.Equal(1, controller.StopRecordingCalls);
42+
}
43+
44+
[Fact]
45+
public async Task StopAsync_NotRecording_DoesNotStopRecording()
46+
{
47+
var (service, controller) = CreateService();
48+
49+
await service.StopAsync(CancellationToken.None);
50+
51+
Assert.Equal(0, controller.StopRecordingCalls);
52+
}
53+
54+
private sealed class FakeServer : IServer
55+
{
56+
public IFeatureCollection Features { get; } = new FeatureCollection();
57+
public void Dispose() { }
58+
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull => Task.CompletedTask;
59+
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
60+
}
61+
62+
private sealed class FakeLifetime : IHostApplicationLifetime
63+
{
64+
public CancellationToken ApplicationStarted => CancellationToken.None;
65+
public CancellationToken ApplicationStopping => CancellationToken.None;
66+
public CancellationToken ApplicationStopped => CancellationToken.None;
67+
public void StopApplication() { }
68+
}
69+
}

‎DevProxy/Proxy/InteractiveConsoleService.cs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
130130
}
131131
}
132132

133+
/// <summary>
134+
/// Flushes an active recording on shutdown (Ctrl+C, <c>devproxy stop</c>, API stop)
135+
/// so plugins like HarGeneratorPlugin still process the recorded requests.
136+
/// Hosted services stop in reverse registration order, so normal shutdown
137+
/// stops the proxy engine before this service flushes the recording.
138+
/// </summary>
139+
public override async Task StopAsync(CancellationToken cancellationToken)
140+
{
141+
// Stop the hotkey loop first so a concurrent 'S' press can't race this flush.
142+
await base.StopAsync(cancellationToken);
143+
144+
if (controller.ProxyState.IsRecording)
145+
{
146+
await controller.StopRecordingAsync(cancellationToken);
147+
}
148+
}
149+
133150
/// <summary>
134151
/// Waits for the host to finish starting so the banner is printed after the
135152
/// engine's startup logs (and the bound port is known). Returns <c>false</c>

0 commit comments

Comments
 (0)