From e30d14501565da890058ff76bb3eec2a6dd7a24d Mon Sep 17 00:00:00 2001 From: Craig Edmunds Date: Tue, 24 Dec 2024 06:56:08 +0000 Subject: [PATCH] CDMS-200 adding decision integration tests & refactoring ApplicationFactory --- .../AnalyticsTests.cs | 4 +- Btms.Backend.IntegrationTests/BaseApiTests.cs | 9 +- .../DecisionTests.cs | 46 +++++++++ Btms.Backend.IntegrationTests/GmrTests.cs | 18 ++-- .../Helpers/ApplicationFactory.cs | 93 +++++++++++++++++++ ...ctory.cs => ScenarioApplicationFactory.cs} | 8 +- Btms.Backend.IntegrationTests/LinkingTests.cs | 12 +-- Btms.Backend.IntegrationTests/SmokeTests.cs | 14 +-- 8 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 Btms.Backend.IntegrationTests/DecisionTests.cs create mode 100644 Btms.Backend.IntegrationTests/Helpers/ApplicationFactory.cs rename Btms.Backend.IntegrationTests/Helpers/{IntegrationTestsApplicationFactory.cs => ScenarioApplicationFactory.cs} (90%) diff --git a/Btms.Backend.IntegrationTests/AnalyticsTests.cs b/Btms.Backend.IntegrationTests/AnalyticsTests.cs index 598be38..4b2a13f 100644 --- a/Btms.Backend.IntegrationTests/AnalyticsTests.cs +++ b/Btms.Backend.IntegrationTests/AnalyticsTests.cs @@ -19,8 +19,8 @@ namespace Btms.Backend.IntegrationTests; [Trait("Category", "Integration")] -public class AnalyticsTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) - : BaseApiTests(factory, testOutputHelper), IClassFixture +public class AnalyticsTests(ApplicationFactory factory, ITestOutputHelper testOutputHelper) + : BaseApiTests(factory, testOutputHelper), IClassFixture { // private static void ShouldNotBeNull([DoesNotReturnIf(true), NotNull] T? value) diff --git a/Btms.Backend.IntegrationTests/BaseApiTests.cs b/Btms.Backend.IntegrationTests/BaseApiTests.cs index 3d547e4..de65728 100644 --- a/Btms.Backend.IntegrationTests/BaseApiTests.cs +++ b/Btms.Backend.IntegrationTests/BaseApiTests.cs @@ -19,9 +19,14 @@ namespace Btms.Backend.IntegrationTests; public abstract class BaseApiTests { protected readonly HttpClient Client; - protected readonly IntegrationTestsApplicationFactory Factory; + internal readonly IIntegrationTestsApplicationFactory Factory; + // protected readonly IntegrationTestsApplicationFactory Factory; - protected BaseApiTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) + protected async Task ClearDb() + { + await Factory.ClearDb(Client); + } + protected BaseApiTests(IIntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) { Factory = factory; Factory.TestOutputHelper = testOutputHelper; diff --git a/Btms.Backend.IntegrationTests/DecisionTests.cs b/Btms.Backend.IntegrationTests/DecisionTests.cs new file mode 100644 index 0000000..ddc773f --- /dev/null +++ b/Btms.Backend.IntegrationTests/DecisionTests.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; +using Btms.Common.Extensions; +using Btms.Model; +using Btms.SyncJob; +using Btms.Backend.IntegrationTests.JsonApiClient; +using FluentAssertions; +using System.Net; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using Btms.Backend.IntegrationTests.Extensions; +using Btms.Backend.IntegrationTests.Helpers; +using Json.More; +using Xunit; +using Xunit.Abstractions; + +namespace Btms.Backend.IntegrationTests; + +[Trait("Category", "Integration")] +public class DecsionTests(ScenarioApplicationFactory factory, ITestOutputHelper testOutputHelper) + : BaseApiTests(factory, testOutputHelper), IClassFixture +{ + + [Fact] + public async Task SimpleChedPScenario() + { + // Arrange + await factory.ClearDb(Client); + + // Act + // await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand + // { + // SyncPeriod = SyncPeriod.All, RootFolder = "SmokeTest" + // }); + + // Assert + var jsonClientResponse = Client.AsJsonApiClient().Get("api/movements"); + jsonClientResponse.Data + .Where(x => x.Relationships is not null) + .SelectMany(x => x.Relationships!) + .Any(x => x.Value is { Links: not null }) + .Should().Be(false); + } +} \ No newline at end of file diff --git a/Btms.Backend.IntegrationTests/GmrTests.cs b/Btms.Backend.IntegrationTests/GmrTests.cs index 11b693a..8c33eda 100644 --- a/Btms.Backend.IntegrationTests/GmrTests.cs +++ b/Btms.Backend.IntegrationTests/GmrTests.cs @@ -14,25 +14,27 @@ namespace Btms.Backend.IntegrationTests; [Trait("Category", "Integration")] public class GmrTests : - IClassFixture, IAsyncLifetime + IClassFixture, IAsyncLifetime { - private readonly HttpClient client; + private readonly HttpClient _client; + private IIntegrationTestsApplicationFactory _factory; - public GmrTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) + public GmrTests(ApplicationFactory factory, ITestOutputHelper testOutputHelper) { factory.TestOutputHelper = testOutputHelper; factory.DatabaseName = "GmrTests"; - client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); + _client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var credentials = "IntTest:Password"; var credentialsAsBytes = Encoding.UTF8.GetBytes(credentials.ToCharArray()); var encodedCredentials = Convert.ToBase64String(credentialsAsBytes); - client.DefaultRequestHeaders.Authorization = + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(BasicAuthenticationDefaults.AuthenticationScheme, encodedCredentials); + _factory = factory; } public async Task InitializeAsync() { - await IntegrationTestsApplicationFactory.ClearDb(client); + await _factory.ClearDb(_client); await MakeSyncGmrsRequest(new SyncGmrsCommand { @@ -48,7 +50,7 @@ await MakeSyncGmrsRequest(new SyncGmrsCommand public void FetchSingleGmrTest() { //Act - var jsonClientResponse = client.AsJsonApiClient().GetById("GMRAPOQSPDUG", "api/gmrs"); + var jsonClientResponse = _client.AsJsonApiClient().GetById("GMRAPOQSPDUG", "api/gmrs"); // Assert jsonClientResponse.Data.Relationships?["customs"]?.Links?.Self.Should().Be("/api/gmr/:id/relationships/import-notifications"); @@ -68,7 +70,7 @@ private Task PostCommand(T command, string uri) var jsonData = JsonSerializer.Serialize(command); HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json"); - return client.PostAsync(uri, content); + return _client.PostAsync(uri, content); } public Task DisposeAsync() diff --git a/Btms.Backend.IntegrationTests/Helpers/ApplicationFactory.cs b/Btms.Backend.IntegrationTests/Helpers/ApplicationFactory.cs new file mode 100644 index 0000000..4323818 --- /dev/null +++ b/Btms.Backend.IntegrationTests/Helpers/ApplicationFactory.cs @@ -0,0 +1,93 @@ +using Btms.Backend.Data; +using Btms.BlobService; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Driver; +using Xunit.Abstractions; + +namespace Btms.Backend.IntegrationTests.Helpers; + +public interface IIntegrationTestsApplicationFactory +{ + ITestOutputHelper TestOutputHelper { get; set; } + string DatabaseName { get; set; } + + HttpClient CreateClient(WebApplicationFactoryClientOptions options); + HttpClient CreateClient(); + IMongoDbContext GetDbContext(); + Task ClearDb(HttpClient client); +} + +public class ApplicationFactory : WebApplicationFactory, IIntegrationTestsApplicationFactory +{ + // protected override Bef + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Any integration test overrides could be added here + // And we don't want to load the backend ini file + var configurationValues = new Dictionary + { + { "DisableLoadIniFile", "true" }, + { "BlobServiceOptions:CachePath", "../../../Fixtures" }, + { "BlobServiceOptions:CacheReadEnabled", "true" }, + { "AuthKeyStore:Credentials:IntTest", "Password" } + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configurationValues!) + .Build(); + + builder + .UseConfiguration(configuration) + .ConfigureServices(services => + { + var mongoDatabaseDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IMongoDatabase))!; + services.Remove(mongoDatabaseDescriptor); + + var blobOptionsValidatorDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IValidateOptions))!; + services.Remove(blobOptionsValidatorDescriptor); + + services.AddSingleton(sp => + { + var options = sp.GetService>()!; + var settings = MongoClientSettings.FromConnectionString(options.Value.DatabaseUri); + var client = new MongoClient(settings); + + var camelCaseConvention = new ConventionPack { new CamelCaseElementNameConvention() }; + // convention must be registered before initialising collection + ConventionRegistry.Register("CamelCase", camelCaseConvention, _ => true); + + var dbName = string.IsNullOrEmpty(DatabaseName) ? Random.Shared.Next().ToString() : DatabaseName; + return client.GetDatabase($"Btms_MongoDb_{dbName}_Test"); + }); + + services.AddLogging(lb => lb.AddXUnit(TestOutputHelper)); + }); + + builder.UseEnvironment("Development"); + } + + public ITestOutputHelper TestOutputHelper { get; set; } = null!; + + public string DatabaseName { get; set; } = null!; + + public IMongoDbContext GetDbContext() + { + return Services.CreateScope().ServiceProvider.GetRequiredService(); + } + + // public new HttpClient CreateClient() + // { + // throw new NotImplementedException(); + // } + + public async Task ClearDb(HttpClient client) + { + await client.GetAsync("mgmt/collections/drop"); + } +} \ No newline at end of file diff --git a/Btms.Backend.IntegrationTests/Helpers/IntegrationTestsApplicationFactory.cs b/Btms.Backend.IntegrationTests/Helpers/ScenarioApplicationFactory.cs similarity index 90% rename from Btms.Backend.IntegrationTests/Helpers/IntegrationTestsApplicationFactory.cs rename to Btms.Backend.IntegrationTests/Helpers/ScenarioApplicationFactory.cs index edfbc47..6972264 100644 --- a/Btms.Backend.IntegrationTests/Helpers/IntegrationTestsApplicationFactory.cs +++ b/Btms.Backend.IntegrationTests/Helpers/ScenarioApplicationFactory.cs @@ -12,7 +12,7 @@ namespace Btms.Backend.IntegrationTests.Helpers; -public class IntegrationTestsApplicationFactory : WebApplicationFactory +public class ScenarioApplicationFactory : WebApplicationFactory, IIntegrationTestsApplicationFactory { // protected override Bef protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -61,16 +61,16 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.UseEnvironment("Development"); } - internal ITestOutputHelper TestOutputHelper { get; set; } = null!; + public ITestOutputHelper TestOutputHelper { get; set; } = null!; - internal string DatabaseName { get; set; } = null!; + public string DatabaseName { get; set; } = null!; public IMongoDbContext GetDbContext() { return Services.CreateScope().ServiceProvider.GetRequiredService(); } - public static async Task ClearDb(HttpClient client) + public async Task ClearDb(HttpClient client) { await client.GetAsync("mgmt/collections/drop"); } diff --git a/Btms.Backend.IntegrationTests/LinkingTests.cs b/Btms.Backend.IntegrationTests/LinkingTests.cs index a2ef463..aa0265d 100644 --- a/Btms.Backend.IntegrationTests/LinkingTests.cs +++ b/Btms.Backend.IntegrationTests/LinkingTests.cs @@ -8,14 +8,14 @@ namespace Btms.Backend.IntegrationTests; [Trait("Category", "Integration")] -public class LinkingTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) - : BaseApiTests(factory, testOutputHelper), IClassFixture +public class LinkingTests(ApplicationFactory factory, ITestOutputHelper testOutputHelper) + : BaseApiTests(factory, testOutputHelper), IClassFixture { [Fact] public async Task SyncClearanceRequests_WithNoReferencedNotifications_ShouldNotLink() { // Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await Factory.ClearDb(Client); // Act await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand @@ -36,7 +36,7 @@ await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand public async Task SyncClearanceRequests_WithReferencedNotifications_ShouldLink() { // Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); // Act await MakeSyncNotificationsRequest(new SyncNotificationsCommand @@ -61,7 +61,7 @@ await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand public async Task SyncNotifications_WithNoReferencedMovements_ShouldNotLink() { // Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); // Act await MakeSyncNotificationsRequest(new SyncNotificationsCommand @@ -82,7 +82,7 @@ await MakeSyncNotificationsRequest(new SyncNotificationsCommand public async Task SyncNotifications_WithReferencedMovements_ShouldLink() { // Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); // Act await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand diff --git a/Btms.Backend.IntegrationTests/SmokeTests.cs b/Btms.Backend.IntegrationTests/SmokeTests.cs index 85f63cc..335f6c4 100644 --- a/Btms.Backend.IntegrationTests/SmokeTests.cs +++ b/Btms.Backend.IntegrationTests/SmokeTests.cs @@ -15,11 +15,11 @@ namespace Btms.Backend.IntegrationTests; [Trait("Category", "Integration")] -public class SmokeTests : BaseApiTests, IClassFixture +public class SmokeTests : BaseApiTests, IClassFixture { private readonly JsonSerializerOptions jsonOptions; - public SmokeTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper testOutputHelper) :base(factory, testOutputHelper) + public SmokeTests(ApplicationFactory factory, ITestOutputHelper testOutputHelper) :base(factory, testOutputHelper) { jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new JsonStringEnumConverter()); @@ -30,7 +30,7 @@ public SmokeTests(IntegrationTestsApplicationFactory factory, ITestOutputHelper public async Task CancelSyncJob() { //Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); var jobId = await StartJob(new SyncNotificationsCommand { SyncPeriod = SyncPeriod.All, @@ -56,7 +56,7 @@ public async Task CancelSyncJob() public async Task SyncNotifications() { //Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); await MakeSyncNotificationsRequest(new SyncNotificationsCommand { SyncPeriod = SyncPeriod.All, @@ -76,7 +76,7 @@ await MakeSyncNotificationsRequest(new SyncNotificationsCommand public async Task SyncDecisions() { //Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); await SyncClearanceRequests(); await MakeSyncDecisionsRequest(new SyncDecisionsCommand { @@ -106,7 +106,7 @@ await MakeSyncDecisionsRequest(new SyncDecisionsCommand public async Task SyncClearanceRequests() { //Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); //Act await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand @@ -127,7 +127,7 @@ await MakeSyncClearanceRequest(new SyncClearanceRequestsCommand public async Task SyncGmrs() { //Arrange - await IntegrationTestsApplicationFactory.ClearDb(Client); + await base.ClearDb(); //Act await MakeSyncGmrsRequest(new SyncGmrsCommand