Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/api-tests
diff options
context:
space:
mode:
authorLeitwolf2026-10-03 20:12:23 +0300
committerGitHub2026-10-03 20:12:23 +0300
commit933244cc0fa149427d22d06be4d226c0a67e8b80 (patch)
tree048ae623f49cc5f2f94234d747351f9e659f5267 /apps/api-tests
parentcf444eb3bccf99c10f59ed5330b61c0397e0aeaa (diff)
parentb84a2216c26cc5392d0dc300b7a605ccb288d8c8 (diff)
downloadprogmaweb-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.cs19
-rw-r--r--apps/api-tests/UnconfiguredGoogleTests.cs97
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);
+ }
+}