feat: implement ofac file service
This commit is contained in:
@@ -2,4 +2,115 @@ namespace SanctionsSearch.Worker.Tests.Unit;
|
|||||||
|
|
||||||
public class OfacFileServiceOptionsTests
|
public class OfacFileServiceOptionsTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetSdnFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
SdnFileName = "sdn.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetSdnFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/sdn.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAddressFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
AddressFileName = "address.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetAddressFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/address.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAltNamesFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
AltNamesFileName = "alt_names.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetAltNamesFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/alt_names.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCommentsFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
CommentsFileName = "comments.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetCommentsFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/comments.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConPrimaryNameFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
ConPrimaryNameFileName = "con_primary_name.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetConPrimaryNameFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/con_primary_name.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConAddressesFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
ConAddressesFileName = "con_addresses.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetConAddressesFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/con_addresses.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConAltNamesFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
ConAltNamesFileName = "con_alt_names.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetConAltNamesFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/con_alt_names.csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetConCommentsFileUri_WhenCalled_ReturnsUri()
|
||||||
|
{
|
||||||
|
var options = new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = "https://example.com",
|
||||||
|
ConCommentsFileName = "con_comments.csv"
|
||||||
|
};
|
||||||
|
|
||||||
|
var uri = options.GetConCommentsFileUri();
|
||||||
|
|
||||||
|
uri.Should().Be(new Uri("https://example.com/con_comments.csv"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,47 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace SanctionsSearch.Worker.Tests.Unit;
|
namespace SanctionsSearch.Worker.Tests.Unit;
|
||||||
|
|
||||||
public class OfacFileServiceTests
|
public class OfacFileServiceTests
|
||||||
{
|
{
|
||||||
|
private const string Url = "https://example.com";
|
||||||
|
private const string TestCsv = "Name,Address\nJohn Doe,123 Main St\n";
|
||||||
|
private static Stream TestStream => new MemoryStream(Encoding.UTF8.GetBytes(TestCsv));
|
||||||
private readonly MockHttpMessageHandler _mockHttp = new();
|
private readonly MockHttpMessageHandler _mockHttp = new();
|
||||||
private readonly Mock<ILogger<OfacFileService>> _mockLogger = new();
|
private readonly Mock<ILogger<OfacFileService>> _mockLogger = new();
|
||||||
private readonly Mock<IOptionsSnapshot<OfacFileServiceOptions>> _mockOptions = new();
|
private readonly Mock<IOptionsSnapshot<OfacFileServiceOptions>> _mockOptions = new();
|
||||||
|
private readonly OfacFileService _service;
|
||||||
|
|
||||||
|
public OfacFileServiceTests()
|
||||||
|
{
|
||||||
|
_mockOptions
|
||||||
|
.Setup(x => x.Value)
|
||||||
|
.Returns(new OfacFileServiceOptions
|
||||||
|
{
|
||||||
|
Url = Url,
|
||||||
|
SdnFileName = "sdn.csv",
|
||||||
|
AddressFileName = "address.csv",
|
||||||
|
AltNamesFileName = "alt_names.csv",
|
||||||
|
CommentsFileName = "comments.csv",
|
||||||
|
ConPrimaryNameFileName = "con_primary_name.csv",
|
||||||
|
ConAddressesFileName = "con_addresses.csv",
|
||||||
|
ConAltNamesFileName = "con_alt_names.csv",
|
||||||
|
ConCommentsFileName = "con_comments.csv"
|
||||||
|
});
|
||||||
|
|
||||||
|
_service = new OfacFileService(
|
||||||
|
_mockHttp.ToHttpClient(),
|
||||||
|
_mockLogger.Object,
|
||||||
|
_mockOptions.Object
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetStreamContent(Stream stream)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
return reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_WhenCalledWithNullClient_ThrowsArgumentNullException()
|
public void Constructor_WhenCalledWithNullClient_ThrowsArgumentNullException()
|
||||||
@@ -55,4 +92,216 @@ public class OfacFileServiceTests
|
|||||||
|
|
||||||
act.Should().Throw<ArgumentNullException>();
|
act.Should().Throw<ArgumentNullException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSdnFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/sdn.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetSdnFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSdnFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/sdn.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetSdnFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSdnFileAsync_WhenExceptionThrown_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/sdn.csv")
|
||||||
|
.Throw(new Exception("Test exception"));
|
||||||
|
|
||||||
|
var result = await _service.GetSdnFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAddressFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/address.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetAddressFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAddressFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/address.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetAddressFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAltNamesFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/alt_names.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetAltNamesFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAltNamesFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/alt_names.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetAltNamesFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCommentsFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/comments.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetCommentsFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCommentsFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/comments.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetCommentsFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConPrimaryNameFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_primary_name.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetConPrimaryNameFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConPrimaryNameFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_primary_name.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetConPrimaryNameFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConAddressesFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_addresses.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetConAddressesFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConAddressesFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_addresses.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetConAddressesFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConAltNamesFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_alt_names.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetConAltNamesFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConAltNamesFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_alt_names.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetConAltNamesFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConCommentsFileAsync_WhenCalled_ItShouldReturnStream()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_comments.csv")
|
||||||
|
.Respond("text/csv", TestStream);
|
||||||
|
|
||||||
|
var result = await _service.GetConCommentsFileAsync();
|
||||||
|
|
||||||
|
result.IsSuccess.Should().BeTrue();
|
||||||
|
GetStreamContent(result.Value).Should().Be(TestCsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConCommentsFileAsync_WhenRequestFails_ItShouldReturnFailure()
|
||||||
|
{
|
||||||
|
_mockHttp
|
||||||
|
.When("https://example.com/con_comments.csv")
|
||||||
|
.Respond(HttpStatusCode.InternalServerError);
|
||||||
|
|
||||||
|
var result = await _service.GetConCommentsFileAsync();
|
||||||
|
|
||||||
|
result.IsFailed.Should().BeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
|
using FluentResults;
|
||||||
|
|
||||||
namespace SanctionsSearch.Worker.Interfaces;
|
namespace SanctionsSearch.Worker.Interfaces;
|
||||||
|
|
||||||
interface IOfacFileService
|
interface IOfacFileService
|
||||||
{
|
{
|
||||||
|
Task<Result<Stream>> GetSdnFileAsync();
|
||||||
|
Task<Result<Stream>> GetAddressFileAsync();
|
||||||
|
Task<Result<Stream>> GetAltNamesFileAsync();
|
||||||
|
Task<Result<Stream>> GetCommentsFileAsync();
|
||||||
|
Task<Result<Stream>> GetConPrimaryNameFileAsync();
|
||||||
|
Task<Result<Stream>> GetConAddressesFileAsync();
|
||||||
|
Task<Result<Stream>> GetConAltNamesFileAsync();
|
||||||
|
Task<Result<Stream>> GetConCommentsFileAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
namespace SanctionsSearch.Worker.Services;
|
namespace SanctionsSearch.Worker.Services;
|
||||||
|
|
||||||
class OfacFileService(
|
class OfacFileService(
|
||||||
@@ -9,4 +12,37 @@ class OfacFileService(
|
|||||||
private readonly HttpClient _client = client ?? throw new ArgumentNullException(nameof(client));
|
private readonly HttpClient _client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
private readonly ILogger<OfacFileService> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
private readonly ILogger<OfacFileService> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
private readonly OfacFileServiceOptions _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
private readonly OfacFileServiceOptions _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
private async Task<Result<Stream>> GetFileAsync(Uri fileUri)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Downloading file from {FileUri}", fileUri);
|
||||||
|
|
||||||
|
var response = await _client.GetAsync(fileUri);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode is false)
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to download file from {FileUri}", fileUri);
|
||||||
|
return Result.Fail($"Failed to download file from {fileUri}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Downloaded file from {FileUri}", fileUri);
|
||||||
|
var stream = await response.Content.ReadAsStreamAsync();
|
||||||
|
return Result.Ok(stream);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to download file from {FileUri}", fileUri);
|
||||||
|
return Result.Fail($"Failed to download file from {fileUri}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<Stream>> GetSdnFileAsync() => GetFileAsync(_options.GetSdnFileUri());
|
||||||
|
public Task<Result<Stream>> GetAddressFileAsync() => GetFileAsync(_options.GetAddressFileUri());
|
||||||
|
public Task<Result<Stream>> GetAltNamesFileAsync() => GetFileAsync(_options.GetAltNamesFileUri());
|
||||||
|
public Task<Result<Stream>> GetCommentsFileAsync() => GetFileAsync(_options.GetCommentsFileUri());
|
||||||
|
public Task<Result<Stream>> GetConPrimaryNameFileAsync() => GetFileAsync(_options.GetConPrimaryNameFileUri());
|
||||||
|
public Task<Result<Stream>> GetConAddressesFileAsync() => GetFileAsync(_options.GetConAddressesFileUri());
|
||||||
|
public Task<Result<Stream>> GetConAltNamesFileAsync() => GetFileAsync(_options.GetConAltNamesFileUri());
|
||||||
|
public Task<Result<Stream>> GetConCommentsFileAsync() => GetFileAsync(_options.GetConCommentsFileUri());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,16 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"OfacFileServiceOptions": {
|
||||||
|
"Url": "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports",
|
||||||
|
"SdnFileName": "SDN.CSV",
|
||||||
|
"AddressFileName": "ADD.CSV",
|
||||||
|
"AltNamesFileName": "ALT.CSV",
|
||||||
|
"CommentsFileName": "SDN_COMMENTS.CSV",
|
||||||
|
"ConPrimaryNameFileName": "CONS_PRIM.CSV",
|
||||||
|
"ConAddressesFileName": "CONS_ADD.CSV",
|
||||||
|
"ConAltNamesFileName": "CONS_ALT.CSV",
|
||||||
|
"ConCommentsFileName": "CONS_COMMENTS.CSV"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user