diff options
| author | Leitwolf | 2026-10-07 07:09:39 +0300 |
|---|---|---|
| committer | GitHub | 2026-10-07 07:09:39 +0300 |
| commit | 111a6175e0a92808ddc2a47088fa9858001eeed9 (patch) | |
| tree | 0668bcb5fb3f52c92e94e40280228bad546a7511 /apps/api-tests/PostgresTestDatabase.cs | |
| parent | 5d4a9699675c0c0b9cd84c67f423e6e0f836a5d1 (diff) | |
| parent | a1383b688d3d2159e3ba525682180f9d5a4a2c80 (diff) | |
| download | progmaweb-111a6175e0a92808ddc2a47088fa9858001eeed9.tar.gz | |
Merge pull request #28 from Progmasoft/feature/durable-account-store
Keep accounts and sessions in PostgreSQL
Diffstat (limited to 'apps/api-tests/PostgresTestDatabase.cs')
| -rw-r--r-- | apps/api-tests/PostgresTestDatabase.cs | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/apps/api-tests/PostgresTestDatabase.cs b/apps/api-tests/PostgresTestDatabase.cs new file mode 100644 index 0000000..401a69c --- /dev/null +++ b/apps/api-tests/PostgresTestDatabase.cs @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +using Npgsql; + +namespace Progmasoft.Progmaweb.Api.Tests; + +/// <summary>A schema of its own in the PostgreSQL database of the test run, removed when the test ends.</summary> +/// <remarks> +/// <para> +/// The tests that need PostgreSQL read its connection string from the environment variable +/// <see cref="VariableName"/>. The user it names must be allowed to create schemas in its database. Without the +/// variable those tests are skipped, and <see cref="PostgresConfigurationTests"/> fails the run where skipping +/// them would hide a gap: in continuous integration. +/// </para> +/// <para> +/// Each test works in a schema with a random name, selected through the search path of its connection string, so +/// tests neither see nor wait for one another and nothing is left behind. +/// </para> +/// </remarks> +internal sealed class PostgresTestDatabase : IAsyncDisposable +{ + /// <summary>Environment variable that holds the connection string of the test database.</summary> + public const string VariableName = "PROGMAWEB_TEST_POSTGRES"; + + private readonly string administrativeConnectionString; + private readonly string schema; + + private PostgresTestDatabase(string administrativeConnectionString, string schema) + { + this.administrativeConnectionString = administrativeConnectionString; + this.schema = schema; + ConnectionString = new NpgsqlConnectionStringBuilder(administrativeConnectionString) { SearchPath = schema } + .ConnectionString; + } + + /// <summary>The connection string of the test database, or <see langword="null"/> when none is configured.</summary> + public static string? ConfiguredConnectionString + { + get + { + string? value = Environment.GetEnvironmentVariable(VariableName); + return string.IsNullOrWhiteSpace(value) ? null : value; + } + } + + /// <summary>A connection string whose statements run in the schema of this test.</summary> + public string ConnectionString { get; } + + /// <summary>Creates the schema of a test; skips the test when no database is configured.</summary> + public static async Task<PostgresTestDatabase> CreateAsync() + { + string? configured = ConfiguredConnectionString; + Assert.SkipUnless(configured is not null, $"{VariableName} names no PostgreSQL database."); + + string schema = "test_" + Guid.NewGuid().ToString("N"); + await using NpgsqlDataSource dataSource = NpgsqlDataSource.Create(configured!); + await using NpgsqlCommand command = dataSource.CreateCommand($"CREATE SCHEMA {schema}"); + await command.ExecuteNonQueryAsync(TestContext.Current.CancellationToken); + return new PostgresTestDatabase(configured!, schema); + } + + /// <summary>Opens connections to the schema of this test. The caller disposes the data source.</summary> + public NpgsqlDataSource OpenDataSource() => NpgsqlDataSource.Create(ConnectionString); + + public async ValueTask DisposeAsync() + { + // Pooled connections of disposed data sources are closed, so nothing holds the schema. + await using NpgsqlDataSource dataSource = NpgsqlDataSource.Create(administrativeConnectionString); + await using NpgsqlCommand command = dataSource.CreateCommand($"DROP SCHEMA IF EXISTS {schema} CASCADE"); + await command.ExecuteNonQueryAsync(CancellationToken.None); + } +} + +public sealed class PostgresConfigurationTests +{ + [Fact] + public void ContinuousIntegrationRunsTheDatabaseTests() + { + // GitHub Actions sets CI. A run there without the database would report the PostgreSQL tests as skipped and + // the run as passed, which is how an untested store would be merged. + if (string.Equals(Environment.GetEnvironmentVariable("CI"), "true", StringComparison.OrdinalIgnoreCase)) + { + Assert.NotNull(PostgresTestDatabase.ConfiguredConnectionString); + } + } +} |