feat: complete legacy UI and playout parity migration
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#nullable enable
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.DbWriteSmoke;
|
||||
|
||||
internal enum DevelopmentDatabaseSmokeStage
|
||||
{
|
||||
None,
|
||||
Preflight,
|
||||
Create,
|
||||
CreateReadback,
|
||||
Save,
|
||||
SaveReadback,
|
||||
Cleanup,
|
||||
VerifyAbsent
|
||||
}
|
||||
|
||||
internal sealed record DevelopmentDatabaseSmokeResult(
|
||||
string ScenarioName,
|
||||
bool Passed,
|
||||
DevelopmentDatabaseSmokeStage FailureStage,
|
||||
string FailureClass,
|
||||
bool OutcomeUnknown,
|
||||
bool CleanupAttempted,
|
||||
bool CleanupVerified,
|
||||
bool ManualCleanupMayBeRequired);
|
||||
|
||||
/// <summary>
|
||||
/// Runs one collision-isolated development write scenario. Every delegate is
|
||||
/// invoked at most once. A mutation with an unknown outcome quarantines the
|
||||
/// scenario immediately: no cleanup mutation or later scenario may be issued.
|
||||
/// </summary>
|
||||
internal sealed class DevelopmentDatabaseSmokeScenario
|
||||
{
|
||||
private readonly Func<CancellationToken, Task> _preflight;
|
||||
private readonly Func<CancellationToken, Task> _create;
|
||||
private readonly Func<CancellationToken, Task> _verifyCreate;
|
||||
private readonly Func<CancellationToken, Task>? _save;
|
||||
private readonly Func<CancellationToken, Task>? _verifySave;
|
||||
private readonly Func<CancellationToken, Task> _cleanup;
|
||||
private readonly Func<CancellationToken, Task> _verifyAbsent;
|
||||
private readonly Func<Exception, bool> _isOutcomeUnknown;
|
||||
|
||||
internal DevelopmentDatabaseSmokeScenario(
|
||||
string name,
|
||||
Func<CancellationToken, Task> preflight,
|
||||
Func<CancellationToken, Task> create,
|
||||
Func<CancellationToken, Task> verifyCreate,
|
||||
Func<CancellationToken, Task>? save,
|
||||
Func<CancellationToken, Task>? verifySave,
|
||||
Func<CancellationToken, Task> cleanup,
|
||||
Func<CancellationToken, Task> verifyAbsent,
|
||||
Func<Exception, bool> isOutcomeUnknown)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
||||
if ((save is null) != (verifySave is null))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Save and save-readback delegates must either both be present or both be absent.",
|
||||
nameof(save));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
_preflight = preflight ?? throw new ArgumentNullException(nameof(preflight));
|
||||
_create = create ?? throw new ArgumentNullException(nameof(create));
|
||||
_verifyCreate = verifyCreate ?? throw new ArgumentNullException(nameof(verifyCreate));
|
||||
_save = save;
|
||||
_verifySave = verifySave;
|
||||
_cleanup = cleanup ?? throw new ArgumentNullException(nameof(cleanup));
|
||||
_verifyAbsent = verifyAbsent ?? throw new ArgumentNullException(nameof(verifyAbsent));
|
||||
_isOutcomeUnknown = isOutcomeUnknown ??
|
||||
throw new ArgumentNullException(nameof(isOutcomeUnknown));
|
||||
}
|
||||
|
||||
internal string Name { get; }
|
||||
|
||||
internal async Task<DevelopmentDatabaseSmokeResult> RunAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var currentStage = DevelopmentDatabaseSmokeStage.Preflight;
|
||||
Exception? primaryFailure = null;
|
||||
var failureStage = DevelopmentDatabaseSmokeStage.None;
|
||||
var outcomeUnknown = false;
|
||||
var failureClass = "none";
|
||||
var createKnownCommitted = false;
|
||||
var cleanupAttempted = false;
|
||||
var cleanupVerified = false;
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await _preflight(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
currentStage = DevelopmentDatabaseSmokeStage.Create;
|
||||
await _create(cancellationToken).ConfigureAwait(false);
|
||||
createKnownCommitted = true;
|
||||
|
||||
currentStage = DevelopmentDatabaseSmokeStage.CreateReadback;
|
||||
await _verifyCreate(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (_save is not null)
|
||||
{
|
||||
currentStage = DevelopmentDatabaseSmokeStage.Save;
|
||||
await _save(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
currentStage = DevelopmentDatabaseSmokeStage.SaveReadback;
|
||||
await _verifySave!(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
primaryFailure = exception;
|
||||
failureStage = currentStage;
|
||||
failureClass = RedactedDatabaseFailureClassifier.Classify(exception);
|
||||
outcomeUnknown = _isOutcomeUnknown(exception);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// A known successful create is cleaned up even when a later read-only
|
||||
// verification fails. An unknown mutation outcome is quarantined and
|
||||
// never followed by another mutation.
|
||||
if (createKnownCommitted && !outcomeUnknown)
|
||||
{
|
||||
cleanupAttempted = true;
|
||||
try
|
||||
{
|
||||
currentStage = DevelopmentDatabaseSmokeStage.Cleanup;
|
||||
await _cleanup(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
currentStage = DevelopmentDatabaseSmokeStage.VerifyAbsent;
|
||||
await _verifyAbsent(CancellationToken.None).ConfigureAwait(false);
|
||||
cleanupVerified = true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
primaryFailure = exception;
|
||||
failureStage = currentStage;
|
||||
failureClass = RedactedDatabaseFailureClassifier.Classify(exception);
|
||||
outcomeUnknown = _isOutcomeUnknown(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (primaryFailure is null && cleanupVerified)
|
||||
{
|
||||
return new DevelopmentDatabaseSmokeResult(
|
||||
Name,
|
||||
Passed: true,
|
||||
DevelopmentDatabaseSmokeStage.None,
|
||||
"none",
|
||||
OutcomeUnknown: false,
|
||||
CleanupAttempted: true,
|
||||
CleanupVerified: true,
|
||||
ManualCleanupMayBeRequired: false);
|
||||
}
|
||||
|
||||
return new DevelopmentDatabaseSmokeResult(
|
||||
Name,
|
||||
Passed: false,
|
||||
failureStage,
|
||||
failureClass,
|
||||
outcomeUnknown,
|
||||
cleanupAttempted,
|
||||
cleanupVerified,
|
||||
ManualCleanupMayBeRequired: outcomeUnknown &&
|
||||
(createKnownCommitted || failureStage == DevelopmentDatabaseSmokeStage.Create));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user