Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions ScheduledTasks/AutoScavenge/scavenge.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
param(
[string]$BaseUrl = "http://localhost:2113",
[string]$Username,
[string]$Password
[string]$Password,
[System.Management.Automation.PSCredential]$Credential
Comment on lines 2 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the Pester callers for the credential-only interface

The pull-request workflow runs ScheduledTasks/Tests, but Scavenge.Tests.ps1 still invokes this script with -Username and -Password and expects a Basic authorization header. Those arguments no longer populate anything in the script, so $Credential remains null and the authorization assertion fails. Retain compatibility or update the checked-in callers and tests to construct a PSCredential in the same change.

Useful? React with 👍 / 👎.

)

$BaseUrl = $BaseUrl.TrimEnd('/')
Expand All @@ -12,7 +13,13 @@ $headers = @{
'Content-Type' = 'application/json'
}

if ($Username -and $Password) {
if ($Credential) {
$pair = "$($Credential.UserName)`:$($Credential.GetNetworkCredential().Password)"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$encoded = [Convert]::ToBase64String($bytes)
$headers['Authorization'] = "Basic $encoded"
}
elseif (-not [string]::IsNullOrWhiteSpace($Username) -and -not [string]::IsNullOrWhiteSpace($Password)) {
$pair = "$Username`:$Password"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$encoded = [Convert]::ToBase64String($bytes)
Expand All @@ -25,4 +32,4 @@ try {
catch {
Write-Error "POST to $uri failed: $($_.Exception.Message)"
exit 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ param(
[ValidateNotNullOrEmpty()]
[string]$ClientId,

[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory = $false)]
[string]$ClientSecret
)

Expand Down Expand Up @@ -164,11 +163,19 @@ function Invoke-DailySettlementProcessing {
[Parameter(Mandatory = $true)]
[string]$ClientId,

[Parameter(Mandatory = $true)]
[Parameter(Mandatory = $false)]
[string]$ClientSecret
)

$settlementDate = (Get-Date).ToString('yyyy-MM-dd')
if ([string]::IsNullOrWhiteSpace($ClientSecret)) {
$ClientSecret = $env:TRANSACTIONPROCESSOR_SERVICECLIENT_SECRET
}

if ([string]::IsNullOrWhiteSpace($ClientSecret)) {
throw 'Missing required client secret. Set TRANSACTIONPROCESSOR_SERVICECLIENT_SECRET or pass -ClientSecret.'
}

$accessToken = Get-AccessToken -SecurityServiceUrl $SecurityServiceUrl -ClientId $ClientId -ClientSecret $ClientSecret
$headers = @{
Authorization = "Bearer $accessToken"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ private MerchantProcessingConfigurationSnapshot BuildBootstrapSnapshot()
}

private async Task<MerchantProcessingConfigurationSnapshot?> TryLoadFromRelationalTablesAsync(MerchantFileProcessorDbContext dbContext, CancellationToken cancellationToken)
{
var snapshot = await LoadRelationalConfigurationAsync(dbContext, cancellationToken);
if (snapshot is null)
{
return null;
}

var json = JsonSerializer.Serialize(snapshot.Options, jsonSerializerOptions);
return new MerchantProcessingConfigurationSnapshot(snapshot.Options, json, snapshot.UpdatedUtc);
}

private async Task<RelationalConfigurationSnapshot?> LoadRelationalConfigurationAsync(MerchantFileProcessorDbContext dbContext, CancellationToken cancellationToken)
{
var authentication = await dbContext.MerchantProcessingAuthenticationRecords.AsNoTracking().FirstOrDefaultAsync(record => record.Id == 1, cancellationToken);
var fileProcessing = await dbContext.MerchantProcessingFileProcessingRecords.AsNoTracking().FirstOrDefaultAsync(record => record.Id == 1, cancellationToken);
Expand Down Expand Up @@ -147,7 +159,6 @@ private MerchantProcessingConfigurationSnapshot BuildBootstrapSnapshot()
return null;
}

var profileIds = fileProfiles.ToDictionary(record => record.Id);
var fileProfileFields = await LoadFieldRecordsAsync(dbContext.MerchantProcessingFileProfileFieldRecords, cancellationToken);
var headerFieldRecords = await LoadFieldRecordsAsync(dbContext.MerchantProcessingFileProfileHeaderFieldRecords, cancellationToken);
var trailerFieldRecords = await LoadFieldRecordsAsync(dbContext.MerchantProcessingFileProfileTrailerFieldRecords, cancellationToken);
Expand Down Expand Up @@ -243,8 +254,7 @@ private MerchantProcessingConfigurationSnapshot BuildBootstrapSnapshot()
contractDefinitions.Select(record => record.UpdatedUtc),
merchants.Select(record => record.UpdatedUtc));

var json = JsonSerializer.Serialize(options, jsonSerializerOptions);
return new MerchantProcessingConfigurationSnapshot(options, json, updatedUtc);
return new RelationalConfigurationSnapshot(options, updatedUtc);
}

private async Task<MerchantProcessingConfigurationSnapshot?> TryLoadFromLegacyConfigurationAsync(
Expand All @@ -267,6 +277,8 @@ private MerchantProcessingConfigurationSnapshot BuildBootstrapSnapshot()
return new MerchantProcessingConfigurationSnapshot(options, json, updatedUtc);
}

private sealed record RelationalConfigurationSnapshot(MerchantProcessingOptions Options, DateTimeOffset UpdatedUtc);

private async Task<bool> HasNormalizedConfigurationAsync(MerchantFileProcessorDbContext dbContext, CancellationToken cancellationToken)
{
return await dbContext.MerchantProcessingAuthenticationRecords.AnyAsync(record => record.Id == 1, cancellationToken) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,111 @@ namespace TransactionProcessing.MerchantFileProcessor.Configuration;

public static class MerchantProcessingOptionsValidator {
public static bool Validate(MerchantProcessingOptions options) {
if (string.IsNullOrWhiteSpace(options.Authentication.ClientId) || string.IsNullOrWhiteSpace(options.Authentication.ClientSecret)) {
return false;
}
return HasValidAuthentication(options)
&& HasValidFileProcessing(options)
&& HasValidTransactionGeneration(options)
&& HasValidPolling(options)
&& HasValidMerchantScan(options)
&& HasValidFileProfiles(options, out var fileProfileIds)
&& HasValidContracts(options, fileProfileIds)
&& HasValidMerchants(options);
}

if (string.IsNullOrWhiteSpace(options.FileProcessing.UserId) || !Guid.TryParse(options.FileProcessing.UserId, out _)) {
return false;
}
private static bool HasValidAuthentication(MerchantProcessingOptions options) =>
!string.IsNullOrWhiteSpace(options.Authentication.ClientId) && !string.IsNullOrWhiteSpace(options.Authentication.ClientSecret);

private static bool HasValidFileProcessing(MerchantProcessingOptions options) =>
!string.IsNullOrWhiteSpace(options.FileProcessing.UserId) && Guid.TryParse(options.FileProcessing.UserId, out _);

private static bool HasValidTransactionGeneration(MerchantProcessingOptions options) =>
options.TransactionGeneration.MinimumTransactionsPerContract > 1 &&
options.TransactionGeneration.MaximumTransactionsPerContract >= options.TransactionGeneration.MinimumTransactionsPerContract;

private static bool HasValidPolling(MerchantProcessingOptions options) =>
options.FileStatusPolling.PollIntervalSeconds > 0;

private static bool HasValidMerchantScan(MerchantProcessingOptions options) =>
options.MerchantScanIntervalSeconds > 0;

if (options.FileProfiles.Count == 0) {
private static bool HasValidFileProfiles(MerchantProcessingOptions options, out HashSet<string> fileProfileIds)
{
fileProfileIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

if (options.FileProfiles.Count == 0)
{
return false;
}

if (options.TransactionGeneration.MinimumTransactionsPerContract <= 1 || options.TransactionGeneration.MaximumTransactionsPerContract < options.TransactionGeneration.MinimumTransactionsPerContract) {
return false;
foreach (var fileProfile in options.FileProfiles)
{
if (!IsValidFileProfile(fileProfile, fileProfileIds))
{
return false;
}
}

if (options.FileStatusPolling.PollIntervalSeconds <= 0) {
return true;
}

private static bool IsValidFileProfile(FileProfileOptions fileProfile, ISet<string> fileProfileIds)
{
if (string.IsNullOrWhiteSpace(fileProfile.FileProfileId) ||
string.IsNullOrWhiteSpace(fileProfile.FileProcessorFileProfileId) ||
!fileProfileIds.Add(fileProfile.FileProfileId) ||
!Guid.TryParse(fileProfile.FileProcessorFileProfileId, out _) ||
!FileProfileFormats.All.Contains(fileProfile.Format) ||
string.IsNullOrWhiteSpace(fileProfile.FileExtension) ||
fileProfile.Fields.Count == 0)
{
return false;
}

if (options.MerchantScanIntervalSeconds <= 0) {
if (fileProfile.Format.Equals(FileProfileFormats.Delimited, StringComparison.OrdinalIgnoreCase) &&
string.IsNullOrWhiteSpace(fileProfile.Delimited.Delimiter))
{
return false;
}

if (options.ContractDefinitions.Count == 0) {
return AreFieldsValid(fileProfile.Fields) &&
AreFieldsValid(fileProfile.Delimited.HeaderFields) &&
AreFieldsValid(fileProfile.Delimited.TrailerFields);
}

private static bool HasValidContracts(MerchantProcessingOptions options, ISet<string> fileProfileIds)
{
if (options.ContractDefinitions.Count == 0)
{
return false;
}

var fileProfileIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var contractIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (var fileProfile in options.FileProfiles) {
if (string.IsNullOrWhiteSpace(fileProfile.FileProfileId) || string.IsNullOrWhiteSpace(fileProfile.FileProcessorFileProfileId) || !fileProfileIds.Add(fileProfile.FileProfileId) || !Guid.TryParse(fileProfile.FileProcessorFileProfileId, out _) || !FileProfileFormats.All.Contains(fileProfile.Format) || string.IsNullOrWhiteSpace(fileProfile.FileExtension) || fileProfile.Fields.Count == 0) {
return false;
}

if (fileProfile.Format.Equals(FileProfileFormats.Delimited, StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(fileProfile.Delimited.Delimiter)) {
return false;
}

if (!AreFieldsValid(fileProfile.Fields)) {
return false;
}

if (!AreFieldsValid(fileProfile.Delimited.HeaderFields) || !AreFieldsValid(fileProfile.Delimited.TrailerFields)) {
foreach (var contractDefinition in options.ContractDefinitions)
{
if (string.IsNullOrWhiteSpace(contractDefinition.ContractId) ||
string.IsNullOrWhiteSpace(contractDefinition.FileProfileId) ||
!contractIds.Add(contractDefinition.ContractId) ||
!fileProfileIds.Contains(contractDefinition.FileProfileId))
{
return false;
}
}

foreach (var contractDefinition in options.ContractDefinitions) {
if (string.IsNullOrWhiteSpace(contractDefinition.ContractId) || string.IsNullOrWhiteSpace(contractDefinition.FileProfileId) || !contractIds.Add(contractDefinition.ContractId) || !fileProfileIds.Contains(contractDefinition.FileProfileId)) {
return false;
}
}
return true;
}

foreach (var merchant in options.Merchants) {
private static bool HasValidMerchants(MerchantProcessingOptions options)
{
foreach (var merchant in options.Merchants)
{
var configuredTimes = merchant.RunTimesUtc.Count > 0 ? merchant.RunTimesUtc : [merchant.RunAtUtc];

if (string.IsNullOrWhiteSpace(merchant.EstateId) || string.IsNullOrWhiteSpace(merchant.MerchantId) || !Guid.TryParse(merchant.EstateId, out _) || !Guid.TryParse(merchant.MerchantId, out _) || configuredTimes.Count == 0 || configuredTimes.Any(runTime => !TimeOnly.TryParseExact(runTime, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _))) {
if (string.IsNullOrWhiteSpace(merchant.EstateId) ||
string.IsNullOrWhiteSpace(merchant.MerchantId) ||
!Guid.TryParse(merchant.EstateId, out _) ||
!Guid.TryParse(merchant.MerchantId, out _) ||
configuredTimes.Count == 0 ||
configuredTimes.Any(runTime => !TimeOnly.TryParseExact(runTime, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _)))
{
return false;
}
}
Expand Down
Loading
Loading