Skip to content

Commit

Permalink
Add tests for status code handling
Browse files Browse the repository at this point in the history
  • Loading branch information
myieye committed Nov 8, 2023
1 parent 535515b commit 22c5f0a
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 72 deletions.
8 changes: 8 additions & 0 deletions backend/LexBoxApi/Controllers/AuthTestingController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using LexBoxApi.Auth;
using LexCore.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace LexBoxApi.Controllers;
Expand Down Expand Up @@ -27,4 +28,11 @@ public OkResult RequiresForgotPasswordAudience()
{
return Ok();
}

[HttpGet("403")]
[AllowAnonymous]
public ForbidResult Forbidden()
{
return Forbid();
}
}
15 changes: 13 additions & 2 deletions backend/Testing/Browser/Base/PageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ await Context.Tracing.StartAsync(new()
{
DeferredExceptions.Add(new UnexpectedResponseException(response));
}
else if (response.Request.IsNavigationRequest && response.Status >= (int)HttpStatusCode.BadRequest)
{
// 400s are client errors that our tests shouldn't trigger under normal circumstances.
// And if they're navigation requests SvelteKit/our UI might never see them (e.g. /api/*)
// i.e. they won't be handled well i.e. we don't like them.
DeferredExceptions.Add(new UnexpectedResponseException(response));
}
};
}

Expand Down Expand Up @@ -102,18 +109,22 @@ public async Task LoginAs(string user, string password)
.ShouldContainKey("Set-Cookie");
var cookies = responseMessage.Headers.GetValues("Set-Cookie").ToArray();
cookies.ShouldNotBeEmpty();
await SetCookies(cookies);
}

protected async Task SetCookies(string[] cookies)
{
var cookieContainer = new CookieContainer();
foreach (var cookie in cookies)
{
cookieContainer.SetCookies(new($"{TestingEnvironmentVariables.ServerBaseUrl}"), cookie);
}

await Context.AddCookiesAsync(cookieContainer.GetAllCookies()
.Select(cookie => new Microsoft.Playwright.Cookie
{
Value = cookie.Value,
Domain = cookie.Domain,
Expires = (float)cookie.Expires.Subtract(DateTime.UnixEpoch).TotalSeconds,
Expires = cookie.Expires == default ? null : (float)cookie.Expires.Subtract(DateTime.UnixEpoch).TotalSeconds,
Name = cookie.Name,
Path = cookie.Path,
Secure = cookie.Secure,
Expand Down
187 changes: 187 additions & 0 deletions backend/Testing/Browser/ErrorHandlingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using LexBoxApi.Auth;
using Microsoft.Playwright;
using Shouldly;
using Testing.Browser.Base;
using Testing.Browser.Page;
using Testing.Browser.Page.External;
using Testing.Services;

namespace Testing.Browser;

[Trait("Category", "Integration")]
public class ErrorHandlingTests : PageTest
{
[Fact]
public async Task CatchGoto500InSameTab()
{
await new SandboxPage(Page).Goto();
await Page.RunAndWaitForResponseAsync(async () =>
{
await Page.GetByText("Goto API 500", new() { Exact = true }).ClickAsync();
}, "/api/testing/test500NoException");
ExpectDeferredException();
}

[Fact]
public async Task CatchGoto500InNewTab()
{
await new SandboxPage(Page).Goto();
await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Goto API 500 new tab").ClickAsync();
});
ExpectDeferredException();
}

[Fact]
public async Task CatchPageLoad500()
{
await new SandboxPage(Page).Goto();
await Page.GetByText("Goto page load 500").ClickAsync();
ExpectDeferredException();
await Expect(Page.Locator(":text-matches('Unexpected response:.*(500)', 'g')").First).ToBeVisibleAsync();
}

[Fact]
public async Task PageLoad500InNewTabLandsOnErrorPage()
{
await new SandboxPage(Page).Goto();
var newPage = await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Goto page load 500").ClickAsync(new()
{
Modifiers = new[] { KeyboardModifier.Control },
});
});
await Expect(newPage.Locator(":text-matches('Unexpected response:.*(500)', 'g')").First).ToBeVisibleAsync();
}

[Fact]
public async Task CatchFetch500AndErrorDialog()
{
await new SandboxPage(Page).Goto();
await Page.RunAndWaitForResponseAsync(async () =>
{
await Page.GetByText("Fetch 500").ClickAsync();
}, "/api/testing/test500NoException");
ExpectDeferredException();
await Expect(Page.Locator(".modal-box.bg-error:text-matches('Unexpected response:.*(500)', 'g')")).ToBeVisibleAsync();
}

