diff options
| author | Leitwolf | 2026-10-29 16:34:29 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-29 16:34:29 +0300 |
| commit | 6b4e5e403875d3648be383395f1dad50f0d6503d (patch) | |
| tree | 673620dce10f2723d8badf68b3b30de19ad8e6e0 /apps/api-tests | |
| parent | 0c0bbeab246a94035b572ce47d3502de997ac58a (diff) | |
| parent | 3749e30da561652a226017d9360b84e34a8b74fc (diff) | |
| download | progmaweb-6b4e5e403875d3648be383395f1dad50f0d6503d.tar.gz | |
Merge pull request #50 from Progmasoft/feature/provision-reserved-accounts
Provision the reserved accounts on the server
Diffstat (limited to 'apps/api-tests')
| -rw-r--r-- | apps/api-tests/AccountCommandTests.cs | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/apps/api-tests/AccountCommandTests.cs b/apps/api-tests/AccountCommandTests.cs new file mode 100644 index 0000000..46c8e31 --- /dev/null +++ b/apps/api-tests/AccountCommandTests.cs @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +using Microsoft.Extensions.DependencyInjection; +using Progmasoft.Progmaweb.Api.Accounts; + +namespace Progmasoft.Progmaweb.Api.Tests; + +/// <summary>The operations command that provisions the reserved accounts.</summary> +public sealed class AccountCommandTests +{ + private const string Password = "a long test passphrase"; + + private sealed class Input(bool terminal, params string?[] lines) : IPasswordInput + { + private readonly Queue<string?> remaining = new(lines); + + public bool IsTerminal { get; } = terminal; + + public List<string> Prompts { get; } = []; + + public string? Read(string prompt) + { + Prompts.Add(prompt); + return remaining.Count > 0 ? remaining.Dequeue() : null; + } + } + + private static (ServiceProvider Services, InMemoryAccountStore Store) Services() + { + InMemoryAccountStore store = new(); + ServiceProvider services = new ServiceCollection() + .AddSingleton<IAccountStore>(store) + .AddSingleton(TimeProvider.System) + .AddSingleton<AccountService>() + .BuildServiceProvider(); + return (services, store); + } + + private static async Task<(int Code, string Output, string Errors)> RunAsync( + IServiceProvider services, + IPasswordInput input, + params string[] args) + { + StringWriter output = new(); + StringWriter errors = new(); + int code = await AccountCommand.RunAsync( + services, args, input, output, errors, TestContext.Current.CancellationToken); + return (code, output.ToString(), errors.ToString()); + } + + [Fact] + public async Task AReservedAccountIsCreatedWithThePasswordTypedTwice() + { + (ServiceProvider services, InMemoryAccountStore store) = Services(); + Input input = new(terminal: true, Password, Password); + + (int code, string output, _) = await RunAsync( + services, input, "provision-reserved", "Progmasoft", "support@progmasoft.com"); + + Assert.Equal(0, code); + Assert.Contains("Created the reserved account Progmasoft", output, StringComparison.Ordinal); + Assert.DoesNotContain(Password, output, StringComparison.Ordinal); + Assert.Equal(["Password: ", "Password again: "], input.Prompts); + AccountRecord? account = await store.FindByAccountNameAsync("PROGMASOFT", TestContext.Current.CancellationToken); + Assert.NotNull(account); + Assert.Equal("Progmasoft", account.AccountName); + // The password works for signing in. + AccountRecord? verified = await services.GetRequiredService<AccountService>().VerifyPasswordAsync( + "support@progmasoft.com", Password, TestContext.Current.CancellationToken); + Assert.Equal(account.Id, verified?.Id); + } + + [Fact] + public async Task OnlyReservedNamesAreProvisioned() + { + (ServiceProvider services, _) = Services(); + (int code, _, string errors) = await RunAsync( + services, new Input(terminal: false, Password), "provision-reserved", "Somebody1", "a@example.com"); + Assert.Equal(1, code); + Assert.Contains("is not a reserved name", errors, StringComparison.Ordinal); + } + + [Fact] + public async Task DifferentPasswordsCreateNothing() + { + (ServiceProvider services, InMemoryAccountStore store) = Services(); + (int code, _, string errors) = await RunAsync( + services, new Input(terminal: true, Password, Password + "!"), "provision-reserved", "Leitwolf", + "owner@example.com"); + Assert.Equal(1, code); + Assert.Contains("differ", errors, StringComparison.Ordinal); + Assert.Null(await store.FindByAccountNameAsync("LEITWOLF", TestContext.Current.CancellationToken)); + } + + [Fact] + public async Task AWeakPasswordOrAnExistingAccountIsRefused() + { + (ServiceProvider services, _) = Services(); + (int weak, _, string weakErrors) = await RunAsync( + services, new Input(terminal: false, "short"), "provision-reserved", "Progmasoft", "support@progmasoft.com"); + Assert.Equal(1, weak); + Assert.Contains("at least 12 characters", weakErrors, StringComparison.Ordinal); + + Assert.Equal(0, (await RunAsync( + services, new Input(terminal: false, Password), "provision-reserved", "Progmasoft", + "support@progmasoft.com")).Code); + (int again, _, string againErrors) = await RunAsync( + services, new Input(terminal: false, Password), "provision-reserved", "PROGMASOFT", "other@progmasoft.com"); + Assert.Equal(1, again); + Assert.Contains("exists already", againErrors, StringComparison.Ordinal); + } + + [Fact] + public async Task PublicRegistrationStillRefusesReservedNames() + { + Assert.False(AccountNamePolicy.TryNormalize("Progmasoft", out _, out string error)); + Assert.Equal("That account name is reserved.", error); + (int code, _, string errors) = await RunAsync(Services().Services, new Input(terminal: false), "provision"); + Assert.Equal(2, code); + Assert.Contains("usage", errors, StringComparison.Ordinal); + } +} |