/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
601 строка
19 KB
Brennan
Fix IAsyncEnumerable controller methods to allow setting headers (#57924)
21 сен 2024, 02:42
Не верифицирован
21 сен 2024, 02:42
92376ce
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; #nullable enable namespace Microsoft.AspNetCore.Http.Extensions.Tests; public class HttpResponseJsonExtensionsTests { [Fact] public async Task WriteAsJsonAsyncGeneric_SimpleValue_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(1); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); var data = body.ToArray(); Assert.Collection(data, b => Assert.Equal((byte)'1', b)); } [Fact] public async Task WriteAsJsonAsyncGeneric_NullValue_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync<Uri?>(value: null); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal("null", data); } [Fact] public async Task WriteAsJsonAsyncGeneric_WithOptions_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act var options = new JsonSerializerOptions(); options.Converters.Add(new IntegerConverter()); await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, options); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal("[false,true,false]", data); } private class IntegerConverter : JsonConverter<int> { public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotImplementedException(); } public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { writer.WriteBooleanValue(value % 2 == 0); } } [Fact] public async Task WriteAsJsonAsyncGeneric_CustomStatusCode_StatusCodeUnchanged() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act context.Response.StatusCode = StatusCodes.Status418ImATeapot; await context.Response.WriteAsJsonAsync(1); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode); } [Fact] public async Task WriteAsJsonAsyncGeneric_WithContentType_JsonResponseWithCustomContentType() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(1, options: null, contentType: "application/custom-type"); // Assert Assert.Equal("application/custom-type", context.Response.ContentType); } [Fact] public async Task WriteAsJsonAsyncGeneric_WithCancellationToken_CancellationRaised() { // Arrange var context = new DefaultHttpContext(); context.Response.Body = new TestStream(); var cts = new CancellationTokenSource(); // Act var writeTask = context.Response.WriteAsJsonAsync(1, cts.Token); Assert.False(writeTask.IsCompleted); cts.Cancel(); // Assert await Assert.ThrowsAsync<TaskCanceledException>(async () => await writeTask); } [Fact] public async Task WriteAsJsonAsyncGeneric_ObjectWithStrings_CamcelCaseAndNotEscaped() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; var value = new TestObject { StringProperty = "激光這兩個字是甚麼意思" }; // Act await context.Response.WriteAsJsonAsync(value); // Assert var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data); } [Fact] public async Task WriteAsJsonAsync_SimpleValue_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(1, typeof(int)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); var data = body.ToArray(); Assert.Collection(data, b => Assert.Equal((byte)'1', b)); } [Fact] public async Task WriteAsJsonAsync_NullValue_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(value: null, typeof(int?)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal("null", data); } [Fact] public async Task WriteAsJsonAsync_NullType_ThrowsArgumentNullException() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act & Assert await Assert.ThrowsAsync<ArgumentNullException>(async () => await context.Response.WriteAsJsonAsync(value: null, type: null!)); } [Fact] public async Task WriteAsJsonAsync_NullResponse_ThrowsArgumentNullException() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act & Assert await Assert.ThrowsAsync<ArgumentNullException>(async () => await HttpResponseJsonExtensions.WriteAsJsonAsync(response: null!, value: null, typeof(int?))); } [Fact] public async Task WriteAsJsonAsync_ObjectWithStrings_CamcelCaseAndNotEscaped() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; var value = new TestObject { StringProperty = "激光這兩個字是甚麼意思" }; // Act await context.Response.WriteAsJsonAsync(value, typeof(TestObject)); // Assert var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data); } [Fact] public async Task WriteAsJsonAsync_CustomStatusCode_StatusCodeUnchanged() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act context.Response.StatusCode = StatusCodes.Status418ImATeapot; await context.Response.WriteAsJsonAsync(1, typeof(int)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode); } [Fact] public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(AsyncEnumerable()); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray())); async IAsyncEnumerable<int> AsyncEnumerable() { await Task.Yield(); yield return 1; yield return 2; } } [Fact] public async Task WriteAsJsonAsync_AsyncEnumerable() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray())); async IAsyncEnumerable<int> AsyncEnumerable() { await Task.Yield(); yield return 1; yield return 2; } } [Fact] public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable_ClosedConnecton() { // Arrange var cts = new CancellationTokenSource(); var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; context.RequestAborted = cts.Token; var iterated = false; // Act await context.Response.WriteAsJsonAsync(AsyncEnumerable()); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); // System.Text.Json might write the '[' before cancellation is observed Assert.InRange(body.ToArray().Length, 0, 1); Assert.False(iterated); async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Yield(); cts.Cancel(); for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++) { iterated = true; yield return i; } } } [Fact] public async Task WriteAsJsonAsync_AsyncEnumerable_ClosedConnecton() { // Arrange var cts = new CancellationTokenSource(); var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; context.RequestAborted = cts.Token; var iterated = false; // Act await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); // System.Text.Json might write the '[' before cancellation is observed Assert.InRange(body.ToArray().Length, 0, 1); Assert.False(iterated); async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Yield(); cts.Cancel(); for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++) { iterated = true; yield return i; } } } [Fact] public async Task WriteAsJsonAsync_AsyncEnumerable_UserPassedTokenThrows() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; context.RequestAborted = new CancellationToken(canceled: true); var cts = new CancellationTokenSource(); var iterated = false; // Act await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>), cts.Token)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); // System.Text.Json might write the '[' before cancellation is observed Assert.InRange(body.ToArray().Length, 0, 1); Assert.False(iterated); async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Yield(); cts.Cancel(); for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++) { iterated = true; yield return i; } } } [Fact] public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable_UserPassedTokenThrows() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; context.RequestAborted = new CancellationToken(canceled: true); var cts = new CancellationTokenSource(); var iterated = false; // Act await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), cts.Token)); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); // System.Text.Json might write the '[' before cancellation is observed Assert.InRange(body.ToArray().Length, 0, 1); Assert.False(iterated); async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Yield(); cts.Cancel(); for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++) { iterated = true; yield return i; } } } [Fact] public async Task WriteAsJsonAsyncGeneric_WithJsonTypeInfo_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act var options = new JsonSerializerOptions(); options.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, (JsonTypeInfo<int[]>)options.GetTypeInfo(typeof(int[]))); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal("[1,2,3]", data); } [Fact] public async Task WriteAsJsonAsync_NullValue_WithJsonTypeInfo_JsonResponse() { // Arrange var body = new MemoryStream(); var context = new DefaultHttpContext(); context.Response.Body = body; // Act var options = new JsonSerializerOptions(); options.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); await context.Response.WriteAsJsonAsync(value : null, options.GetTypeInfo(typeof(Uri))); // Assert Assert.Equal(ContentTypeConstants.JsonContentTypeWithCharset, context.Response.ContentType); var data = Encoding.UTF8.GetString(body.ToArray()); Assert.Equal("null", data); } // Regression test: https://github.com/dotnet/aspnetcore/issues/57895 [Fact] public async Task AsyncEnumerableCanSetHeader() { var builder = WebApplication.CreateBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); app.MapGet("/", IAsyncEnumerable<int> (HttpContext httpContext) => { return AsyncEnum(); async IAsyncEnumerable<int> AsyncEnum() { await Task.Yield(); httpContext.Response.Headers["Test"] = "t"; yield return 1; } }); await app.StartAsync(); var client = app.GetTestClient(); var result = await client.GetAsync("/"); result.EnsureSuccessStatusCode(); var headerValue = Assert.Single(result.Headers.GetValues("Test")); Assert.Equal("t", headerValue); await app.StopAsync(); } // Regression test: https://github.com/dotnet/aspnetcore/issues/57895 [Fact] public async Task EnumerableCanSetHeader() { var builder = WebApplication.CreateBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); app.MapGet("/", IEnumerable<int> (HttpContext httpContext) => { return Enum(); IEnumerable<int> Enum() { httpContext.Response.Headers["Test"] = "t"; yield return 1; } }); await app.StartAsync(); var client = app.GetTestClient(); var result = await client.GetAsync("/"); result.EnsureSuccessStatusCode(); var headerValue = Assert.Single(result.Headers.GetValues("Test")); Assert.Equal("t", headerValue); await app.StopAsync(); } public class TestObject { public string? StringProperty { get; set; } } private class TestStream : Stream { public override bool CanRead { get; } public override bool CanSeek { get; } public override bool CanWrite { get; } public override long Length { get; } public override long Position { get; set; } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource<int>(); cancellationToken.Register(s => ((TaskCompletionSource<int>)s!).SetCanceled(), tcs); return new ValueTask<int>(tcs.Task); } public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); cancellationToken.Register(s => ((TaskCompletionSource)s!).SetCanceled(), tcs); return new ValueTask(tcs.Task); } } }