[Fact]
public async Task ServerPageLoad403IsRedirectedToLogin()
{
await SetCookies(new[] { $"{AuthKernel.AuthCookieName}={TestConstants.InvalidJwt}" });
await new UserDashboardPage(Page).Goto(new() { ExpectRedirect = true });
await new LoginPage(Page).WaitFor();
}

[Fact]
public async Task ClientPageLoad403IsRedirectedToLogin()
{
await LoginAs("admin", TestingEnvironmentVariables.DefaultPassword);
var adminDashboardPage = await new AdminDashboardPage(Page).Goto();

await SetCookies(new[] { $"{AuthKernel.AuthCookieName}={TestConstants.InvalidJwt}" });

var response = await Page.RunAndWaitForResponseAsync(async () =>
{
await adminDashboardPage.ClickProject("Sena 3");
}, "/api/graphql");

response.Status.ShouldBe(401);
await new LoginPage(Page).WaitFor();
}

[Fact]
public async Task CatchGoto403InSameTab()
{

await new SandboxPage(Page).Goto();
await Page.RunAndWaitForResponseAsync(async () =>
{
await Page.GetByText("Goto API 403", new() { Exact = true }).ClickAsync();
}, "/api/AuthTesting/403");
ExpectDeferredException();
}

[Fact]
public async Task CatchGoto403InNewTab()
{
await new SandboxPage(Page).Goto();
await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Goto API 403 new tab").ClickAsync();
});
ExpectDeferredException();
}

[Fact]
public async Task PageLoad403IsRedirectedToHome()
{
await LoginAs("manager", TestingEnvironmentVariables.DefaultPassword);
await new SandboxPage(Page).Goto();
await Page.GetByText("Goto page load 403").ClickAsync();
await new UserDashboardPage(Page).WaitFor();
}

[Fact]
public async Task PageLoad403InNewTabIsRedirectedToHome()
{
await LoginAs("manager", TestingEnvironmentVariables.DefaultPassword);
await new SandboxPage(Page).Goto();
var newPage = await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Goto page load 403").ClickAsync(new()
{
Modifiers = new[] { KeyboardModifier.Control },
});
});
await new UserDashboardPage(newPage).WaitFor();
}

[Fact]
public async Task PageLoad403OnHomePageIsRedirectedToLogin()
{
// (1) Get JWT with only forgot-password audience
// - Register
var mailinatorId = Guid.NewGuid().ToString();
var email = $"{mailinatorId}@mailinator.com";
var password = email;
await using var userDashboardPage = await RegisterUser($"Test: {nameof(PageLoad403OnHomePageIsRedirectedToLogin)} - {mailinatorId}", email, password);

// - Request forgot password email
var loginPage = await Logout();
var forgotPasswordPage = await loginPage.ClickForgotPassword();
await forgotPasswordPage.FillForm(email);
await forgotPasswordPage.Submit();

// - Get JWT from reset password link
var inboxPage = await MailInboxPage.Get(Page, mailinatorId).Goto();
var emailPage = await inboxPage.OpenEmail();
var href = await emailPage.ResetPasswordButton.GetAttributeAsync("href");
var forgotPasswordJwt = href.Split("jwt=")[1].Split("&")[0];

// (2) Get to a non-home page with an empty urql cache
await LoginAs(email, password);
var userAccountPage = await new UserAccountSettingsPage(Page).Goto();

// (3) Update cookie with the reset-password audience JWT and try to go home
await SetCookies(new[] { $"{AuthKernel.AuthCookieName}={forgotPasswordJwt}" });

var response = await Page.RunAndWaitForResponseAsync(userAccountPage.GoHome, "/api/graphql");
response.Status.ShouldBe(403);

// (4) Expect to be redirected to login page
await new LoginPage(Page).WaitFor();
}

[Fact]
public async Task NodeSurvivesCorruptJwt()
{
var corruptJwt = "bla-bla-bla";
await SetCookies(new[] { $"{AuthKernel.AuthCookieName}={corruptJwt}" });
await new UserDashboardPage(Page).Goto(new() { ExpectRedirect = true });
await new LoginPage(Page).WaitFor();
}
}
9 changes: 7 additions & 2 deletions backend/Testing/Browser/Page/AdminDashboardPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ public AdminDashboardPage(IPage page)

public async Task<ProjectPage> OpenProject(string projectName, string projectCode)
{
var projectTable = Page.Locator("table").Nth(0);
await projectTable.GetByRole(AriaRole.Link, new() { Name = projectName, Exact = true}).ClickAsync();
await ClickProject(projectName);
return await new ProjectPage(Page, projectName, projectCode).WaitFor();
}

