diff --git a/src/Aspire.Dashboard/Components/Pages/Login.razor b/src/Aspire.Dashboard/Components/Pages/Login.razor index 06470643491..06d7b8b137a 100644 --- a/src/Aspire.Dashboard/Components/Pages/Login.razor +++ b/src/Aspire.Dashboard/Components/Pages/Login.razor @@ -31,7 +31,7 @@
@Loc[nameof(Dashboard.Resources.Login.WhereIsMyTokenLinkText)] - @Loc[nameof(Dashboard.Resources.Login.LogInButtonText)] + @Loc[nameof(Dashboard.Resources.Login.LogInButtonText)]
diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs index 2fbd1e5e353..1de4e34e063 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Dashboard.Resources; +using Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; using Aspire.Workload.Tests; using Microsoft.Playwright; using Xunit; @@ -9,10 +10,10 @@ namespace Aspire.Dashboard.Tests.Integration.Playwright; [ActiveIssue("https://github.com/dotnet/aspire/issues/4623", typeof(PlaywrightProvider), nameof(PlaywrightProvider.DoesNotHavePlaywrightSupport))] -public class AppBarTests : PlaywrightTestsBase +public class AppBarTests : PlaywrightTestsBase { - public AppBarTests(DashboardServerFixture dashboardServerFixture, PlaywrightFixture playwrightFixture) - : base(dashboardServerFixture, playwrightFixture) + public AppBarTests(DashboardServerFixture dashboardServerFixture) + : base(dashboardServerFixture) { } diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs new file mode 100644 index 00000000000..e48b2c49689 --- /dev/null +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Configuration; +using Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; +using Aspire.Hosting; +using Aspire.Workload.Tests; +using Microsoft.Playwright; +using Xunit; + +namespace Aspire.Dashboard.Tests.Integration.Playwright; + +[ActiveIssue("https://github.com/dotnet/aspire/issues/4623", typeof(PlaywrightProvider), nameof(PlaywrightProvider.DoesNotHavePlaywrightSupport))] +public class BrowserTokenAuthenticationTests : PlaywrightTestsBase +{ + public class BrowserTokenDashboardServerFixture : DashboardServerFixture + { + public BrowserTokenDashboardServerFixture() + { + Configuration[DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.BrowserToken); + Configuration[DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey] = "VALID_TOKEN"; + } + } + + public BrowserTokenAuthenticationTests(BrowserTokenDashboardServerFixture dashboardServerFixture) + : base(dashboardServerFixture) + { + } + + [Fact] + public async Task BrowserToken_LoginPage_Success_RedirectToResources() + { + // Arrange + await RunTestAsync(async page => + { + // Act + var response = await page.GotoAsync("/"); + var uri = new Uri(response!.Url); + + Assert.Equal("/login?returnUrl=%2F", uri.PathAndQuery); + + var tokenTextBox = page.GetByRole(AriaRole.Textbox); + await tokenTextBox.FillAsync("VALID_TOKEN"); + + var submitButton = page.GetByRole(AriaRole.Button); + await submitButton.ClickAsync(); + + // Assert + await Assertions + .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) + .ToBeVisibleAsync(); + }); + } + + [Fact] + public async Task BrowserToken_LoginPage_Failure_DisplayFailureMessage() + { + // Arrange + await RunTestAsync(async page => + { + // Act + var response = await page.GotoAsync("/"); + var uri = new Uri(response!.Url); + + Assert.Equal("/login?returnUrl=%2F", uri.PathAndQuery); + + var tokenTextBox = page.GetByRole(AriaRole.Textbox); + await tokenTextBox.FillAsync("INVALID_TOKEN"); + + var submitButton = page.GetByRole(AriaRole.Button); + await submitButton.ClickAsync(); + + // Assert + await Assertions + .Expect(page.GetByText("Invalid token")) + .ToBeVisibleAsync(); + }); + } + + [Fact] + public async Task BrowserToken_QueryStringToken_Success_RestrictToResources() + { + // Arrange + await RunTestAsync(async page => + { + // Act + await page.GotoAsync("/login?t=VALID_TOKEN"); + + // Assert + await Assertions + .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) + .ToBeVisibleAsync(); + }); + } + + [Fact] + public async Task BrowserToken_QueryStringToken_Failure_DisplayLoginPage() + { + // Arrange + await RunTestAsync(async page => + { + // Act + await page.GotoAsync("/login?t=INVALID_TOKEN"); + + var submitButton = page.GetByRole(AriaRole.Button); + var name = await submitButton.GetAttributeAsync("name"); + + // Assert + Assert.Equal("submit-token", name); + }); + } +} diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/DashboardServerFixture.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/DashboardServerFixture.cs similarity index 73% rename from tests/Aspire.Dashboard.Tests/Integration/Playwright/DashboardServerFixture.cs rename to tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/DashboardServerFixture.cs index 0da0361eaa6..d508656c8bc 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/DashboardServerFixture.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/DashboardServerFixture.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; @@ -11,28 +11,40 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Xunit; -namespace Aspire.Dashboard.Tests.Integration.Playwright; +namespace Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; public class DashboardServerFixture : IAsyncLifetime { + public Dictionary Configuration { get; } + public DashboardWebApplication DashboardApp { get; private set; } = null!; - public Task InitializeAsync() + // Can't have multiple fixtures when one is generic. Workaround by nesting playwright fixture. + public PlaywrightFixture PlaywrightFixture { get; } + + public DashboardServerFixture() { - const string aspireDashboardAssemblyName = "Aspire.Dashboard"; - var currentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name!; - var currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - var aspireAssemblyDirectory = currentAssemblyDirectory.Replace(currentAssemblyName, aspireDashboardAssemblyName); + PlaywrightFixture = new PlaywrightFixture(); - var initialData = new Dictionary + Configuration = new Dictionary { [DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = "http://127.0.0.1:0", [DashboardConfigNames.DashboardOtlpHttpUrlName.ConfigKey] = "http://127.0.0.1:0", [DashboardConfigNames.DashboardOtlpAuthModeName.ConfigKey] = nameof(OtlpAuthMode.Unsecured), [DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.Unsecured) }; + } + + public async Task InitializeAsync() + { + await PlaywrightFixture.InitializeAsync(); + + const string aspireDashboardAssemblyName = "Aspire.Dashboard"; + var currentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name!; + var currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + var aspireAssemblyDirectory = currentAssemblyDirectory.Replace(currentAssemblyName, aspireDashboardAssemblyName); - var config = new ConfigurationManager().AddInMemoryCollection(initialData).Build(); + var config = new ConfigurationManager().AddInMemoryCollection(Configuration).Build(); // Add services to the container. DashboardApp = new DashboardWebApplication( @@ -53,11 +65,12 @@ public Task InitializeAsync() builder.Services.AddSingleton(); }); - return DashboardApp.StartAsync(); + await DashboardApp.StartAsync(); } - public Task DisposeAsync() + public async Task DisposeAsync() { - return DashboardApp.DisposeAsync().AsTask(); + await DashboardApp.DisposeAsync(); + await PlaywrightFixture.DisposeAsync(); } } diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/MockDashboardClient.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs similarity index 95% rename from tests/Aspire.Dashboard.Tests/Integration/Playwright/MockDashboardClient.cs rename to tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs index e330a07b8c1..23c29fa49f4 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/MockDashboardClient.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs @@ -6,7 +6,7 @@ using Aspire.Dashboard.Model; using Google.Protobuf.WellKnownTypes; -namespace Aspire.Dashboard.Tests.Integration.Playwright; +namespace Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; public sealed class MockDashboardClient : IDashboardClient { @@ -18,7 +18,7 @@ public sealed class MockDashboardClient : IDashboardClient CreationTimeStamp = DateTime.Now, Environment = ImmutableArray.Empty, ResourceType = KnownResourceTypes.Project, - Properties = new [] + Properties = new[] { new KeyValuePair(KnownProperties.Project.Path, new Value() { diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightFixture.cs similarity index 93% rename from tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs rename to tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightFixture.cs index 944cec5f305..35b0ec1502a 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightFixture.cs @@ -5,7 +5,7 @@ using Microsoft.Playwright; using Xunit; -namespace Aspire.Dashboard.Tests.Integration.Playwright; +namespace Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; public class PlaywrightFixture : IAsyncLifetime { diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightTestsBase.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightTestsBase.cs similarity index 74% rename from tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightTestsBase.cs rename to tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightTestsBase.cs index f4995b7de3e..bf342d3d47a 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightTestsBase.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/PlaywrightTestsBase.cs @@ -1,22 +1,23 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Playwright; using Xunit; -namespace Aspire.Dashboard.Tests.Integration.Playwright; +namespace Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; -public class PlaywrightTestsBase : IClassFixture, IClassFixture, IAsyncDisposable +public class PlaywrightTestsBase : IClassFixture, IAsyncDisposable + where TDashboardServerFixture : DashboardServerFixture { public DashboardServerFixture DashboardServerFixture { get; } public PlaywrightFixture PlaywrightFixture { get; } private IBrowserContext? _context; - public PlaywrightTestsBase(DashboardServerFixture dashboardServerFixture, PlaywrightFixture playwrightFixture) + public PlaywrightTestsBase(DashboardServerFixture dashboardServerFixture) { DashboardServerFixture = dashboardServerFixture; - PlaywrightFixture = playwrightFixture; + PlaywrightFixture = dashboardServerFixture.PlaywrightFixture; } public async Task RunTestAsync(Func test)