// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;

namespace Microsoft.Extensions.DependencyInjection
{
    public class PageConventionCollectionExtensionsTest
    {
        [Fact]
        public void AddFilter_AddsFiltersToAllPages()
        {
            // Arrange
            var filter = Mock.Of<IFilterMetadata>();
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.ConfigureFilter(filter);
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Same(filter, Assert.Single(model.Filters)),
                model => Assert.Same(filter, Assert.Single(model.Filters)),
                model => Assert.Same(filter, Assert.Single(model.Filters)));
        }

        [Fact]
        public void AuthorizePage_AddsAllowAnonymousAttributeToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/Users");
            conventions.AllowAnonymousToPage("/Users/Contact");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.EndpointMetadata),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.IsType<AuthorizeAttribute>(model.EndpointMetadata[0]);
                    Assert.IsType<AllowAnonymousAttribute>(model.EndpointMetadata[1]);
                });
        }

        [Fact]
        public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/Users");
            conventions.AllowAnonymousToPage("/Users/Contact");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(model.Filters[0]);
                    Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
                });
        }

        [Fact]
        public void AllowAnonymousToAreaPage_AddsAllowAnonymousAttributeToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AllowAnonymousToAreaPage("Accounts", "/Profile");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
                });
        }

        [Fact]
        public void AllowAnonymousToAreaPage_WithoutEndpointRouting_AddsAllowAnonymousFilterToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AllowAnonymousToAreaPage("Accounts", "/Profile");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    Assert.IsType<AllowAnonymousFilter>(Assert.Single(model.Filters));
                });
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_AddsAllowAnonymousAttributeToPageUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/");
            conventions.AllowAnonymousToFolder(folderName);
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Index", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata));
                },
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata),
                        metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata),
                        metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
                });
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToPageUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/");
            conventions.AllowAnonymousToFolder(folderName);
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Index", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                },
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(model.Filters[0]);
                    Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(model.Filters[0]);
                    Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
                });
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_AddsAllowAnonymousAttributeToPagesUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/");
            conventions.AllowAnonymousToFolder("/Users");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Index", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata));
                },
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata),
                        metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    Assert.Collection(model.EndpointMetadata,
                        metadata => Assert.IsType<AuthorizeAttribute>(metadata),
                        metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
                });
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder("/");
            conventions.AllowAnonymousToFolder("/Users");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Index", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                },
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(model.Filters[0]);
                    Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.IsType<AuthorizeFilter>(model.Filters[0]);
                    Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
                });
        }

        [Fact]
        public void AllowAnonymousToAreaFolder_AddsEndpointMetadata()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AllowAnonymousToAreaFolder("Accounts", "/Manage");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.EndpointMetadata),
                model => Assert.Empty(model.EndpointMetadata),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
                },
                model => Assert.Empty(model.EndpointMetadata));
        }

        [Fact]
        public void AllowAnonymousToAreaFolder_WithoutEndpointRouting_AddsAllowAnonymousFilterToFolderInArea()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AllowAnonymousToAreaFolder("Accounts", "/Manage");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    Assert.IsType<AllowAnonymousFilter>(Assert.Single(model.Filters));
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    Assert.IsType<AllowAnonymousFilter>(Assert.Single(model.Filters));
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizePage_AddsAuthorizeAttributeWithPolicyToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizePage("/Users/Account", "Manage-Accounts");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("Manage-Accounts", authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithPolicyToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizePage("/Users/Account", "Manage-Accounts");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal("Manage-Accounts", authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizeAreaPage_AddsAuthorizeAttributeWithDefaultPolicyToAreaPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaPage("Accounts", "/Profile");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeAttribute = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Empty(authorizeAttribute.Policy);
                });
        }

        [Fact]
        public void AuthorizeAreaPage_WithoutEndpointRouting_AddsAuthorizeFilterWithDefaultPolicyToAreaPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaPage("Accounts", "/Profile");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    var authFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeAttribute = Assert.Single(authFilter.AuthorizeData);
                    Assert.Empty(authorizeAttribute.Policy);
                });
        }

        [Fact]
        public void AuthorizeAreaPage_AddsAuthorizeAttributeWithCustomPolicyToAreaPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaPage("Accounts", "/Profile", "custom");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeAttribute = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("custom", authorizeAttribute.Policy);
                });
        }

        [Fact]
        public void AuthorizeAreaPage_WithoutEndpointRouting_AddsAuthorizeFilterWithCustomPolicyToAreaPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaPage("Accounts", "/Profile", "custom");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    var authFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeAttribute = Assert.Single(authFilter.AuthorizeData);
                    Assert.Equal("custom", authorizeAttribute.Policy);
                });
        }

        [Fact]
        public void AuthorizePage_AddsAuthorizeAttributeWithoutPolicyToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizePage("/Users/Account");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal(string.Empty, authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithoutPolicyToSpecificPage()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizePage("/Users/Account");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal(string.Empty, authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithPolicyToPagesUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder(folderName, "Manage-Accounts");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("Manage-Accounts", authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("Manage-Accounts", authorizeData.Policy);
                });
        }

        [Theory]
        [InlineData("/Users")]
        [InlineData("/Users/")]
        public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithoutPolicyToPagesUnderFolder(string folderName)
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
                CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
                CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
            };

            // Act
            conventions.AuthorizeFolder(folderName);
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Users/Account", model.ViewEnginePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal(string.Empty, authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Users/Contact", model.ViewEnginePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal(string.Empty, authorizeData.Policy);
                });
        }

        [Fact]
        public void AuthorizeAreaFolder_AddsAuthorizeAttributeWithDefaultPolicyToAreaPagesInFolder()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaFolder("Accounts", "/Manage");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Empty(authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Empty(authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizeAreaFolder_WithoutEndpointRouting_AddsAuthorizeFilterWithDefaultPolicyToAreaPagesInFolder()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaFolder("Accounts", "/Manage");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Empty(authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Empty(authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizeAreaFolder_AddsAuthorizeAttributeWithCustomPolicyToAreaPagesInFolder()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaFolder("Accounts", "/Manage", "custom");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.EndpointMetadata),
                model => Assert.Empty(model.EndpointMetadata),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("custom", authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    Assert.Empty(model.Filters);
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
                    Assert.Equal("custom", authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AuthorizeAreaFolder_WithoutEndpointRouting_AddsAuthorizeFilterWithCustomPolicyToAreaPagesInFolder()
        {
            // Arrange
            var conventions = GetConventions(enableEndpointRouting: false);
            var models = new[]
            {
                CreateApplicationModel("/Profile.cshtml", "/Profile"),
                CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
                CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
            };

            // Act
            conventions.AuthorizeAreaFolder("Accounts", "/Manage", "custom");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model => Assert.Empty(model.Filters),
                model => Assert.Empty(model.Filters),
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal("custom", authorizeData.Policy);
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
                    var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
                    var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
                    Assert.Equal("custom", authorizeData.Policy);
                },
                model => Assert.Empty(model.Filters));
        }

        [Fact]
        public void AddPageRoute_AddsRouteToSelector()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                new PageRouteModel("/Pages/Index.cshtml", "/Index")
                {
                    Selectors =
                    {
                        CreateSelectorModel("Index", suppressLinkGeneration: true),
                        CreateSelectorModel(""),
                    }
                },
                new PageRouteModel("/Pages/About.cshtml", "/About")
                {
                    Selectors =
                    {
                        CreateSelectorModel("About"),
                    }
                }
            };

            // Act
            conventions.AddPageRoute("/Index", "Different-Route");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Index", model.ViewEnginePath);
                    Assert.Collection(model.Selectors,
                        selector =>
                        {
                            Assert.Equal("Index", selector.AttributeRouteModel.Template);
                            Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
                        },
                        selector =>
                        {
                            Assert.Equal("", selector.AttributeRouteModel.Template);
                            Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
                        },
                        selector =>
                        {
                            Assert.Equal("Different-Route", selector.AttributeRouteModel.Template);
                            Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
                        });
                },
                model =>
                {
                    Assert.Equal("/About", model.ViewEnginePath);
                    Assert.Collection(model.Selectors,
                        selector =>
                        {
                            Assert.Equal("About", selector.AttributeRouteModel.Template);
                            Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
                        });
                });
        }

        [Fact]
        public void AddAreaPageRoute_AddsRouteToSelector()
        {
            // Arrange
            var conventions = GetConventions();
            var models = new[]
            {
                new PageRouteModel("/Pages/Profile.cshtml", "/Profile")
                {
                    Selectors =
                    {
                        CreateSelectorModel("Profile"),
                    }
                },
                new PageRouteModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts")
                {
                    Selectors =
                    {
                        CreateSelectorModel("Accounts/Profile"),
                    }
                }
            };

            // Act
            conventions.AddAreaPageRoute("Accounts", "/Profile", "Different-Route");
            ApplyConventions(conventions, models);

            // Assert
            Assert.Collection(models,
                model =>
                {
                    Assert.Equal("/Pages/Profile.cshtml", model.RelativePath);
                    Assert.Collection(model.Selectors,
                        selector =>
                        {
                            Assert.Equal("Profile", selector.AttributeRouteModel.Template);
                            Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
                        });
                },
                model =>
                {
                    Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
                    Assert.Collection(model.Selectors,
                        selector =>
                        {
                            Assert.Equal("Accounts/Profile", selector.AttributeRouteModel.Template);
                            Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
                        },
                        selector =>
                        {
                            Assert.Equal("Different-Route", selector.AttributeRouteModel.Template);
                            Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
                        });
                });
        }

        private PageConventionCollection GetConventions(bool enableEndpointRouting = true)
        {
            var options = new MvcOptions { EnableEndpointRouting = enableEndpointRouting };
            var serviceProvider = new ServiceCollection()
                .AddSingleton<IOptions<MvcOptions>>(Options.Options.Create(options))
                .BuildServiceProvider();
            return new PageConventionCollection(serviceProvider);
        }

        private static SelectorModel CreateSelectorModel(string template, bool suppressLinkGeneration = false)
        {
            return new SelectorModel
            {
                AttributeRouteModel = new AttributeRouteModel
                {
                    Template = template,
                    SuppressLinkGeneration = suppressLinkGeneration
                },
            };
        }

        private static void ApplyConventions(PageConventionCollection conventions, PageRouteModel[] models)
        {
            foreach (var convention in conventions.OfType<IPageRouteModelConvention>())
            {
                foreach (var model in models)
                {
                    convention.Apply(model);
                }
            }
        }
        private static void ApplyConventions(PageConventionCollection conventions, PageApplicationModel[] models)
        {
            foreach (var convention in conventions.OfType<IPageApplicationModelConvention>())
            {
                foreach (var model in models)
                {
                    convention.Apply(model);
                }
            }
        }

        private PageApplicationModel CreateApplicationModel(string relativePath, string viewEnginePath, string areaName = null)
        {
            var descriptor = new PageActionDescriptor
            {
                ViewEnginePath = viewEnginePath,
                RelativePath = relativePath,
                AreaName = areaName,
            };

            return new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), new object[0]);
        }
    }
}
