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

summaryrefslogtreecommitdiffhomepage
path: root/apps/api-tests
diff options
context:
space:
mode:
authorLeitwolf2026-11-07 11:43:30 +0300
committerGitHub2026-11-07 11:43:30 +0300
commitd28c8c216708c3bb59773722e6235c4c8cbb2b73 (patch)
tree00101b2c540df4f683a0aae890f8366f473f2be4 /apps/api-tests
parent097fb096966117fe259bc2cd721527bbec04e57d (diff)
parent5379c74171c0ef0d08c999ce93e2a03c37d25889 (diff)
downloadprogmaweb-d28c8c216708c3bb59773722e6235c4c8cbb2b73.tar.gz
Merge pull request #58 from Progmasoft/feature/policies-consent
Ask for the Privacy and Security Policies at registration
Diffstat (limited to 'apps/api-tests')
-rw-r--r--apps/api-tests/AccountEndpointTests.cs58
-rw-r--r--apps/api-tests/AccountServiceTests.cs64
-rw-r--r--apps/api-tests/AccountStoreContractTests.cs20
-rw-r--r--apps/api-tests/AccountStoreSelectionTests.cs9
-rw-r--r--apps/api-tests/OAuthTestClient.cs2
-rw-r--r--apps/api-tests/SessionLifetimeTests.cs2
-rw-r--r--apps/api-tests/UnconfiguredGoogleTests.cs2
7 files changed, 144 insertions, 13 deletions
diff --git a/apps/api-tests/AccountEndpointTests.cs b/apps/api-tests/AccountEndpointTests.cs
index c5c305e..50d43d5 100644
--- a/apps/api-tests/AccountEndpointTests.cs
+++ b/apps/api-tests/AccountEndpointTests.cs
@@ -22,7 +22,7 @@ public sealed class AccountEndpointTests
using HttpClient client = factory.CreateAccountClient(handleCookies: false);
HttpResponseMessage response = await client.GetAsync(
- "/api/v1/accounts/oauth/google/start?accountName=GoogleUser",
+ "/api/v1/accounts/oauth/google/start?accountName=GoogleUser&acceptPolicies=true",
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
@@ -62,6 +62,47 @@ public sealed class AccountEndpointTests
Assert.Equal("/register?google=invalid-account-name", response.Headers.Location?.OriginalString);
}
+ [Theory]
+ [InlineData("/api/v1/accounts/oauth/google/start?accountName=GoogleUser")]
+ [InlineData("/api/v1/accounts/oauth/google/start?accountName=GoogleUser&acceptPolicies=false")]
+ public async Task GoogleRegistrationWithoutThePoliciesStaysOnTheSite(string start)
+ {
+ await using AccountApiFactory factory = new();
+ using HttpClient client = factory.CreateAccountClient(handleCookies: false);
+
+ HttpResponseMessage response = await client.GetAsync(start, TestContext.Current.CancellationToken);
+
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal("/register?google=policies-required", response.Headers.Location?.OriginalString);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData(false)]
+ public async Task RegistrationWithoutThePoliciesIsRefused(bool? acceptPolicies)
+ {
+ await using AccountApiFactory factory = new();
+ using HttpClient client = factory.CreateAccountClient();
+
+ HttpResponseMessage response = await client.PostAsJsonAsync("/api/v1/accounts/register", new
+ {
+ accountName = "PolicyUser",
+ email = "policy@example.com",
+ password = "a-long-development-password",
+ acceptPolicies
+ }, TestContext.Current.CancellationToken);
+
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ using JsonDocument problem = JsonDocument.Parse(
+ await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken));
+ JsonElement messages = problem.RootElement.GetProperty("errors").GetProperty("acceptPolicies");
+ Assert.Equal([PolicyAcceptance.RequiredMessage], messages.EnumerateArray().Select(message => message.GetString()));
+
+ // The refusal kept neither the name nor the address.
+ HttpResponseMessage accepted = await RegisterAsync(client, "PolicyUser", "policy@example.com");
+ Assert.Equal(HttpStatusCode.Created, accepted.StatusCode);
+ }
+
[Fact]
public async Task GoogleCompleteDoesNotIssueASessionForAnExistingPasswordAccountWithTheSameEmail()
{
@@ -109,7 +150,8 @@ public sealed class AccountEndpointTests
{
accountName = "PrimaryUser",
email = "leitwolf1112@gmail.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, registration.StatusCode);
@@ -139,7 +181,8 @@ public sealed class AccountEndpointTests
{
accountName = "LogoutUser",
email = "logout@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, registration.StatusCode);
@@ -165,7 +208,8 @@ public sealed class AccountEndpointTests
{
accountName,
email = $"{Guid.NewGuid():N}@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
@@ -229,7 +273,8 @@ public sealed class AccountEndpointTests
HttpResponseMessage missing = await client.PostAsJsonAsync("/api/v1/accounts/login", new
{
email = "missing@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
HttpResponseMessage wrong = await client.PostAsJsonAsync("/api/v1/accounts/login", new
{
@@ -284,6 +329,7 @@ public sealed class AccountEndpointTests
{
accountName,
email,
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
}
diff --git a/apps/api-tests/AccountServiceTests.cs b/apps/api-tests/AccountServiceTests.cs
index 0ff7b33..27863bf 100644
--- a/apps/api-tests/AccountServiceTests.cs
+++ b/apps/api-tests/AccountServiceTests.cs
@@ -23,6 +23,7 @@ public sealed class AccountServiceTests
null,
"google@example.com",
"GoogleUser",
+ PolicyAcceptance.CurrentVersion,
sessions,
TestContext.Current.CancellationToken);
@@ -44,7 +45,8 @@ public sealed class AccountServiceTests
new RegisterAccountRequest(
"PasswordUser",
"unverified@example.com",
- "a-long-development-password"),
+ "a-long-development-password",
+ true),
sessions,
TestContext.Current.CancellationToken);
Assert.NotNull(registration);
@@ -54,6 +56,7 @@ public sealed class AccountServiceTests
"google-subject-victim",
"unverified@example.com",
null,
+ null,
sessions,
TestContext.Current.CancellationToken);
@@ -85,6 +88,7 @@ public sealed class AccountServiceTests
"google-subject-race",
"race@example.com",
"GoogleUser",
+ PolicyAcceptance.CurrentVersion,
sessions,
TestContext.Current.CancellationToken);
@@ -107,6 +111,7 @@ public sealed class AccountServiceTests
"google-subject-stable",
"google@example.com",
"GoogleUser",
+ PolicyAcceptance.CurrentVersion,
sessions,
TestContext.Current.CancellationToken);
@@ -115,6 +120,7 @@ public sealed class AccountServiceTests
"google-subject-stable",
"changed@example.com",
null,
+ null,
sessions,
TestContext.Current.CancellationToken);
@@ -125,6 +131,62 @@ public sealed class AccountServiceTests
Assert.Equal(first.Account.Id, second.Account.Id);
}
+ [Fact]
+ public async Task RegistrationKeepsTheVersionOfThePoliciesAndTheMoment()
+ {
+ InMemoryAccountStore store = new();
+ AccountService accounts = new(store, TimeProvider.System);
+ SessionService sessions = new(
+ store,
+ TimeProvider.System,
+ Options.Create(new AccountSessionOptions()));
+
+ (AuthenticatedAccount? password, _) = await accounts.RegisterAsync(
+ new RegisterAccountRequest("PasswordUser", "password@example.com", "a-long-development-password", true),
+ sessions,
+ TestContext.Current.CancellationToken);
+ (AuthenticatedAccount? google, _) = await accounts.AuthenticateGoogleAsync(
+ "google-subject-policies",
+ "google@example.com",
+ "GoogleUser",
+ PolicyAcceptance.CurrentVersion,
+ sessions,
+ TestContext.Current.CancellationToken);
+
+ foreach (AuthenticatedAccount? authentication in new[] { password, google })
+ {
+ Assert.NotNull(authentication);
+ Assert.Equal(PolicyAcceptance.CurrentVersion, authentication.Account.PoliciesVersion);
+ Assert.Equal(authentication.Account.CreatedAt, authentication.Account.PoliciesAcceptedAt);
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("2020-01-01")]
+ public async Task GoogleAuthenticationCreatesNoAccountWithoutTheCurrentPolicies(string? policies)
+ {
+ InMemoryAccountStore store = new();
+ AccountService accounts = new(store, TimeProvider.System);
+ SessionService sessions = new(
+ store,
+ TimeProvider.System,
+ Options.Create(new AccountSessionOptions()));
+
+ (AuthenticatedAccount? authentication, GoogleAccountFailure failure) =
+ await accounts.AuthenticateGoogleAsync(
+ "google-subject-new",
+ "new@example.com",
+ "GoogleUser",
+ policies,
+ sessions,
+ TestContext.Current.CancellationToken);
+
+ Assert.Null(authentication);
+ Assert.Equal(GoogleAccountFailure.PoliciesRequired, failure);
+ Assert.Null(await store.FindByGoogleSubjectAsync("google-subject-new", TestContext.Current.CancellationToken));
+ }
+
private sealed class ConcurrentPasswordRegistrationStore(AccountRecord passwordAccount) : IAccountStore
{
private bool createAttempted;
diff --git a/apps/api-tests/AccountStoreContractTests.cs b/apps/api-tests/AccountStoreContractTests.cs
index 2266273..f9f36be 100644
--- a/apps/api-tests/AccountStoreContractTests.cs
+++ b/apps/api-tests/AccountStoreContractTests.cs
@@ -65,6 +65,26 @@ public abstract class AccountStoreContractTests
Assert.Equal("AQAAAA-hash", found.PasswordHash);
Assert.Equal("Grace", found.AccountName);
Assert.Equal("grace@example.com", found.Email);
+ Assert.Null(found.PoliciesVersion);
+ Assert.Null(found.PoliciesAcceptedAt);
+ }
+
+ [Fact]
+ public async Task AnAccountKeepsThePoliciesItAccepted()
+ {
+ IAccountStore store = await CreateStoreAsync();
+ AccountRecord account = Account("Hopper", "hopper@example.com") with
+ {
+ PoliciesVersion = PolicyAcceptance.CurrentVersion,
+ PoliciesAcceptedAt = Moment
+ };
+
+ AccountRecord stored = Assert.IsType<AccountRecord>((await store.CreateAsync(account, Cancellation)).Account);
+
+ AccountRecord found = Assert.IsType<AccountRecord>(await store.FindByIdAsync(account.Id, Cancellation));
+ Assert.Equal(stored, found);
+ Assert.Equal(PolicyAcceptance.CurrentVersion, found.PoliciesVersion);
+ Assert.True((Moment - found.PoliciesAcceptedAt!.Value).Duration() < TimeSpan.FromMicroseconds(1));
}
[Fact]
diff --git a/apps/api-tests/AccountStoreSelectionTests.cs b/apps/api-tests/AccountStoreSelectionTests.cs
index 1087575..f1153dd 100644
--- a/apps/api-tests/AccountStoreSelectionTests.cs
+++ b/apps/api-tests/AccountStoreSelectionTests.cs
@@ -223,7 +223,8 @@ public sealed class DurableAccountApiTests
{
accountName = "Survivor",
email = "survivor@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, registration.StatusCode);
cookie = await SessionCookieAsync(registration);
@@ -244,7 +245,8 @@ public sealed class DurableAccountApiTests
HttpResponseMessage login = await restarted.PostAsJsonAsync("/api/v1/accounts/login", new
{
email = "survivor@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, login.StatusCode);
@@ -252,7 +254,8 @@ public sealed class DurableAccountApiTests
{
accountName = "survivor",
email = "other@example.com",
- password = "a-long-development-password"
+ password = "a-long-development-password",
+ acceptPolicies = true
}, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, again.StatusCode);
diff --git a/apps/api-tests/OAuthTestClient.cs b/apps/api-tests/OAuthTestClient.cs
index a176c04..a5d342d 100644
--- a/apps/api-tests/OAuthTestClient.cs
+++ b/apps/api-tests/OAuthTestClient.cs
@@ -37,7 +37,7 @@ internal sealed class OAuthTestClient(AccountApiFactory factory)
{
using HttpClient accounts = factory.CreateAccountClient(handleCookies: false);
HttpResponseMessage registered = await accounts.PostAsJsonAsync("/api/v1/accounts/register",
- new { accountName, email, password = "correct horse battery staple" }, Cancel);
+ new { accountName, email, password = "correct horse battery staple", acceptPolicies = true }, Cancel);
Assert.Equal(HttpStatusCode.Created, registered.StatusCode);
// The console knows the account of the Progmasoft account session; it has no sign-in of its own.
string session = registered.Headers.GetValues("Set-Cookie")
diff --git a/apps/api-tests/SessionLifetimeTests.cs b/apps/api-tests/SessionLifetimeTests.cs
index 8626186..aa9d9ea 100644
--- a/apps/api-tests/SessionLifetimeTests.cs
+++ b/apps/api-tests/SessionLifetimeTests.cs
@@ -22,7 +22,7 @@ public sealed class SessionLifetimeTests
AccountService accounts = new(store, clock);
SessionService sessions = new(store, clock, Options.Create(options));
(AuthenticatedAccount? registration, _) = await accounts.RegisterAsync(
- new RegisterAccountRequest("LifetimeUser", "lifetime@example.com", "a-long-development-password"),
+ new RegisterAccountRequest("LifetimeUser", "lifetime@example.com", "a-long-development-password", true),
sessions,
TestContext.Current.CancellationToken);
return Assert.IsType<AuthenticatedAccount>(registration).ExpiresAt;
diff --git a/apps/api-tests/UnconfiguredGoogleTests.cs b/apps/api-tests/UnconfiguredGoogleTests.cs
index d4e8c53..9a7ca77 100644
--- a/apps/api-tests/UnconfiguredGoogleTests.cs
+++ b/apps/api-tests/UnconfiguredGoogleTests.cs
@@ -36,7 +36,7 @@ public sealed class UnconfiguredGoogleTests
HttpResponseMessage registration = await client.PostAsJsonAsync(
"/api/v1/accounts/register",
- new RegisterAccountRequest("LocalUser1", "local@example.com", password),
+ new RegisterAccountRequest("LocalUser1", "local@example.com", password, true),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Created, registration.StatusCode);