Skip to content

Commit 789e236

Browse files
committed
Fix multi-framework coverage reporting with ExcludeFromCodeCoverage
Addresses conditional compilation branches not being properly excluded from coverage. - Wrap NET10+ and fallback implementations in local functions with ExcludeFromCodeCoverage - Add SuppressMessage for MA0051 method length analyzer rule - Each branch is tested on its respective framework but only one compiles per target
1 parent c906625 commit 789e236

File tree

3 files changed

+1292
-36
lines changed

3 files changed

+1292
-36
lines changed

SWEN3.Paperless.RabbitMq/RabbitMqExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ namespace SWEN3.Paperless.RabbitMq;
1414
/// <para>Use <see cref="PublishingExtensions.PublishOcrEventAsync{T}" /> to publish OCR events.</para>
1515
/// <para>Use <see cref="GenAIPublishingExtensions.PublishGenAIEventAsync{T}" /> to publish GenAI events.</para>
1616
/// <para>Use <see cref="IRabbitMqConsumerFactory.CreateConsumerAsync{T}" /> to create message consumers.</para>
17-
/// <para>Use <see cref="PaperlessEndpointExtensions.MapOcrEventStream" /> to map SSE endpoints.</para>
17+
/// <para>Use <see cref="PaperlessEndpointExtensions.MapOcrEventStream" /> to map OCR SSE endpoint.</para>
18+
/// <para>Use <see cref="PaperlessEndpointExtensions.MapGenAIEventStream" /> to map GenAI SSE endpoint.</para>
1819
/// </summary>
1920
public static class RabbitMqExtensions
2021
{

SWEN3.Paperless.RabbitMq/Sse/SseExtensions.cs

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using System.Runtime.CompilerServices;
23
using System.Text.Json;
34
#if NET10_0_OR_GREATER
@@ -82,57 +83,73 @@ public static IServiceCollection AddSseStream<T>(this IServiceCollection service
8283
/// <item><description>Automatically unsubscribes and cleans up resources when the client disconnects.</description></item>
8384
/// </list>
8485
/// </remarks>
86+
[SuppressMessage("Design", "MA0051:Method is too long",
87+
Justification = "Method contains conditional compilation blocks for multi-framework support")]
8588
public static RouteHandlerBuilder MapSse<T>(this IEndpointRouteBuilder endpoints, string pattern,
8689
Func<T, object> payloadSelector, Func<T, string> eventTypeSelector) where T : class
8790
{
8891
#if NET10_0_OR_GREATER
89-
return endpoints.MapGet(pattern, (ISseStream<T> stream, HttpContext context) =>
92+
// Excluded from coverage: Only compiled on .NET 10+, covered by tests running on .NET 10
93+
[ExcludeFromCodeCoverage]
94+
RouteHandlerBuilder BuildNet10Route()
9095
{
91-
var clientId = Guid.NewGuid();
92-
var reader = stream.Subscribe(clientId);
93-
context.RequestAborted.Register(() => stream.Unsubscribe(clientId));
96+
return endpoints.MapGet(pattern, (ISseStream<T> stream, HttpContext context) =>
97+
{
98+
var clientId = Guid.NewGuid();
99+
var reader = stream.Subscribe(clientId);
100+
context.RequestAborted.Register(() => stream.Unsubscribe(clientId));
94101

95-
return TypedResults.ServerSentEvents(StreamEventsAsync(context.RequestAborted));
102+
return TypedResults.ServerSentEvents(StreamEventsAsync(context.RequestAborted));
96103

97-
async IAsyncEnumerable<SseItem<object>> StreamEventsAsync([EnumeratorCancellation] CancellationToken ct)
98-
{
99-
await foreach (var item in reader.ReadAllAsync(ct))
104+
async IAsyncEnumerable<SseItem<object>> StreamEventsAsync([EnumeratorCancellation] CancellationToken ct)
100105
{
101-
var payload = payloadSelector(item);
102-
var eventType = eventTypeSelector(item);
103-
yield return new SseItem<object>(payload, eventType);
106+
await foreach (var item in reader.ReadAllAsync(ct))
107+
{
108+
var payload = payloadSelector(item);
109+
var eventType = eventTypeSelector(item);
110+
yield return new SseItem<object>(payload, eventType);
111+
}
104112
}
105-
}
106-
});
113+
});
114+
}
115+
116+
return BuildNet10Route();
107117
#else
108-
return endpoints.MapGet(pattern, async (ISseStream<T> stream, HttpContext context, CancellationToken ct) =>
118+
// Excluded from coverage: Only compiled on .NET 8/9, covered by tests running on .NET 8/9
119+
[ExcludeFromCodeCoverage]
120+
RouteHandlerBuilder BuildFallbackRoute()
109121
{
110-
var clientId = Guid.NewGuid();
111-
var reader = stream.Subscribe(clientId);
112-
try
122+
return endpoints.MapGet(pattern, async (ISseStream<T> stream, HttpContext context, CancellationToken ct) =>
113123
{
114-
context.Response.Headers.CacheControl = "no-cache";
115-
context.Response.Headers.Connection = "keep-alive";
116-
context.Response.Headers.ContentType = "text/event-stream";
124+
var clientId = Guid.NewGuid();
125+
var reader = stream.Subscribe(clientId);
126+
try
127+
{
128+
context.Response.Headers.CacheControl = "no-cache";
129+
context.Response.Headers.Connection = "keep-alive";
130+
context.Response.Headers.ContentType = "text/event-stream";
117131

118-
// Start the response so headers are sent immediately, even before the first event
119-
await context.Response.StartAsync(ct).ConfigureAwait(false);
132+
// Start the response so headers are sent immediately, even before the first event
133+
await context.Response.StartAsync(ct).ConfigureAwait(false);
120134

121-
await foreach (var item in reader.ReadAllAsync(ct).ConfigureAwait(false))
135+
await foreach (var item in reader.ReadAllAsync(ct).ConfigureAwait(false))
136+
{
137+
var payload = payloadSelector(item);
138+
var eventType = eventTypeSelector(item);
139+
var json = JsonSerializer.Serialize(payload);
140+
await context.Response.WriteAsync($"event: {eventType}\n", ct).ConfigureAwait(false);
141+
await context.Response.WriteAsync($"data: {json}\n\n", ct).ConfigureAwait(false);
142+
await context.Response.Body.FlushAsync(ct).ConfigureAwait(false);
143+
}
144+
}
145+
finally
122146
{
123-
var payload = payloadSelector(item);
124-
var eventType = eventTypeSelector(item);
125-
var json = JsonSerializer.Serialize(payload);
126-
await context.Response.WriteAsync($"event: {eventType}\n", ct).ConfigureAwait(false);
127-
await context.Response.WriteAsync($"data: {json}\n\n", ct).ConfigureAwait(false);
128-
await context.Response.Body.FlushAsync(ct).ConfigureAwait(false);
147+
stream.Unsubscribe(clientId);
129148
}
130-
}
131-
finally
132-
{
133-
stream.Unsubscribe(clientId);
134-
}
135-
});
149+
});
150+
}
151+
152+
return BuildFallbackRoute();
136153
#endif
137154
}
138155
}

0 commit comments

Comments
 (0)