// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Endpoints.FormMapping;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Routing.FunctionalTests;

public class MinimalFormTests
{

    [Fact]
    public async Task MapPost_WithForm_ValidToken_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var body = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<Todo>(body, JsonSerializerOptions.Web);
        Assert.Equal("Test task", result.Name);
        Assert.False(result.IsCompleted);
        Assert.Equal(DateTime.Today.AddDays(1), result.DueDate);
    }

    [Fact]
    public async Task MapRequestDelegate_WithForm_RequiresValidation_ValidToken_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", async context =>
                            {
                                var form = await context.Request.ReadFormAsync();
                                var todo = new Todo
                                {
                                    Name = form["name"],
                                    IsCompleted = bool.Parse(form["isComplete"]),
                                    DueDate = DateTime.Parse(form["dueDate"], CultureInfo.InvariantCulture)
                                };
                                await context.Response.WriteAsJsonAsync(todo);
                            }).WithMetadata(AntiforgeryMetadata.ValidationRequired));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var body = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<Todo>(body, JsonSerializerOptions.Web);
        Assert.Equal("Test task", result.Name);
        Assert.False(result.IsCompleted);
        Assert.Equal(DateTime.Today.AddDays(1), result.DueDate);
    }

    [Fact]
    public async Task MapPost_WithForm_InvalidToken_Fails()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task MapPost_WithForm_WithoutMiddleware_ThrowsException()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.SendAsync(request));
        Assert.Equal(
            "Endpoint HTTP: POST /todo contains anti-forgery metadata, but a middleware was not found that supports anti-forgery." +
            Environment.NewLine +
            "Configure your application startup by adding app.UseAntiforgery() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAntiforgery() must go between them. " +
            "Calls to app.UseAntiforgery() must be placed after calls to app.UseAuthentication() and app.UseAuthorization().",
            exception.Message);
    }

    [Fact]
    public async Task MapPost_WithForm_WithoutServices_WithMiddleware_ThrowsException()
    {
        Exception exception = null;
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        exception = Assert.Throws<InvalidOperationException>(() => app.UseAntiforgery());
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();

        Assert.NotNull(exception);
        Assert.Equal(
            "Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddAntiforgery' in the application startup code.",
            exception.Message);
    }

    [Fact]
    public async Task MapPost_WithForm_WithoutAntiforgery_WithoutMiddleware_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo)
                            .DisableAntiforgery());
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var body = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<Todo>(body, JsonSerializerOptions.Web);
        Assert.Equal("Test task", result.Name);
        Assert.False(result.IsCompleted);
        Assert.Equal(DateTime.Today.AddDays(1), result.DueDate);
    }

    [Fact]
    public async Task MapPost_WithForm_WithoutAntiforgery_AndRouteGroup_WithoutMiddleware_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseEndpoints(b =>
                        {
                            var group = b.MapGroup("/todo").DisableAntiforgery();
                            group.MapPost("", ([FromForm] Todo todo) => todo);
                        });
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        var body = await response.Content.ReadAsStringAsync();
        var result = JsonSerializer.Deserialize<Todo>(body, JsonSerializerOptions.Web);
        Assert.Equal("Test task", result.Name);
        Assert.False(result.IsCompleted);
        Assert.Equal(DateTime.Today.AddDays(1), result.DueDate);
    }

    public static IEnumerable<object[]> RequestDelegateData
    {
        get
        {
            yield return new object[]
            {
                (IEndpointRouteBuilder builder) => builder.MapPost("/todo", async context =>
                {
                    var form = await context.Request.ReadFormAsync();
                    var todo = new Todo
                    {
                        Name = form["name"],
                        IsCompleted = bool.Parse(form["isComplete"]),
                        DueDate = DateTime.Parse(form["dueDate"], CultureInfo.InvariantCulture)
                    };
                    await context.Response.WriteAsJsonAsync(todo);
                }),
            };
            yield return new object[]
            {
                (IEndpointRouteBuilder builder) => builder.MapPost("/todo", async context =>
                {
                    var form = context.Request.Form;
                    var todo = new Todo
                    {
                        Name = form["name"],
                        IsCompleted = bool.Parse(form["isComplete"]),
                        DueDate = DateTime.Parse(form["dueDate"], CultureInfo.InvariantCulture)
                    };
                    await context.Response.WriteAsJsonAsync(todo);
                }),
            };
            yield return new object[]
            {
                (IEndpointRouteBuilder builder) => builder.MapPost("/todo", async context =>
                {
                    var form = context.Features.Get<IFormFeature>()?.ReadForm();
                    var todo = new Todo
                    {
                        Name = form["name"],
                        IsCompleted = bool.Parse(form["isComplete"]),
                        DueDate = DateTime.Parse(form["dueDate"], CultureInfo.InvariantCulture)
                    };
                    await context.Response.WriteAsJsonAsync(todo);
                }),
            };
        }
    }

    [Theory]
    [MemberData(nameof(RequestDelegateData))]
    public async Task MapRequestDelegate_WithForm_RequiresValidation_InvalidToken_Fails(Func<IEndpointRouteBuilder, IEndpointConventionBuilder> addDelegate)
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            addDelegate(b).WithMetadata(AntiforgeryMetadata.ValidationRequired));

                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.SendAsync(request));
        Assert.Equal("This form is being accessed with an invalid anti-forgery token. Validate the `IAntiforgeryValidationFeature` on the request before reading from the form.", exception.Message);
    }

    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task MapPost_WithForm_ValidToken_RequestSizeLimit_Works(bool hasLimit)
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.Use((context, next) =>
                        {
                            context.Features.Set<IHttpMaxRequestBodySizeFeature>(new FakeHttpMaxRequestBodySizeFeature(5_000_000));
                            return next(context);
                        });
                        app.UseRouting();
                        app.Use((context, next) =>
                        {
                            context.Request.Body = new SizeLimitedStream(context.Request.Body, context.Features.Get<IHttpMaxRequestBodySizeFeature>()?.MaxRequestBodySize);
                            return next(context);
                        });
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo).WithMetadata(new RequestSizeLimitMetadata(hasLimit ? 2 : null)));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        if (hasLimit)
        {
            var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.SendAsync(request));
            Assert.Equal("The maximum number of bytes have been read.", exception.Message);
        }
        else
        {
            var response = await client.SendAsync(request);
            response.EnsureSuccessStatusCode();
            var body = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<Todo>(body, JsonSerializerOptions.Web);
            Assert.Equal("Test task", result.Name);
            Assert.False(result.IsCompleted);
            Assert.Equal(DateTime.Today.AddDays(1), result.DueDate);
        }
    }

    [Fact]
    public async Task MapPost_WithForm_AndFormMapperOptions_ValidToken_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Dictionary<string, string> todo) => todo)
                                .WithFormMappingOptions(maxCollectionSize: 2));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("[name]", "Test task"),
            new KeyValuePair<string,string>("[name1]", "Test task"),
            new KeyValuePair<string,string>("[isComplete]", "false"),
            new KeyValuePair<string,string>("[isComplete1]", "false"),
            new KeyValuePair<string,string>("[dueDate]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
            new KeyValuePair<string,string>("[dueDate1]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task SupportsMergingFormMappingOptionsFromGroupAndEndpoint()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                        {
                            var g = b.MapGroup("/todos").WithFormMappingOptions(maxCollectionSize: 2);
                            g.MapPost("/1", ([FromForm] Dictionary<string, string> todo) => todo)
                                .WithFormMappingOptions(maxCollectionSize: 7);
                        });
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "/todos/1");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("[name]", "Test task"),
            new KeyValuePair<string,string>("[name1]", "Test task"),
            new KeyValuePair<string,string>("[isComplete]", "false"),
            new KeyValuePair<string,string>("[isComplete1]", "false"),
            new KeyValuePair<string,string>("[dueDate]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
            new KeyValuePair<string,string>("[dueDate1]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task SupportsMergingFormOptionsFromGroupAndEndpoint()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                        {
                            var g = b.MapGroup("/todos").WithFormOptions(valueCountLimit: 7);
                            g.MapPost("/1", ([FromForm] Dictionary<string, string> todo) => todo)
                                .WithFormOptions(valueCountLimit: 2);
                        });
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "/todos/1");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("[name]", "Test task"),
            new KeyValuePair<string,string>("[name1]", "Test task"),
            new KeyValuePair<string,string>("[isComplete]", "false"),
            new KeyValuePair<string,string>("[isComplete1]", "false"),
            new KeyValuePair<string,string>("[dueDate]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
            new KeyValuePair<string,string>("[dueDate1]", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task MapPost_WithForm_AndRequestLimits_ValidToken_Works()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseAntiforgery();
                        app.UseEndpoints(b =>
                            b.MapPost("/todo", ([FromForm] Todo todo) => todo)
                                .WithFormOptions(keyLengthLimit: 8));
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services =>
            {
                services.AddRouting();
                services.AddAntiforgery();
            })
            .Build();

        using var server = host.GetTestServer();
        await host.StartAsync();
        var client = server.CreateClient();

        var antiforgery = host.Services.GetRequiredService<IAntiforgery>();
        var antiforgeryOptions = host.Services.GetRequiredService<IOptions<AntiforgeryOptions>>();
        var tokens = antiforgery.GetAndStoreTokens(new DefaultHttpContext());
        var request = new HttpRequestMessage(HttpMethod.Post, "todo");
        request.Headers.Add("Cookie", antiforgeryOptions.Value.Cookie.Name + "=" + tokens.CookieToken);
        var nameValueCollection = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string,string>("__RequestVerificationToken", tokens.RequestToken),
            new KeyValuePair<string,string>("name", "Test task"),
            new KeyValuePair<string,string>("isComplete", "false"),
            new KeyValuePair<string,string>("dueDate", DateTime.Today.AddDays(1).ToString(CultureInfo.InvariantCulture)),
        };
        request.Content = new FormUrlEncodedContent(nameValueCollection);

        var response = await client.SendAsync(request);
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task MapPost_WithFormFile_MissingBody_ReturnsBadRequest()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseEndpoints(b => b.MapPost("/", (IFormFile formFile) => "ok").DisableAntiforgery());
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services => services.AddRouting())
            .Build();

        using var server = host.GetTestServer();

        await host.StartAsync();
        var client = server.CreateClient();

        var response = await client.PostAsync("/", null);

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task MapPost_WithFormFile_MissingContentType_ReturnsUnsupportedMediaType()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                    .Configure(app =>
                    {
                        app.UseRouting();
                        app.UseEndpoints(b => b.MapPost("/", (IFormFile formFile) => "ok").DisableAntiforgery());
                    })
                    .UseTestServer();
            })
            .ConfigureServices(services => services.AddRouting())
            .Build();

        using var server = host.GetTestServer();

        await host.StartAsync();
        var client = server.CreateClient();

        var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "/")
        {
            Content = new ByteArrayContent([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
        });

        Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
    }

    class Todo
    {
        public string Name { get; set; }
        public bool IsCompleted { get; set; }
        public DateTime DueDate { get; set; }
    }

    [AttributeUsage(AttributeTargets.Parameter)]
    class FromFormAttribute(string name = "") : Attribute, IFromFormMetadata
    {
        public string Name => name;
    }

    class RequestSizeLimitMetadata(long? maxRequestBodySize) : IRequestSizeLimitMetadata
    {

        public long? MaxRequestBodySize => maxRequestBodySize;
    }

    private class FakeHttpMaxRequestBodySizeFeature : IHttpMaxRequestBodySizeFeature
    {
        public FakeHttpMaxRequestBodySizeFeature(
            long? maxRequestBodySize = null,
            bool isReadOnly = false)
        {
            MaxRequestBodySize = maxRequestBodySize;
            IsReadOnly = isReadOnly;
        }

        public bool IsReadOnly { get; }

        public long? MaxRequestBodySize { get; set; }
    }
}