public async Task ClickProject(string projectName)
{
var projectTable = Page.Locator("table").Nth(0);
await projectTable.GetByRole(AriaRole.Link, new() { Name = projectName, Exact = true }).ClickAsync();
}
}
3 changes: 1 addition & 2 deletions backend/Testing/Browser/Page/AuthenticatedBasePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ public AuthenticatedBasePage(IPage page, string url, ILocator testLocator)
EmailVerificationAlert = new EmailVerificationAlert(Page);
}

public async Task<UserDashboardPage> GoHome()
public async Task GoHome()
{
await Page.Locator(".breadcrumbs").GetByRole(AriaRole.Link, new() { Name = "Home" }).ClickAsync();
return await new UserDashboardPage(Page).WaitFor();
}
}
12 changes: 10 additions & 2 deletions backend/Testing/Browser/Page/BasePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Testing.Browser.Page;

public record GotoOptions(bool? ExpectRedirect = false);

public abstract class BasePage<T> where T : BasePage<T>
{
public IPage Page { get; private set; }
Expand All @@ -22,7 +24,7 @@ public BasePage(IPage page, string? url, ILocator[] testLocators)
TestLocators = testLocators;
}

public virtual async Task<T> Goto()
public virtual async Task<T> Goto(GotoOptions? options = null)
{
if (Url is null)
{
Expand All @@ -31,7 +33,13 @@ public virtual async Task<T> Goto()

var response = await Page.GotoAsync(Url);
response?.Ok.ShouldBeTrue(); // is null if same URL, but different hash
return await WaitFor();

if (options?.ExpectRedirect != true)
{
await WaitFor();
}

return (T)this;
}

public async Task<T> WaitFor()
Expand Down
4 changes: 2 additions & 2 deletions backend/Testing/Browser/Page/External/MailDevPages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ protected override MailEmailPage GetEmailPage()
return new MailDevEmailPage(Page);
}

public override async Task<MailInboxPage> Goto()
public override async Task<MailInboxPage> Goto(GotoOptions? options = null)
{
await base.Goto();
await base.Goto(options);
await Page.Locator("input.search-input").FillAsync(MailboxId);
return this;
}
Expand Down
4 changes: 3 additions & 1 deletion backend/Testing/Browser/Page/External/MailPages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public abstract class MailEmailPage : BasePage<MailEmailPage>
{
protected readonly ILocator bodyLocator;

public ILocator ResetPasswordButton => bodyLocator.GetByRole(AriaRole.Link, new() { Name = "Reset password" });

public MailEmailPage(IPage page, string? url, ILocator bodyLocator) : base(page, url, bodyLocator)
{
this.bodyLocator = bodyLocator;
Expand All @@ -53,6 +55,6 @@ public Task ClickVerifyEmail()

public Task ClickResetPassword()
{
return bodyLocator.GetByRole(AriaRole.Link, new() { Name = "Reset password" }).ClickAsync();
return ResetPasswordButton.ClickAsync();
}
}
4 changes: 2 additions & 2 deletions backend/Testing/Browser/Page/External/MailinatorPages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ protected override MailEmailPage GetEmailPage()
return new MailinatorEmailPage(Page);
}

public override async Task<MailInboxPage> Goto()
public override async Task<MailInboxPage> Goto(GotoOptions? options = null)
{
Url = $"https://www.mailinator.com/v4/public/inboxes.jsp?to={MailboxId}";
return await base.Goto();
return await base.Goto(options);
}
}

Expand Down
2 changes: 1 addition & 1 deletion backend/Testing/Browser/Page/SandboxPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Testing.Browser.Page;

public class SandboxPage : BasePage<SandboxPage>
{
public SandboxPage(IPage page) : base(page, "/sandbox", page.Locator(":text('Sandbox')"))
public SandboxPage(IPage page) : base(page, "/sandbox", page.GetByRole(AriaRole.Heading, new() { Name = "Sandbox" }))
{
}
}
5 changes: 4 additions & 1 deletion backend/Testing/Browser/Page/TempUserDashboardPage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Playwright;
using Testing.Browser.Util;
using Testing.Services;

namespace Testing.Browser.Page;

Expand All @@ -14,6 +15,8 @@ public TempUserDashboardPage(IPage page, TempUser user) : base(page)

public async ValueTask DisposeAsync()
{
await Page.DeleteUser(User.Id);
var context = await Page.Context.Browser.NewContextAsync();
await context.APIRequest.LoginAs("admin", TestingEnvironmentVariables.DefaultPassword);
await context.APIRequest.DeleteUser(User.Id);
}
}
Loading

0 comments on commit 22c5f0a

Please sign in to comment.