33
44using System . Net . ServerSentEvents ;
55using System . Reflection ;
6+ using System . Runtime . CompilerServices ;
67using System . Text ;
78using Microsoft . AspNetCore . Builder ;
89using Microsoft . AspNetCore . Http . Json ;
@@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Http.HttpResults;
1617public class ServerSentEventsResultTests
1718{
1819 [ Fact ]
19- public async Task ExecuteAsync_SetsContentType ( )
20+ public async Task ExecuteAsync_SetsContentTypeAndHeaders ( )
2021 {
2122 // Arrange
2223 var httpContext = GetHttpContext ( ) ;
@@ -28,6 +29,8 @@ public async Task ExecuteAsync_SetsContentType()
2829
2930 // Assert
3031 Assert . Equal ( "text/event-stream" , httpContext . Response . ContentType ) ;
32+ Assert . Equal ( "no-cache,no-store" , httpContext . Response . Headers . CacheControl ) ;
33+ Assert . Equal ( "no-cache" , httpContext . Response . Headers . Pragma ) ;
3134 }
3235
3336 [ Fact ]
@@ -52,20 +55,20 @@ public async Task ExecuteAsync_WritesStringEventsToResponse()
5255 }
5356
5457 [ Fact ]
55- public async Task ExecuteAsync_WritesStringsEventsWithType ( )
58+ public async Task ExecuteAsync_WritesStringsEventsWithEventType ( )
5659 {
5760 // Arrange
5861 var httpContext = GetHttpContext ( ) ;
59- var events = new [ ] { "event1" } . ToAsyncEnumerable ( ) ;
62+ var events = new [ ] { "event1" , "event2" } . ToAsyncEnumerable ( ) ;
6063 var result = TypedResults . ServerSentEvents ( events , "test-event" ) ;
6164
6265 // Act
6366 await result . ExecuteAsync ( httpContext ) ;
6467
6568 // Assert
6669 var responseBody = Encoding . UTF8 . GetString ( ( ( MemoryStream ) httpContext . Response . Body ) . ToArray ( ) ) ;
67- Assert . Contains ( "event: test-event\n " , responseBody ) ;
68- Assert . Contains ( "data: event1 \n \n " , responseBody ) ;
70+ Assert . Contains ( "event: test-event\n data: event1 \n \ n " , responseBody ) ;
71+ Assert . Contains ( "event: test-event \n data: event2 \n \n " , responseBody ) ;
6972 }
7073
7174 [ Fact ]
@@ -207,7 +210,51 @@ public async Task ExecuteAsync_WithPolymorphicType_SerializesCorrectly()
207210
208211 // Assert
209212 var responseBody = Encoding . UTF8 . GetString ( ( ( MemoryStream ) httpContext . Response . Body ) . ToArray ( ) ) ;
210- Assert . Contains ( @"""extra"":""Additional""" , responseBody ) ;
213+ Assert . Contains ( @"data: {""extra"":""Additional"",""name"":""Test"",""value"":42}" , responseBody ) ;
214+ }
215+
216+ [ Fact ]
217+ public async Task ExecuteAsync_ObservesCancellationViaRequestAborted ( )
218+ {
219+ // Arrange
220+ var cts = new CancellationTokenSource ( ) ;
221+ var httpContext = GetHttpContext ( ) ;
222+ httpContext . RequestAborted = cts . Token ;
223+ var firstEventReceived = new TaskCompletionSource ( ) ;
224+ var secondEventAttempted = new TaskCompletionSource ( ) ;
225+
226+ var events = GetEvents ( cts . Token ) ;
227+ var result = TypedResults . ServerSentEvents ( events ) ;
228+
229+ // Act & Assert
230+ var executeTask = result . ExecuteAsync ( httpContext ) ;
231+
232+ // Wait for first event to be processed then cancel the request and wait
233+ // to observe the cancellation
234+ await firstEventReceived . Task ;
235+ cts . Cancel ( ) ;
236+ await secondEventAttempted . Task ;
237+
238+ // Verify the execution was cancelled and only the first event was written
239+ await Assert . ThrowsAsync < TaskCanceledException > ( ( ) => executeTask ) ;
240+ var responseBody = Encoding . UTF8 . GetString ( ( ( MemoryStream ) httpContext . Response . Body ) . ToArray ( ) ) ;
241+ Assert . Contains ( "data: event1\n \n " , responseBody ) ;
242+ Assert . DoesNotContain ( "data: event2\n \n " , responseBody ) ;
243+
244+ async IAsyncEnumerable < string > GetEvents ( [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
245+ {
246+ try
247+ {
248+ yield return "event1" ;
249+ firstEventReceived . SetResult ( ) ;
250+ await Task . Delay ( 1 , cancellationToken ) ;
251+ yield return "event2" ;
252+ }
253+ finally
254+ {
255+ secondEventAttempted . SetResult ( ) ;
256+ }
257+ }
211258 }
212259
213260 private static void PopulateMetadata < TResult > ( MethodInfo method , EndpointBuilder builder )
0 commit comments