173 lines
6.9 KiB
C#
173 lines
6.9 KiB
C#
#nullable enable
|
|
|
|
using System.Text.RegularExpressions;
|
|
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.DbWriteSmoke;
|
|
|
|
internal static partial class OwnedManualSmokeResidueRecovery
|
|
{
|
|
internal const string RecoveryOption = "--reconcile-known-owned-manual-residue";
|
|
|
|
private static readonly IReadOnlyDictionary<ManualFinancialScreenKind, char> ScenarioCodes =
|
|
new Dictionary<ManualFinancialScreenKind, char>
|
|
{
|
|
[ManualFinancialScreenKind.RevenueComposition] = 'P',
|
|
[ManualFinancialScreenKind.GrowthMetrics] = 'G',
|
|
[ManualFinancialScreenKind.Sales] = 'S',
|
|
[ManualFinancialScreenKind.OperatingProfit] = 'O'
|
|
};
|
|
|
|
internal static bool IsRequested(IReadOnlyList<string> arguments) =>
|
|
arguments.Contains(RecoveryOption, StringComparer.Ordinal);
|
|
|
|
internal static async Task<int> RunAsync(
|
|
IReadOnlyList<string> arguments,
|
|
TextWriter output,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(arguments);
|
|
ArgumentNullException.ThrowIfNull(output);
|
|
if (!TryParse(arguments, out var configurationPath))
|
|
{
|
|
await output.WriteLineAsync(
|
|
"Usage: MBN_STOCK_WEBVIEW.DbWriteSmoke " + RecoveryOption + " " +
|
|
SmokeCommandLine.WriteAcknowledgement +
|
|
" [" + SmokeCommandLine.ConfigurationOption + " <database.local.json>]")
|
|
.ConfigureAwait(false);
|
|
return DevelopmentDatabaseWriteSmokeApp.NotAcknowledgedExitCode;
|
|
}
|
|
|
|
try
|
|
{
|
|
var options = new DatabaseOptionsLoader().Load(configurationPath);
|
|
options.Resilience.MaximumRetryCount = 0;
|
|
options.Resilience.InitialRetryDelayMilliseconds = 0;
|
|
var runtime = DatabaseRuntime.Create(options);
|
|
var service = new LegacyManualFinancialScreenService(
|
|
runtime.Executor,
|
|
new OracleManualFinancialMutationExecutor(
|
|
runtime.ConnectionFactory,
|
|
options.Resilience));
|
|
var prefix = SmokeRunIdentity.Prefix +
|
|
DateTimeOffset.UtcNow.ToString("yyMMdd", System.Globalization.CultureInfo.InvariantCulture);
|
|
var total = 0;
|
|
|
|
await output.WriteLineAsync(
|
|
"START: reconcile only the closed, current-UTC-date manual smoke namespace; details redacted.")
|
|
.ConfigureAwait(false);
|
|
foreach (var (screen, scenarioCode) in ScenarioCodes)
|
|
{
|
|
var result = await service.SearchAsync(
|
|
screen,
|
|
prefix,
|
|
LegacyManualFinancialScreenService.MaximumResults,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
var owned = result.Items.Where(item =>
|
|
IsOwned(item.Record.Identity.StockName, prefix, scenarioCode))
|
|
.ToArray();
|
|
if (result.IsTruncated || owned.Length > 1)
|
|
{
|
|
await output.WriteLineAsync(
|
|
"BLOCKED: the owned residue set was not singular and complete; writesAttempted=false.")
|
|
.ConfigureAwait(false);
|
|
return DevelopmentDatabaseWriteSmokeApp.FailureExitCode;
|
|
}
|
|
|
|
if (owned.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
await service.DeleteAsync(owned[0], cancellationToken).ConfigureAwait(false);
|
|
total++;
|
|
try
|
|
{
|
|
_ = await service.GetAsync(
|
|
owned[0].Record.Identity,
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (ManualFinancialNotFoundException)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
await output.WriteLineAsync(
|
|
"FAIL: an owned residue remained after one delete; no retry was issued.")
|
|
.ConfigureAwait(false);
|
|
return DevelopmentDatabaseWriteSmokeApp.FailureExitCode;
|
|
}
|
|
|
|
await output.WriteLineAsync(
|
|
$"PASS: reconciled={total}; every owned residue is absent; no retry was issued.")
|
|
.ConfigureAwait(false);
|
|
return DevelopmentDatabaseWriteSmokeApp.SuccessExitCode;
|
|
}
|
|
catch (ManualFinancialMutationException exception)
|
|
{
|
|
await output.WriteLineAsync(
|
|
"FAIL: failureClass=" + RedactedDatabaseFailureClassifier.Classify(exception) +
|
|
"; outcomeUnknown=" + exception.OutcomeUnknown.ToString().ToLowerInvariant() +
|
|
"; no retry was issued.")
|
|
.ConfigureAwait(false);
|
|
return exception.OutcomeUnknown
|
|
? DevelopmentDatabaseWriteSmokeApp.OutcomeUnknownExitCode
|
|
: DevelopmentDatabaseWriteSmokeApp.FailureExitCode;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
await output.WriteLineAsync(
|
|
"FAIL: failureClass=" + RedactedDatabaseFailureClassifier.Classify(exception) +
|
|
"; no retry was issued; details redacted.")
|
|
.ConfigureAwait(false);
|
|
return DevelopmentDatabaseWriteSmokeApp.FailureExitCode;
|
|
}
|
|
}
|
|
|
|
private static bool IsOwned(string value, string prefix, char scenarioCode) =>
|
|
value.Length == 20 &&
|
|
value.StartsWith(prefix, StringComparison.Ordinal) &&
|
|
value[^1] == scenarioCode &&
|
|
OwnedNamePattern().IsMatch(value);
|
|
|
|
private static bool TryParse(
|
|
IReadOnlyList<string> arguments,
|
|
out string? configurationPath)
|
|
{
|
|
configurationPath = null;
|
|
var recovery = false;
|
|
var acknowledged = false;
|
|
for (var index = 0; index < arguments.Count; index++)
|
|
{
|
|
switch (arguments[index])
|
|
{
|
|
case RecoveryOption when !recovery:
|
|
recovery = true;
|
|
break;
|
|
case SmokeCommandLine.WriteAcknowledgement when !acknowledged:
|
|
acknowledged = true;
|
|
break;
|
|
case SmokeCommandLine.ConfigurationOption when configurationPath is null &&
|
|
index + 1 < arguments.Count:
|
|
configurationPath = arguments[++index];
|
|
if (string.IsNullOrWhiteSpace(configurationPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return recovery && acknowledged;
|
|
}
|
|
|
|
[GeneratedRegex("^MBW[0-9]{6}[0-9A-F]{10}[PGSO]$", RegexOptions.CultureInvariant)]
|
|
private static partial Regex OwnedNamePattern();
|
|
}
|