diff options
| author | Leitwolf | 2026-10-03 20:12:23 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-03 20:12:23 +0300 |
| commit | 933244cc0fa149427d22d06be4d226c0a67e8b80 (patch) | |
| tree | 048ae623f49cc5f2f94234d747351f9e659f5267 /apps/api-tests | |
| parent | cf444eb3bccf99c10f59ed5330b61c0397e0aeaa (diff) | |
| parent | b84a2216c26cc5392d0dc300b7a605ccb288d8c8 (diff) | |
| download | progmaweb-933244cc0fa149427d22d06be4d226c0a67e8b80.tar.gz | |
Merge pull request #27 from Progmasoft/fix/api-without-google
Run the account API without a Google client
Diffstat (limited to 'apps/api-tests')
| -rw-r--r-- | apps/api-tests/AccountApiFactory.cs | 19 | ||||
| -rw-r--r-- | apps/api-tests/UnconfiguredGoogleTests.cs | 97 |
2 files changed, 112 insertions, 4 deletions
diff --git a/apps/api-tests/AccountApiFactory.cs b/apps/api-tests/AccountApiFactory.cs index 373cd84..89faa3e 100644 --- a/apps/api-tests/AccountApiFactory.cs +++ b/apps/api-tests/AccountApiFactory.cs @@ -7,19 +7,30 @@ using Microsoft.Extensions.Configuration; namespace Progmasoft.Progmaweb.Api.Tests; -internal sealed class AccountApiFactory : WebApplicationFactory<Program> +/// <summary>Starts the account API in memory for a test.</summary> +/// <param name="configureGoogle"> +/// Whether the Google client is configured. Without it the service has to run and refuse only Google sign-in. +/// </param> +internal sealed class AccountApiFactory(bool configureGoogle = true) : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Testing"); + + // The program decides while it registers services whether the Google scheme exists, so these two values + // have to be host settings: configuration sources added below are applied only when the host is built, + // after that decision. An empty value also overrides credentials the environment of the test run carries. + builder.UseSetting( + "Authentication:Google:ClientId", + configureGoogle ? "test-client.apps.googleusercontent.com" : string.Empty); + builder.UseSetting("Authentication:Google:ClientSecret", configureGoogle ? "test-secret" : string.Empty); + builder.ConfigureAppConfiguration((_, configuration) => { configuration.AddInMemoryCollection(new Dictionary<string, string?> { ["AccountSessions:LifetimeHours"] = "2", - ["AccountSessions:CookieName"] = "__Host-ProgmasoftSession", - ["Authentication:Google:ClientId"] = "test-client.apps.googleusercontent.com", - ["Authentication:Google:ClientSecret"] = "test-secret" + ["AccountSessions:CookieName"] = "__Host-ProgmasoftSession" }); }); } diff --git a/apps/api-tests/UnconfiguredGoogleTests.cs b/apps/api-tests/UnconfiguredGoogleTests.cs new file mode 100644 index 0000000..d4e8c53 --- /dev/null +++ b/apps/api-tests/UnconfiguredGoogleTests.cs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +using System.Net; +using System.Net.Http.Json; +using Progmasoft.Progmaweb.Api.Accounts; + +namespace Progmasoft.Progmaweb.Api.Tests; + +/// <summary> +/// The service without a Google client. It used to answer every request with 500, because the Google scheme was +/// registered with empty credentials and failed its option validation in the authentication middleware. +/// </summary> +public sealed class UnconfiguredGoogleTests +{ + [Theory] + [InlineData("/api/v1/status", HttpStatusCode.OK)] + [InlineData("/health", HttpStatusCode.OK)] + [InlineData("/api/v1/accounts/me", HttpStatusCode.Unauthorized)] + public async Task OrdinaryRoutesAnswerWithoutGoogle(string path, HttpStatusCode expected) + { + await using AccountApiFactory factory = new(configureGoogle: false); + using HttpClient client = factory.CreateAccountClient(); + + HttpResponseMessage response = await client.GetAsync(path, TestContext.Current.CancellationToken); + + Assert.Equal(expected, response.StatusCode); + } + + [Fact] + public async Task PasswordAccountsWorkWithoutGoogle() + { + await using AccountApiFactory factory = new(configureGoogle: false); + using HttpClient client = factory.CreateAccountClient(); + const string password = "a sufficiently long passphrase"; + + HttpResponseMessage registration = await client.PostAsJsonAsync( + "/api/v1/accounts/register", + new RegisterAccountRequest("LocalUser1", "local@example.com", password), + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.Created, registration.StatusCode); + + // The registration set the session cookie, so the same client is now signed in. + HttpResponseMessage me = await client.GetAsync("/api/v1/accounts/me", TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, me.StatusCode); + + HttpResponseMessage logout = await client.PostAsync( + "/api/v1/accounts/logout", null, TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.NoContent, logout.StatusCode); + + HttpResponseMessage login = await client.PostAsJsonAsync( + "/api/v1/accounts/login", + new LoginAccountRequest("local@example.com", password), + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.OK, login.StatusCode); + } + + [Fact] + public async Task GoogleStartReportsThatSignInIsUnavailable() + { + await using AccountApiFactory factory = new(configureGoogle: false); + using HttpClient client = factory.CreateAccountClient(handleCookies: false); + + HttpResponseMessage response = await client.GetAsync( + "/api/v1/accounts/oauth/google/start?accountName=GoogleUser", + TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode); + Assert.Null(response.Headers.Location); + } + + [Fact] + public async Task GoogleCompleteFailsClosedWithoutGoogle() + { + await using AccountApiFactory factory = new(configureGoogle: false); + using HttpClient client = factory.CreateAccountClient(handleCookies: false); + + HttpResponseMessage response = await client.GetAsync( + "/api/v1/accounts/oauth/google/complete", TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal("/login?google=failed", response.Headers.Location?.OriginalString); + Assert.False(response.Headers.Contains("Set-Cookie")); + } + + [Fact] + public async Task GoogleCallbackIsNotARouteWithoutGoogle() + { + await using AccountApiFactory factory = new(configureGoogle: false); + using HttpClient client = factory.CreateAccountClient(handleCookies: false); + + HttpResponseMessage response = await client.GetAsync( + "/api/v1/accounts/oauth/google/callback", TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} |