got saved a RdvQuery
This commit is contained in:
parent
67715f9d67
commit
cbb59f019d
17 changed files with 616 additions and 288 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yavsc.Api.Test.Fixtures;
|
||||
using Yavsc.Helpers;
|
||||
|
|
@ -101,6 +102,58 @@ public sealed class BillingControllerTests : IClassFixture<ApiWebServerFixture>
|
|||
Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProviderOngoingCommands_ignores_rows_with_invalid_discriminator()
|
||||
{
|
||||
WorkflowHelpers.ConfigureBillingService();
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
|
||||
using (var scope = _fixture.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
db.Database.ExecuteSqlInterpolated($@"
|
||||
INSERT INTO ""NominativeServiceCommand""
|
||||
(""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"")
|
||||
VALUES
|
||||
({"dev"}, {"bob"}, {true}, {DateTime.UtcNow.AddMinutes(-5)}, {DateTime.UtcNow.AddMinutes(-4)}, {"Legacy malformed row"}, {""}, {"alice"}, {(int)QueryStatus.Accepted}, {"alice"}, {"alice"});
|
||||
");
|
||||
|
||||
var location = db.Locations.Single();
|
||||
db.RdvQueries.Add(new RdvQuery
|
||||
{
|
||||
ActivityCode = "dev",
|
||||
ClientId = "bob",
|
||||
PerformerId = "alice",
|
||||
Consent = true,
|
||||
UserCreated = "alice",
|
||||
UserModified = "alice",
|
||||
DateCreated = DateTime.UtcNow.AddMinutes(-3),
|
||||
DateModified = DateTime.UtcNow.AddMinutes(-2),
|
||||
EventDate = DateTime.UtcNow.AddDays(1),
|
||||
Location = location,
|
||||
Reason = "Commande valide",
|
||||
Status = QueryStatus.InProgress,
|
||||
Description = "Commande fournisseur valide",
|
||||
});
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
using var http = NewClient();
|
||||
|
||||
var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken);
|
||||
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}");
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<List<ProviderOngoingCommandDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(payload);
|
||||
Assert.NotEmpty(payload!);
|
||||
Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv && item.PerformerId == "alice");
|
||||
Assert.DoesNotContain(payload!, item => string.IsNullOrWhiteSpace(item.BillingCode));
|
||||
}
|
||||
|
||||
private sealed class ProviderOngoingCommandDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using Microsoft.Data.Sqlite;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Npgsql;
|
||||
using System.Runtime.Loader;
|
||||
using Yavsc.Controllers;
|
||||
using Yavsc.Interfaces.Workflow;
|
||||
using Yavsc.Models;
|
||||
|
|
@ -18,27 +20,45 @@ namespace Yavsc.Api.Test.Fixtures;
|
|||
|
||||
public sealed class ApiWebServerFixture : WebHostFixture
|
||||
{
|
||||
private const string DbProviderEnvVar = "YAVSC_API_TEST_DB_PROVIDER";
|
||||
private const string NpgsqlAdminConnectionEnvVar = "YAVSC_API_TEST_NPGSQL_ADMIN_CONNECTION";
|
||||
private const string DefaultDevelopmentConnectionString = "Server=localhost;Port=5432;Database=yavscdev;Username=yavscdev;Password=8*5idas;Include Error Detail=true";
|
||||
|
||||
protected override int HttpsPort => 5104;
|
||||
|
||||
private static SqliteConnection? _sharedSqliteConnection;
|
||||
private static readonly object _sqliteLock = new();
|
||||
private static readonly object _npgsqlLock = new();
|
||||
private static string? _sharedNpgsqlConnectionString;
|
||||
private static string? _sharedNpgsqlAdminConnectionString;
|
||||
private static string? _sharedNpgsqlDatabaseName;
|
||||
private static bool _npgsqlCleanupRegistered;
|
||||
|
||||
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
||||
{
|
||||
SqliteConnection sharedConnection;
|
||||
lock (_sqliteLock)
|
||||
if (UseNpgsqlProvider())
|
||||
{
|
||||
if (_sharedSqliteConnection is null)
|
||||
{
|
||||
_sharedSqliteConnection = new SqliteConnection(
|
||||
"Data Source=YavscApiTests;Mode=Memory;Cache=Shared");
|
||||
_sharedSqliteConnection.Open();
|
||||
}
|
||||
sharedConnection = _sharedSqliteConnection;
|
||||
var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated();
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
||||
opt.UseNpgsql(npgsqlConnectionString));
|
||||
}
|
||||
else
|
||||
{
|
||||
SqliteConnection sharedConnection;
|
||||
lock (_sqliteLock)
|
||||
{
|
||||
if (_sharedSqliteConnection is null)
|
||||
{
|
||||
_sharedSqliteConnection = new SqliteConnection(
|
||||
"Data Source=YavscApiTests;Mode=Memory;Cache=Shared");
|
||||
_sharedSqliteConnection.Open();
|
||||
}
|
||||
sharedConnection = _sharedSqliteConnection;
|
||||
}
|
||||
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
||||
opt.UseSqlite(sharedConnection));
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
||||
opt.UseSqlite(sharedConnection));
|
||||
}
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddApplicationPart(typeof(ActivityApiController).Assembly);
|
||||
|
|
@ -90,6 +110,127 @@ public sealed class ApiWebServerFixture : WebHostFixture
|
|||
|
||||
public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal));
|
||||
|
||||
public static bool UseNpgsqlProvider()
|
||||
=> string.Equals(
|
||||
Environment.GetEnvironmentVariable(DbProviderEnvVar),
|
||||
"npgsql",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string EnsureNpgsqlDatabaseCreated()
|
||||
{
|
||||
lock (_npgsqlLock)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_sharedNpgsqlConnectionString))
|
||||
{
|
||||
return _sharedNpgsqlConnectionString;
|
||||
}
|
||||
|
||||
var adminConnectionString = BuildAdminConnectionString();
|
||||
var databaseName = $"yavsc_api_test_{Guid.NewGuid():N}";
|
||||
|
||||
using (var adminConnection = new NpgsqlConnection(adminConnectionString))
|
||||
{
|
||||
adminConnection.Open();
|
||||
using var createCommand = adminConnection.CreateCommand();
|
||||
createCommand.CommandText = $"CREATE DATABASE \"{databaseName}\"";
|
||||
createCommand.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
var testConnectionBuilder = new NpgsqlConnectionStringBuilder(adminConnectionString)
|
||||
{
|
||||
Database = databaseName,
|
||||
Pooling = false,
|
||||
IncludeErrorDetail = true
|
||||
};
|
||||
|
||||
_sharedNpgsqlAdminConnectionString = adminConnectionString;
|
||||
_sharedNpgsqlDatabaseName = databaseName;
|
||||
_sharedNpgsqlConnectionString = testConnectionBuilder.ToString();
|
||||
RegisterNpgsqlCleanup();
|
||||
return _sharedNpgsqlConnectionString;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildAdminConnectionString()
|
||||
{
|
||||
var configured = Environment.GetEnvironmentVariable(NpgsqlAdminConnectionEnvVar);
|
||||
var source = string.IsNullOrWhiteSpace(configured)
|
||||
? DefaultDevelopmentConnectionString
|
||||
: configured;
|
||||
|
||||
var builder = new NpgsqlConnectionStringBuilder(source)
|
||||
{
|
||||
Pooling = false,
|
||||
IncludeErrorDetail = true
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configured))
|
||||
{
|
||||
builder.Database = "postgres";
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(builder.Database))
|
||||
{
|
||||
builder.Database = "postgres";
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void RegisterNpgsqlCleanup()
|
||||
{
|
||||
if (_npgsqlCleanupRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, __) => DropTemporaryNpgsqlDatabase();
|
||||
AssemblyLoadContext.Default.Unloading += _ => DropTemporaryNpgsqlDatabase();
|
||||
_npgsqlCleanupRegistered = true;
|
||||
}
|
||||
|
||||
private static void DropTemporaryNpgsqlDatabase()
|
||||
{
|
||||
lock (_npgsqlLock)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_sharedNpgsqlDatabaseName)
|
||||
|| string.IsNullOrWhiteSpace(_sharedNpgsqlAdminConnectionString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var adminConnection = new NpgsqlConnection(_sharedNpgsqlAdminConnectionString);
|
||||
adminConnection.Open();
|
||||
|
||||
using (var terminateCommand = adminConnection.CreateCommand())
|
||||
{
|
||||
terminateCommand.CommandText = @"
|
||||
SELECT pg_terminate_backend(pid)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = @databaseName
|
||||
AND pid <> pg_backend_pid();";
|
||||
terminateCommand.Parameters.AddWithValue("databaseName", _sharedNpgsqlDatabaseName);
|
||||
terminateCommand.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using var dropCommand = adminConnection.CreateCommand();
|
||||
dropCommand.CommandText = $"DROP DATABASE IF EXISTS \"{_sharedNpgsqlDatabaseName}\"";
|
||||
dropCommand.ExecuteNonQuery();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort cleanup only.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sharedNpgsqlConnectionString = null;
|
||||
_sharedNpgsqlAdminConnectionString = null;
|
||||
_sharedNpgsqlDatabaseName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NoopMessageSender : IYavscMessageSender
|
||||
{
|
||||
public Task<MessageWithPayloadResponse> NotifyBookQueryAsync(IEnumerable<string> connectionIds, RdvQueryEvent ev)
|
||||
|
|
|
|||
|
|
@ -143,4 +143,87 @@ public sealed class RdvQueryApiControllerTests : IClassFixture<ApiWebServerFixtu
|
|||
Assert.NotNull(created);
|
||||
Assert.Equal(DateTimeKind.Utc, created!.EventDate.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostQuery_with_unknown_location_id_creates_location_and_succeeds()
|
||||
{
|
||||
_fixture.ResetAndSeedRdvQueryGraph();
|
||||
using var http = NewClient(subject: "alice");
|
||||
|
||||
var createPayload = new
|
||||
{
|
||||
ActivityCode = "dev",
|
||||
PerformerId = "alice",
|
||||
Consent = true,
|
||||
EventDate = DateTime.UtcNow.AddDays(3),
|
||||
Location = new
|
||||
{
|
||||
Id = 999999L,
|
||||
Address = "4 rue du Test",
|
||||
Latitude = 48.8569,
|
||||
Longitude = 2.3525,
|
||||
},
|
||||
Reason = "Rendez-vous id location inconnu",
|
||||
Status = QueryStatus.Inserted,
|
||||
};
|
||||
|
||||
var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken);
|
||||
var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(createResponse.StatusCode == HttpStatusCode.Created, $"Unexpected status {(int)createResponse.StatusCode} ({createResponse.StatusCode}): {body}");
|
||||
|
||||
var created = await createResponse.Content.ReadFromJsonAsync<RdvQuery>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(created);
|
||||
Assert.NotNull(created!.Location);
|
||||
Assert.True(created.Location.Id > 0);
|
||||
Assert.NotEqual(999999L, created.Location.Id);
|
||||
Assert.Equal("alice", created.ClientId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostQuery_without_location_returns_bad_request()
|
||||
{
|
||||
_fixture.ResetAndSeedRdvQueryGraph();
|
||||
using var http = NewClient(subject: "alice");
|
||||
|
||||
var createPayload = new
|
||||
{
|
||||
ActivityCode = "dev",
|
||||
PerformerId = "alice",
|
||||
Consent = true,
|
||||
EventDate = DateTime.UtcNow.AddDays(1),
|
||||
Reason = "Rendez-vous sans location",
|
||||
Status = QueryStatus.Inserted,
|
||||
};
|
||||
|
||||
var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostQuery_with_unknown_location_id_and_missing_address_returns_bad_request()
|
||||
{
|
||||
_fixture.ResetAndSeedRdvQueryGraph();
|
||||
using var http = NewClient(subject: "alice");
|
||||
|
||||
var createPayload = new
|
||||
{
|
||||
ActivityCode = "dev",
|
||||
PerformerId = "alice",
|
||||
Consent = true,
|
||||
EventDate = DateTime.UtcNow.AddDays(1),
|
||||
Location = new
|
||||
{
|
||||
Id = 777777L,
|
||||
Address = "",
|
||||
Latitude = 0.0,
|
||||
Longitude = 0.0,
|
||||
},
|
||||
Reason = "Rendez-vous location invalide",
|
||||
Status = QueryStatus.Inserted,
|
||||
};
|
||||
|
||||
var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue