From ecc26d86f6462304f8cc8c1a6459b133db8696fd Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:57:24 -0500 Subject: [PATCH] feat: added Sdn repo and added some initial tests --- .vscode/settings.json | 3 +- .../Fakes/SdnFaker.cs | 2 - .../Integration/EfRepositoryTests.cs | 77 ------ .../Integration/SdnRepositoryTests.cs | 104 ++++++++ .../SanctionsSearch.Worker.Tests.csproj | 1 + .../Unit/OfacFileServiceTests.cs | 3 - .../Unit/SdnRepositoryTests.cs | 63 +++++ src/SanctionsSearch.Worker.Tests/Usings.cs | 14 +- .../Interfaces/IOfacFileService.cs | 4 +- .../Interfaces/IRepository.cs | 2 +- .../20240819001854_initial_migration.cs | 236 +++++++++--------- ...821035135_update_models_with_timestamps.cs | 181 +++++++------- .../Persistence/AppDbContext.cs | 6 +- .../Persistence/EfRepository.cs | 4 +- .../Persistence/SdnRepository.cs | 2 +- src/SanctionsSearch.Worker/Program.cs | 119 +++++---- .../Services/OfacFileService.cs | 5 +- src/SanctionsSearch.Worker/Usings.cs | 21 +- 18 files changed, 474 insertions(+), 373 deletions(-) delete mode 100644 src/SanctionsSearch.Worker.Tests/Integration/EfRepositoryTests.cs create mode 100644 src/SanctionsSearch.Worker.Tests/Integration/SdnRepositoryTests.cs create mode 100644 src/SanctionsSearch.Worker.Tests/Unit/SdnRepositoryTests.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bd4323..703ec55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "cSpell.words": [ "OFAC", "Sdns", - "Szalay" + "Szalay", + "upserting" ] } diff --git a/src/SanctionsSearch.Worker.Tests/Fakes/SdnFaker.cs b/src/SanctionsSearch.Worker.Tests/Fakes/SdnFaker.cs index cc0b7b6..71da378 100644 --- a/src/SanctionsSearch.Worker.Tests/Fakes/SdnFaker.cs +++ b/src/SanctionsSearch.Worker.Tests/Fakes/SdnFaker.cs @@ -1,5 +1,3 @@ -using Bogus; - namespace SanctionsSearch.Worker.Tests.Fakes; class SdnFaker : Faker diff --git a/src/SanctionsSearch.Worker.Tests/Integration/EfRepositoryTests.cs b/src/SanctionsSearch.Worker.Tests/Integration/EfRepositoryTests.cs deleted file mode 100644 index 8e005f9..0000000 --- a/src/SanctionsSearch.Worker.Tests/Integration/EfRepositoryTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; - -using SanctionsSearch.Worker.Tests.Fakes; - -namespace SanctionsSearch.Worker.Tests.Integration; - -public class EfRepositoryTests : IAsyncLifetime -{ - private readonly AppDbContext _context; - private readonly EfRepository _sdnRepository; - private readonly SdnFaker _sdnFaker = new(); - - public EfRepositoryTests() - { - var loggerFactory = LoggerFactory.Create(builder => builder.ClearProviders()); - - _context = new AppDbContext(new DbOptions { DatabaseName = $"{Guid.NewGuid()}.db" }); - _sdnRepository = new EfRepository(_context, loggerFactory.CreateLogger>()); - } - - [Fact] - public async Task Upsert_WithNewEntity_ShouldAddEntityToDatabase() - { - var sdn = _sdnFaker.Generate(); - - await _sdnRepository.Upsert(sdn); - await _context.SaveChangesAsync(); - - var result = await _context.Sdns.FindAsync(sdn.Id); - - result.Should().BeEquivalentTo(sdn); - result!.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); - result.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); - } - - [Fact] - public async Task Upsert_WithExistingEntity_ShouldUpdateEntityInDatabase() - { - var sdn = _sdnFaker.Generate(); - - await _sdnRepository.Upsert(sdn); - await _context.SaveChangesAsync(); - - var createdSdn = await _context.Sdns.FindAsync(sdn.Id); - - createdSdn.Should().BeEquivalentTo(sdn); - createdSdn!.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); - createdSdn.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); - - var updatedSdn = _sdnFaker.Generate(); - updatedSdn.Id = sdn.Id; - - await _sdnRepository.Upsert(updatedSdn); - await _context.SaveChangesAsync(); - - var result = await _context.Sdns.FindAsync(sdn.Id); - - result.Should().BeEquivalentTo(updatedSdn); - result!.CreatedAt.Should().BeSameDateAs(createdSdn.CreatedAt); - result.UpdatedAt.Should().BeAfter(createdSdn.UpdatedAt); - } - - public async Task InitializeAsync() - { - await _context.Database.MigrateAsync(); - } - - public async Task DisposeAsync() - { - await _context.DisposeAsync(); - - SqliteConnection.ClearAllPools(); - - File.Delete(_context.DatabasePath); - } -} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker.Tests/Integration/SdnRepositoryTests.cs b/src/SanctionsSearch.Worker.Tests/Integration/SdnRepositoryTests.cs new file mode 100644 index 0000000..bfa6946 --- /dev/null +++ b/src/SanctionsSearch.Worker.Tests/Integration/SdnRepositoryTests.cs @@ -0,0 +1,104 @@ +namespace SanctionsSearch.Worker.Tests.Integration; + +public class SdnRepositoryTests : IAsyncLifetime +{ + private readonly AppDbContext _context; + private readonly SdnRepository _sdnRepository; + private readonly SdnFaker _sdnFaker = new(); + private readonly Mock _timeProvider = new(); + + public SdnRepositoryTests() + { + var loggerFactory = LoggerFactory.Create(builder => builder.ClearProviders()); + var options = new DbOptions { DatabaseName = $"{Guid.NewGuid()}.db" }; + + _context = new AppDbContext(options, _timeProvider.Object); + _sdnRepository = new SdnRepository(_context, new Logger(loggerFactory)); + } + + [Fact] + public async Task Upsert_WithNewEntity_ShouldAddEntityToDatabase() + { + var now = DateTimeOffset.UtcNow; + _timeProvider.Setup(x => x.GetUtcNow()).Returns(now); + + var sdn = _sdnFaker.Generate(); + + await _sdnRepository.Upsert(sdn); + await _context.SaveChangesAsync(); + + var result = await _context.Sdns.FindAsync(sdn.Id); + + result.Should().BeEquivalentTo(sdn); + result!.CreatedAt.Should().Be(now.DateTime); + result.UpdatedAt.Should().Be(now.DateTime); + } + + [Fact] + public async Task Upsert_WithExistingEntity_ShouldUpdateEntityInDatabase() + { + var createdTimeStamp = DateTimeOffset.UtcNow; + var updatedTimeStamp = createdTimeStamp.AddSeconds(2); + + _timeProvider.Setup(x => x.GetUtcNow()).Returns(createdTimeStamp); + + var sdn = _sdnFaker.Generate(); + + // Insert the entity + await _sdnRepository.Upsert(sdn); + await _context.SaveChangesAsync(); + + // Assert the entity was created + var createdSdn = await _context.Sdns.FindAsync(sdn.Id); + + createdSdn.Should().BeEquivalentTo(sdn); + createdSdn!.CreatedAt.Should().Be(createdTimeStamp.DateTime); + createdSdn.UpdatedAt.Should().Be(createdTimeStamp.DateTime); + + _timeProvider.Setup(x => x.GetUtcNow()).Returns(updatedTimeStamp); + + // Update the entity + sdn.Name = "Updated"; + await _sdnRepository.Upsert(sdn); + await _context.SaveChangesAsync(); + + // Assert the entity was updated + var result = await _context.Sdns.FindAsync(sdn.Id); + + result.Should().BeEquivalentTo(sdn); + result!.CreatedAt.Should().BeSameDateAs(createdTimeStamp.DateTime); + result.UpdatedAt.Should().BeSameDateAs(updatedTimeStamp.DateTime); + } + + [Fact] + public async Task Find_WithPredicate_ShouldReturnEntitiesMatchingPredicate() + { + var sdn1 = _sdnFaker.Generate(); + var sdn2 = _sdnFaker.Generate(); + var sdn3 = _sdnFaker.Generate(); + + await _sdnRepository.Upsert(sdn1); + await _sdnRepository.Upsert(sdn2); + await _sdnRepository.Upsert(sdn3); + await _context.SaveChangesAsync(); + + var result = await _sdnRepository.Find(x => x.Id == sdn2.Id); + + result.Should().HaveCount(1); + result.First().Should().BeEquivalentTo(sdn2); + } + + public async Task InitializeAsync() + { + await _context.Database.MigrateAsync(); + } + + public async Task DisposeAsync() + { + await _context.DisposeAsync(); + + SqliteConnection.ClearAllPools(); + + File.Delete(_context.DatabasePath); + } +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker.Tests/SanctionsSearch.Worker.Tests.csproj b/src/SanctionsSearch.Worker.Tests/SanctionsSearch.Worker.Tests.csproj index 7817dc0..64da083 100644 --- a/src/SanctionsSearch.Worker.Tests/SanctionsSearch.Worker.Tests.csproj +++ b/src/SanctionsSearch.Worker.Tests/SanctionsSearch.Worker.Tests.csproj @@ -31,6 +31,7 @@ ./TestResults/coverage/ cobertura [SanctionsSearch.Worker]* + [SanctionsSearch.Worker]SanctionsSearch.Worker.Migrations* diff --git a/src/SanctionsSearch.Worker.Tests/Unit/OfacFileServiceTests.cs b/src/SanctionsSearch.Worker.Tests/Unit/OfacFileServiceTests.cs index f77469f..0166c38 100644 --- a/src/SanctionsSearch.Worker.Tests/Unit/OfacFileServiceTests.cs +++ b/src/SanctionsSearch.Worker.Tests/Unit/OfacFileServiceTests.cs @@ -1,6 +1,3 @@ -using System.Net; -using System.Text; - namespace SanctionsSearch.Worker.Tests.Unit; public class OfacFileServiceTests diff --git a/src/SanctionsSearch.Worker.Tests/Unit/SdnRepositoryTests.cs b/src/SanctionsSearch.Worker.Tests/Unit/SdnRepositoryTests.cs new file mode 100644 index 0000000..dc1abc2 --- /dev/null +++ b/src/SanctionsSearch.Worker.Tests/Unit/SdnRepositoryTests.cs @@ -0,0 +1,63 @@ +namespace SanctionsSearch.Worker.Tests.Unit; + +public class SdnRepositoryTests +{ + private readonly Mock _context = new(); + private readonly Mock> _logger = new(); + private readonly SdnFaker _sdnFaker = new(); + private readonly SdnRepository _sdnRepository; + + public SdnRepositoryTests() + { + _sdnRepository = new SdnRepository(_context.Object, _logger.Object); + } + + [Fact] + public async Task Upsert_WhenExceptionIsThrown_ItShouldLogError() + { + var sdn = _sdnFaker.Generate(); + var mockSet = new Mock>(); + + mockSet + .Setup(x => x.FindAsync(sdn.Id)) + .Throws(); + + _context + .Setup(x => x.Set()) + .Returns(mockSet.Object); + + await _sdnRepository.Upsert(sdn); + + _logger.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>() + ) + ); + } + + [Fact] + public async Task Find_WhenExceptionIsThrown_ItShouldReturnEmptyListAndLogError() + { + _context + .Setup(x => x.Set()) + .Throws(); + + var result = await _sdnRepository.Find(x => x.Id == 1); + + result.Should().BeEmpty(); + + _logger.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>() + ) + ); + } +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker.Tests/Usings.cs b/src/SanctionsSearch.Worker.Tests/Usings.cs index 21226f5..0f1b6f5 100644 --- a/src/SanctionsSearch.Worker.Tests/Usings.cs +++ b/src/SanctionsSearch.Worker.Tests/Usings.cs @@ -1,5 +1,12 @@ +global using System.Net; +global using System.Text; + +global using Bogus; + global using FluentAssertions; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; @@ -7,7 +14,8 @@ global using Moq; global using RichardSzalay.MockHttp; -global using SanctionsSearch.Worker.Services; -global using SanctionsSearch.Worker.Options; global using SanctionsSearch.Worker.Models; -global using SanctionsSearch.Worker.Persistence; \ No newline at end of file +global using SanctionsSearch.Worker.Options; +global using SanctionsSearch.Worker.Persistence; +global using SanctionsSearch.Worker.Services; +global using SanctionsSearch.Worker.Tests.Fakes; diff --git a/src/SanctionsSearch.Worker/Interfaces/IOfacFileService.cs b/src/SanctionsSearch.Worker/Interfaces/IOfacFileService.cs index e3a4948..03484cc 100644 --- a/src/SanctionsSearch.Worker/Interfaces/IOfacFileService.cs +++ b/src/SanctionsSearch.Worker/Interfaces/IOfacFileService.cs @@ -1,5 +1,3 @@ -using FluentResults; - namespace SanctionsSearch.Worker.Interfaces; interface IOfacFileService @@ -12,4 +10,4 @@ interface IOfacFileService Task> GetConAddressesFileAsync(); Task> GetConAltNamesFileAsync(); Task> GetConCommentsFileAsync(); -} +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Interfaces/IRepository.cs b/src/SanctionsSearch.Worker/Interfaces/IRepository.cs index 36ce61b..83a1c3c 100644 --- a/src/SanctionsSearch.Worker/Interfaces/IRepository.cs +++ b/src/SanctionsSearch.Worker/Interfaces/IRepository.cs @@ -4,4 +4,4 @@ interface IRepository where T : Entity { Task Upsert(T entity); Task> Find(Expression> predicate); -} +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Migrations/20240819001854_initial_migration.cs b/src/SanctionsSearch.Worker/Migrations/20240819001854_initial_migration.cs index 4679dec..17cb9c1 100644 --- a/src/SanctionsSearch.Worker/Migrations/20240819001854_initial_migration.cs +++ b/src/SanctionsSearch.Worker/Migrations/20240819001854_initial_migration.cs @@ -4,130 +4,130 @@ namespace SanctionsSearch.Worker.Migrations { + /// + public partial class initial_migration : Migration + { /// - public partial class initial_migration : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Sdns", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(type: "TEXT", nullable: false), - Type = table.Column(type: "TEXT", nullable: false), - Program = table.Column(type: "TEXT", nullable: false), - Title = table.Column(type: "TEXT", nullable: false), - CallSign = table.Column(type: "TEXT", nullable: false), - VesselType = table.Column(type: "TEXT", nullable: false), - Tonnage = table.Column(type: "TEXT", nullable: false), - GrossRegisteredTonnage = table.Column(type: "TEXT", nullable: false), - VesselFlag = table.Column(type: "TEXT", nullable: false), - VesselOwner = table.Column(type: "TEXT", nullable: false), - Remarks = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Sdns", x => x.Id); - }); + migrationBuilder.CreateTable( + name: "Sdns", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + Program = table.Column(type: "TEXT", nullable: false), + Title = table.Column(type: "TEXT", nullable: false), + CallSign = table.Column(type: "TEXT", nullable: false), + VesselType = table.Column(type: "TEXT", nullable: false), + Tonnage = table.Column(type: "TEXT", nullable: false), + GrossRegisteredTonnage = table.Column(type: "TEXT", nullable: false), + VesselFlag = table.Column(type: "TEXT", nullable: false), + VesselOwner = table.Column(type: "TEXT", nullable: false), + Remarks = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Sdns", x => x.Id); + }); - migrationBuilder.CreateTable( - name: "Addresses", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - SdnId = table.Column(type: "INTEGER", nullable: false), - StreetAddress = table.Column(type: "TEXT", nullable: false), - CityProvincePostal = table.Column(type: "TEXT", nullable: false), - Country = table.Column(type: "TEXT", nullable: false), - Remarks = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Addresses", x => x.Id); - table.ForeignKey( - name: "FK_Addresses_Sdns_SdnId", - column: x => x.SdnId, - principalTable: "Sdns", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "Addresses", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SdnId = table.Column(type: "INTEGER", nullable: false), + StreetAddress = table.Column(type: "TEXT", nullable: false), + CityProvincePostal = table.Column(type: "TEXT", nullable: false), + Country = table.Column(type: "TEXT", nullable: false), + Remarks = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Addresses", x => x.Id); + table.ForeignKey( + name: "FK_Addresses_Sdns_SdnId", + column: x => x.SdnId, + principalTable: "Sdns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "Aliases", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - SdnId = table.Column(type: "INTEGER", nullable: false), - Type = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Remarks = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Aliases", x => x.Id); - table.ForeignKey( - name: "FK_Aliases_Sdns_SdnId", - column: x => x.SdnId, - principalTable: "Sdns", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "Aliases", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SdnId = table.Column(type: "INTEGER", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Remarks = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Aliases", x => x.Id); + table.ForeignKey( + name: "FK_Aliases_Sdns_SdnId", + column: x => x.SdnId, + principalTable: "Sdns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "Comments", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - SdnId = table.Column(type: "INTEGER", nullable: false), - Remarks = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Comments", x => x.Id); - table.ForeignKey( - name: "FK_Comments_Sdns_SdnId", - column: x => x.SdnId, - principalTable: "Sdns", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "Comments", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + SdnId = table.Column(type: "INTEGER", nullable: false), + Remarks = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Comments", x => x.Id); + table.ForeignKey( + name: "FK_Comments_Sdns_SdnId", + column: x => x.SdnId, + principalTable: "Sdns", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_Addresses_SdnId", - table: "Addresses", - column: "SdnId"); + migrationBuilder.CreateIndex( + name: "IX_Addresses_SdnId", + table: "Addresses", + column: "SdnId"); - migrationBuilder.CreateIndex( - name: "IX_Aliases_SdnId", - table: "Aliases", - column: "SdnId"); + migrationBuilder.CreateIndex( + name: "IX_Aliases_SdnId", + table: "Aliases", + column: "SdnId"); - migrationBuilder.CreateIndex( - name: "IX_Comments_SdnId", - table: "Comments", - column: "SdnId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Addresses"); - - migrationBuilder.DropTable( - name: "Aliases"); - - migrationBuilder.DropTable( - name: "Comments"); - - migrationBuilder.DropTable( - name: "Sdns"); - } + migrationBuilder.CreateIndex( + name: "IX_Comments_SdnId", + table: "Comments", + column: "SdnId"); } -} + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Addresses"); + + migrationBuilder.DropTable( + name: "Aliases"); + + migrationBuilder.DropTable( + name: "Comments"); + + migrationBuilder.DropTable( + name: "Sdns"); + } + } +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Migrations/20240821035135_update_models_with_timestamps.cs b/src/SanctionsSearch.Worker/Migrations/20240821035135_update_models_with_timestamps.cs index b44c782..b4539d0 100644 --- a/src/SanctionsSearch.Worker/Migrations/20240821035135_update_models_with_timestamps.cs +++ b/src/SanctionsSearch.Worker/Migrations/20240821035135_update_models_with_timestamps.cs @@ -1,107 +1,108 @@ using System; + using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace SanctionsSearch.Worker.Migrations { + /// + public partial class update_models_with_timestamps : Migration + { /// - public partial class update_models_with_timestamps : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CreatedAt", - table: "Sdns", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Sdns", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "UpdatedAt", - table: "Sdns", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "UpdatedAt", + table: "Sdns", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "CreatedAt", - table: "Comments", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Comments", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "UpdatedAt", - table: "Comments", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "UpdatedAt", + table: "Comments", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "CreatedAt", - table: "Aliases", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Aliases", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "UpdatedAt", - table: "Aliases", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "UpdatedAt", + table: "Aliases", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "CreatedAt", - table: "Addresses", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "Addresses", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - migrationBuilder.AddColumn( - name: "UpdatedAt", - table: "Addresses", - type: "TEXT", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CreatedAt", - table: "Sdns"); - - migrationBuilder.DropColumn( - name: "UpdatedAt", - table: "Sdns"); - - migrationBuilder.DropColumn( - name: "CreatedAt", - table: "Comments"); - - migrationBuilder.DropColumn( - name: "UpdatedAt", - table: "Comments"); - - migrationBuilder.DropColumn( - name: "CreatedAt", - table: "Aliases"); - - migrationBuilder.DropColumn( - name: "UpdatedAt", - table: "Aliases"); - - migrationBuilder.DropColumn( - name: "CreatedAt", - table: "Addresses"); - - migrationBuilder.DropColumn( - name: "UpdatedAt", - table: "Addresses"); - } + migrationBuilder.AddColumn( + name: "UpdatedAt", + table: "Addresses", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); } -} + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Sdns"); + + migrationBuilder.DropColumn( + name: "UpdatedAt", + table: "Sdns"); + + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "UpdatedAt", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Aliases"); + + migrationBuilder.DropColumn( + name: "UpdatedAt", + table: "Aliases"); + + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "Addresses"); + + migrationBuilder.DropColumn( + name: "UpdatedAt", + table: "Addresses"); + } + } +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Persistence/AppDbContext.cs b/src/SanctionsSearch.Worker/Persistence/AppDbContext.cs index 8db2a0c..3d7d841 100644 --- a/src/SanctionsSearch.Worker/Persistence/AppDbContext.cs +++ b/src/SanctionsSearch.Worker/Persistence/AppDbContext.cs @@ -1,8 +1,9 @@ namespace SanctionsSearch.Worker.Persistence; -class AppDbContext(DbOptions options) : DbContext +class AppDbContext(DbOptions options, TimeProvider timeProvider) : DbContext { private readonly DbOptions _options = options; + private readonly TimeProvider _timeProvider = timeProvider; private SqliteConnection? _connection; internal string DatabasePath { get; private set; } = string.Empty; public DbSet Sdns { get; set; } = default!; @@ -32,8 +33,7 @@ class AppDbContext(DbOptions options) : DbContext public async override Task SaveChangesAsync(CancellationToken cancellationToken = default) { - // TODO: Use TimeProvider instead of DateTime.UtcNow - var now = DateTime.UtcNow; + var now = _timeProvider.GetUtcNow().DateTime; foreach (var changedEntity in ChangeTracker.Entries()) { diff --git a/src/SanctionsSearch.Worker/Persistence/EfRepository.cs b/src/SanctionsSearch.Worker/Persistence/EfRepository.cs index 92c920d..83062ba 100644 --- a/src/SanctionsSearch.Worker/Persistence/EfRepository.cs +++ b/src/SanctionsSearch.Worker/Persistence/EfRepository.cs @@ -2,10 +2,10 @@ namespace SanctionsSearch.Worker.Persistence; class EfRepository : IRepository where T : Entity { - protected readonly AppDbContext _context; + protected readonly DbContext _context; protected readonly ILogger> _logger; - public EfRepository(AppDbContext context, ILogger> logger) + public EfRepository(DbContext context, ILogger> logger) { _context = context; _logger = logger; diff --git a/src/SanctionsSearch.Worker/Persistence/SdnRepository.cs b/src/SanctionsSearch.Worker/Persistence/SdnRepository.cs index 8eef5a3..d52b445 100644 --- a/src/SanctionsSearch.Worker/Persistence/SdnRepository.cs +++ b/src/SanctionsSearch.Worker/Persistence/SdnRepository.cs @@ -1,7 +1,7 @@ namespace SanctionsSearch.Worker.Persistence; class SdnRepository( - AppDbContext dbContext, + DbContext dbContext, ILogger logger ) : EfRepository(dbContext, logger), ISdnRepository { diff --git a/src/SanctionsSearch.Worker/Program.cs b/src/SanctionsSearch.Worker/Program.cs index d437dec..cda5156 100644 --- a/src/SanctionsSearch.Worker/Program.cs +++ b/src/SanctionsSearch.Worker/Program.cs @@ -1,62 +1,71 @@ -using SanctionsSearch.Worker.Persistence; -using SanctionsSearch.Worker.Setup; +namespace SanctionsSearch.Worker; -if (EF.IsDesignTime) +class Program { - Host.CreateDefaultBuilder().Build().Run(); - return; -} - -Log.Logger = new LoggerConfiguration() - .Enrich.WithProperty("Application", "SanctionsSearch.Worker") - .Enrich.WithEnvironmentName() - .Enrich.WithMachineName() - .Enrich.WithProcessId() - .Enrich.WithThreadId() - .Enrich.WithExceptionDetails() - .Enrich.FromLogContext() - .MinimumLevel.Debug() - .WriteTo.Console() - .WriteTo.File(new CompactJsonFormatter(), "logs/log.json", rollingInterval: RollingInterval.Day) - .CreateLogger(); - -try -{ - Log.Information("Starting Sanctions Search worker"); - - var builder = Host.CreateApplicationBuilder(args); - - builder.Logging.ClearProviders(); - builder.Logging.AddSerilog(); - - builder.Services.ConfigureOptions(); - builder.Services.ConfigureOptions(); - builder.Services.AddScoped(rs => rs.GetRequiredService>().Value); - - builder.Services.AddDbContext(); - builder.Services.AddHostedService(); - - var host = builder.Build(); - - using var scope = host.Services.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - var pendingMigrations = await context.Database.GetPendingMigrationsAsync(); - - if (pendingMigrations.Any()) + async static Task Main(string[] args) { - Log.Information("Applying pending migrations"); - await context.Database.MigrateAsync(); + if (EF.IsDesignTime) return; + + Log.Logger = new LoggerConfiguration() + .Enrich.WithProperty("Application", "SanctionsSearch.Worker") + .Enrich.WithEnvironmentName() + .Enrich.WithMachineName() + .Enrich.WithProcessId() + .Enrich.WithThreadId() + .Enrich.WithExceptionDetails() + .Enrich.FromLogContext() + .MinimumLevel.Debug() + .WriteTo.Console() + .WriteTo.File(new CompactJsonFormatter(), "logs/log.json", rollingInterval: RollingInterval.Day) + .CreateLogger(); + + try + { + Log.Information("Starting Sanctions Search worker"); + + var builder = CreateHostBuilder(args); + + var host = builder.Build(); + + using var scope = host.Services.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var pendingMigrations = await context.Database.GetPendingMigrationsAsync(); + + if (pendingMigrations.Any()) + { + Log.Information("Applying pending migrations"); + await context.Database.MigrateAsync(); + } + + await host.RunAsync(); + + Log.Information("Stopping Sanctions Search worker"); + } + catch (Exception ex) + { + Log.Fatal(ex, "Worker terminated unexpectedly"); + } + finally + { + await Log.CloseAndFlushAsync(); + } } - host.Run(); + static HostApplicationBuilder CreateHostBuilder(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); - Log.Information("Stopping Sanctions Search worker"); -} -catch (Exception ex) -{ - Log.Fatal(ex, "Worker terminated unexpectedly"); -} -finally -{ - await Log.CloseAndFlushAsync(); + builder.Logging.ClearProviders(); + builder.Logging.AddSerilog(); + + builder.Services.ConfigureOptions(); + builder.Services.ConfigureOptions(); + builder.Services.AddScoped(rs => rs.GetRequiredService>().Value); + + builder.Services.AddSingleton(TimeProvider.System); + builder.Services.AddDbContext(); + builder.Services.AddHostedService(); + + return builder; + } } \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Services/OfacFileService.cs b/src/SanctionsSearch.Worker/Services/OfacFileService.cs index aaccd83..7d51402 100644 --- a/src/SanctionsSearch.Worker/Services/OfacFileService.cs +++ b/src/SanctionsSearch.Worker/Services/OfacFileService.cs @@ -1,6 +1,3 @@ - -using FluentResults; - namespace SanctionsSearch.Worker.Services; class OfacFileService( @@ -45,4 +42,4 @@ class OfacFileService( public Task> GetConAddressesFileAsync() => GetFileAsync(_options.GetConAddressesFileUri()); public Task> GetConAltNamesFileAsync() => GetFileAsync(_options.GetConAltNamesFileUri()); public Task> GetConCommentsFileAsync() => GetFileAsync(_options.GetConCommentsFileUri()); -} +} \ No newline at end of file diff --git a/src/SanctionsSearch.Worker/Usings.cs b/src/SanctionsSearch.Worker/Usings.cs index ecd75f9..400fb68 100644 --- a/src/SanctionsSearch.Worker/Usings.cs +++ b/src/SanctionsSearch.Worker/Usings.cs @@ -1,18 +1,19 @@ +global using System.Linq.Expressions; +global using System.Reflection; + +global using FluentResults; + +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Options; global using SanctionsSearch.Worker.Interfaces; -global using SanctionsSearch.Worker.Options; -global using SanctionsSearch.Worker; global using SanctionsSearch.Worker.Models; +global using SanctionsSearch.Worker.Options; +global using SanctionsSearch.Worker.Persistence; +global using SanctionsSearch.Worker.Services; +global using SanctionsSearch.Worker.Setup; global using Serilog; global using Serilog.Exceptions; global using Serilog.Formatting.Compact; - -global using Microsoft.EntityFrameworkCore; - -global using System.Linq.Expressions; - -global using System.Reflection; - -global using Microsoft.Data.Sqlite; \ No newline at end of file