Files
MBN_STOCK_WEBVIEW/tools/MBN_STOCK_WEBVIEW.DbSmoke/Program.cs

758 lines
28 KiB
C#

using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using MBN_STOCK_WEBVIEW.Infrastructure;
using MMoneyCoderSharp.Data;
Console.OutputEncoding = Encoding.UTF8;
if (args.Length == 1 &&
args[0].Equals("--manual-import-audit", StringComparison.OrdinalIgnoreCase))
{
return RunManualImportAudit();
}
if (args.Length == 1 &&
args[0].Equals("--manual-import-fixture", StringComparison.OrdinalIgnoreCase))
{
return await RunManualImportFixtureAsync();
}
using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(10));
Console.CancelKeyPress += (_, eventArgs) =>
{
eventArgs.Cancel = true;
cancellation.Cancel();
};
try
{
var selection = CreateRuntimeSelection(args);
var runtime = selection.Runtime;
var statuses = await runtime.HealthService.CheckAllAsync(cancellation.Token);
foreach (var status in statuses)
{
Console.WriteLine(FormatHealthStatus(status));
}
if (statuses.Any(status => status.State != DatabaseHealthState.Healthy))
{
Console.Error.WriteLine(
"Smoke test stopped because both database health probes must be healthy.");
return 2;
}
await VerifyOracleServerVersionAsync(runtime.Executor, cancellation.Token);
await VerifyStockSearchAsync(runtime.Executor, cancellation.Token);
await VerifyThemeSelectorAsync(runtime.Executor, cancellation.Token);
await VerifyOverseasSelectorsAsync(runtime.Executor, cancellation.Token);
await VerifyExpertSelectorAsync(runtime.Executor, cancellation.Token);
await VerifyTradingHaltSelectorAsync(runtime.Executor, cancellation.Token);
await VerifyOperatorCatalogSchemaAsync(runtime.Executor, cancellation.Token);
await NamedPlaylistReadOnlyDbAudit.RunAsync(runtime.Executor, cancellation.Token);
await NamedManualRestoreReadOnlyAudit.RunAsync(
runtime.Executor,
NamedManualRestoreReadOnlyAudit.DefaultTrustedDirectory(),
cancellation.Token);
await NxtThemeRestoreDbAudit.RunAsync(
runtime.Executor,
runtime.Options.MariaDb,
selection.ReferenceMariaDb,
cancellation.Token);
await VerifyLegacyComparisonImportAsync(runtime.Executor, cancellation.Token);
var service = new LegacyMarketDataService(runtime.Executor);
await VerifySnapshotAsync(service, MarketDataView.Kospi, cancellation.Token);
await VerifySnapshotAsync(service, MarketDataView.Kosdaq, cancellation.Token);
await VerifySnapshotAsync(service, MarketDataView.Index, cancellation.Token);
await VerifySnapshotAsync(service, MarketDataView.Overseas, cancellation.Token);
await RealSceneDbSmoke.RunAsync(runtime.Executor, cancellation.Token);
Console.WriteLine("REAL_DB_SMOKE: PASS");
return 0;
}
catch (OperationCanceledException)
{
Console.Error.WriteLine("REAL_DB_SMOKE: CANCELED_OR_TIMED_OUT");
return 3;
}
catch (DatabaseOperationException exception)
{
Console.Error.WriteLine(
"REAL_DB_SMOKE: FAIL - " +
$"Source={exception.DataSource} " +
$"IsTransient={FormatNullableBoolean(exception.IsTransient)} " +
$"ProviderErrorCode={FormatProviderErrorCode(exception.ProviderErrorCode)}");
return 1;
}
catch (DatabaseInfrastructureException)
{
Console.Error.WriteLine(
"REAL_DB_SMOKE: FAIL - A database infrastructure error occurred. " +
"No provider details were printed.");
return 1;
}
catch (OverseasIndustryIndexSearchDataException exception)
{
Console.Error.WriteLine($"REAL_DB_SMOKE: FAIL - {exception.Message}");
return 1;
}
catch (Exception exception)
{
Console.Error.WriteLine(
$"REAL_DB_SMOKE: FAIL - {exception.GetType().Name}. No provider details were printed.");
return 1;
}
static string FormatHealthStatus(DatabaseHealthStatus status)
{
var summary =
$"{status.Source}: {status.State} " +
$"({status.Elapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture)} ms)";
if (status.State == DatabaseHealthState.Healthy)
{
return summary;
}
return summary +
$" IsTransient={FormatNullableBoolean(status.IsTransient)}" +
$" ProviderErrorCode={FormatProviderErrorCode(status.ProviderErrorCode)}";
}
static string FormatNullableBoolean(bool? value) => value switch
{
true => "true",
false => "false",
null => "none"
};
static string FormatProviderErrorCode(int? value) =>
value?.ToString(CultureInfo.InvariantCulture) ?? "none";
static DatabaseSmokeRuntimeSelection CreateRuntimeSelection(string[] arguments)
{
if (arguments.Length == 0)
{
return CreateJsonRuntimeSelection(configurationPath: null);
}
if (arguments.Length == 2 &&
arguments[0].Equals("--config", StringComparison.OrdinalIgnoreCase))
{
return CreateJsonRuntimeSelection(arguments[1]);
}
if (arguments.Length == 2 &&
arguments[0].Equals("--legacy-runtime-root", StringComparison.OrdinalIgnoreCase))
{
var root = Path.GetFullPath(arguments[1]);
var iniPath = Path.Combine(root, "Res", "MmoneyCoder.ini");
if (!File.Exists(iniPath))
{
throw new DatabaseConfigurationException(
"The legacy runtime root does not contain Res\\MmoneyCoder.ini.");
}
// Keep the selected file's endpoint as the comparison reference before
// environment overrides are applied. Credentials never leave the
// loader-owned DatabaseOptions instance.
var selectedOptions = new LegacyIniDatabaseOptionsLoader(_ => null).Load(iniPath);
var runtime = DatabaseRuntime.CreateLegacyExecutableDefault(
root,
fallbackConfigurationPath: Path.Combine(root, "missing-database.json"),
legacyOverridePath: Path.Combine(root, "missing-legacy.ini"));
return new DatabaseSmokeRuntimeSelection(
runtime,
MariaDbEndpointReference.From(selectedOptions.MariaDb));
}
throw new DatabaseConfigurationException(
"Usage: MBN_STOCK_WEBVIEW.DbSmoke [--config <path>|--legacy-runtime-root <bin-debug-path>|--manual-import-audit|--manual-import-fixture]");
}
static DatabaseSmokeRuntimeSelection CreateJsonRuntimeSelection(string? configurationPath)
{
var configuredOptions = new DatabaseOptionsLoader(_ => null).Load(configurationPath);
var runtime = DatabaseRuntime.CreateDefault(configurationPath);
var configuredMariaDb = configuredOptions.MariaDb;
var reference = HasCompleteEndpointIdentity(configuredMariaDb)
? MariaDbEndpointReference.From(configuredMariaDb)
: MariaDbEndpointReference.From(runtime.Options.MariaDb);
return new DatabaseSmokeRuntimeSelection(runtime, reference);
}
static bool HasCompleteEndpointIdentity(MariaDbDatabaseOptions options) =>
!string.IsNullOrWhiteSpace(options.Host) &&
options.Port is >= 1 and <= 65_535 &&
!string.IsNullOrWhiteSpace(options.Database);
static async Task VerifyLegacyComparisonImportAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
const string expectedSourceSha256 =
"1EE76BC464FB1C44C8B6546E99CDC21C726C0E2C24AD344F5C19AE41BFC0D2F2";
var result = await new LegacyComparisonPairImportService(
executor,
TrustedLegacyComparisonFileSource.CreateDefault())
.ImportAsync(cancellationToken);
if (!string.Equals(
result.SourceSha256,
expectedSourceSha256,
StringComparison.Ordinal) ||
result.SourceRowCount != 8 ||
result.Pairs.Count != 8 ||
result.Pairs.Where((pair, index) => pair.RowNumber != index + 1).Any())
{
throw new DatabaseConfigurationException(
"The fixed legacy comparison import did not reproduce its audited 8-row identity set.");
}
Console.WriteLine(
$"LegacyComparisonImport: rows={result.SourceRowCount.ToString(CultureInfo.InvariantCulture)}, " +
$"sha256={result.SourceSha256}, identities=fresh-read-only");
}
static async Task<int> RunManualImportFixtureAsync()
{
const string expectedSourceSha256 =
"fd39a3639e574a19f174225e8e65234d6bdd4c58700bb5d10230a779a03a2114";
using var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(1));
var profile = Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile,
Environment.SpecialFolderOption.DoNotVerify);
var localAppData = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData,
Environment.SpecialFolderOption.DoNotVerify);
if (string.IsNullOrWhiteSpace(profile) || string.IsNullOrWhiteSpace(localAppData))
{
Console.Error.WriteLine("MANUAL_IMPORT_FIXTURE: FAIL - required profile roots are unavailable.");
return 1;
}
var sourceDirectory = Path.Combine(
profile,
"source",
"repos",
FixedLegacyManualOperatorDataSource.FixedRelativeDirectory);
var operatingDirectory = Path.Combine(
localAppData,
"MBN_STOCK_WEBVIEW",
"OperatorData");
var temporaryDirectory = Path.GetFullPath(Path.Combine(
Path.GetTempPath(),
"MBN_STOCK_WEBVIEW-manual-import-fixture-" + Guid.NewGuid().ToString("N")));
var expectedDataFiles = new[]
{
"개인.dat",
"외국인.dat",
"기관.dat",
"VI발동.dat"
};
var trackedOperatingFiles = expectedDataFiles.Concat(new[]
{
LegacyManualOperatorDataImporter.MarkerFileName,
LegacyManualOperatorDataImporter.IntentFileName
}).ToArray();
var sourceBefore = SnapshotFiles(sourceDirectory, expectedDataFiles);
var operatingBefore = SnapshotFiles(operatingDirectory, trackedOperatingFiles);
try
{
var destination = TrustedManualDirectory.PreparePrivateWritableDirectory(
temporaryDirectory);
LegacyManualOperatorImportResult result;
LegacyManualOperatorImportException? secondFailure = null;
using (var importer = new LegacyManualOperatorDataImporter(
destination,
new FixedLegacyManualOperatorDataSource()))
{
result = await importer.ImportAsync(cancellation.Token);
try
{
_ = await importer.ImportAsync(cancellation.Token);
}
catch (LegacyManualOperatorImportException exception)
{
secondFailure = exception;
}
}
var sourceAfter = SnapshotFiles(sourceDirectory, expectedDataFiles);
var operatingAfter = SnapshotFiles(operatingDirectory, trackedOperatingFiles);
var fixtureFiles = SnapshotFiles(destination, trackedOperatingFiles);
var dataHashesMatch = expectedDataFiles.All(name =>
sourceBefore.TryGetValue(name, out var sourceValue) &&
fixtureFiles.TryGetValue(name, out var fixtureValue) &&
string.Equals(sourceValue, fixtureValue, StringComparison.Ordinal));
var markerPresent = fixtureFiles.TryGetValue(
LegacyManualOperatorDataImporter.MarkerFileName,
out var markerValue) && markerValue != "missing";
var intentAbsent = fixtureFiles.TryGetValue(
LegacyManualOperatorDataImporter.IntentFileName,
out var intentValue) && intentValue == "missing";
var runtimeState = LegacyManualOperatorDataRuntimeGuard.Inspect(destination).State;
if (!SnapshotsEqual(sourceBefore, sourceAfter) ||
!SnapshotsEqual(operatingBefore, operatingAfter) ||
!dataHashesMatch || !markerPresent || !intentAbsent ||
result.MarkerVersion != LegacyManualOperatorDataImporter.MarkerVersion ||
result.NetSellRowCount != 15 ||
result.ViItemCount != 9 ||
!string.Equals(
result.SourceSha256,
expectedSourceSha256,
StringComparison.Ordinal) ||
runtimeState != LegacyManualOperatorDataRuntimeState.ReadyImportedData ||
secondFailure is null ||
secondFailure.Failure != LegacyManualOperatorImportFailure.AlreadyImported ||
secondFailure.OutcomeUnknown)
{
Console.Error.WriteLine(
"MANUAL_IMPORT_FIXTURE: FAIL - an isolated import invariant was not reproduced.");
return 1;
}
Console.WriteLine(
$"MANUAL_IMPORT_FIXTURE: PASS rows={result.NetSellRowCount.ToString(CultureInfo.InvariantCulture)} " +
$"vi={result.ViItemCount.ToString(CultureInfo.InvariantCulture)} " +
$"sourceSha256={result.SourceSha256} state={runtimeState} " +
"secondAttempt=AlreadyImported outcomeUnknown=false " +
"sourceUnchanged=true operatingImportSetUnchanged=true");
return 0;
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
Console.Error.WriteLine("MANUAL_IMPORT_FIXTURE: CANCELED_OR_TIMED_OUT");
return 3;
}
catch (LegacyManualOperatorImportException exception)
{
Console.Error.WriteLine(
$"MANUAL_IMPORT_FIXTURE: FAIL - failure={exception.Failure} " +
$"outcomeUnknown={exception.OutcomeUnknown.ToString().ToLowerInvariant()}");
return exception.OutcomeUnknown ? 5 : 1;
}
catch (Exception exception)
{
Console.Error.WriteLine(
$"MANUAL_IMPORT_FIXTURE: FAIL - {exception.GetType().Name}");
return 1;
}
finally
{
RemoveOwnedFixtureDirectory(temporaryDirectory);
}
}
static int RunManualImportAudit()
{
try
{
var profile = Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile,
Environment.SpecialFolderOption.DoNotVerify);
var localAppData = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData,
Environment.SpecialFolderOption.DoNotVerify);
if (string.IsNullOrWhiteSpace(profile) || string.IsNullOrWhiteSpace(localAppData))
{
Console.Error.WriteLine("MANUAL_IMPORT_AUDIT: FAIL - required profile roots are unavailable.");
return 1;
}
var sourceDirectory = Path.Combine(
profile,
"source",
"repos",
FixedLegacyManualOperatorDataSource.FixedRelativeDirectory);
var operatingDirectory = Path.Combine(
localAppData,
"MBN_STOCK_WEBVIEW",
"OperatorData");
var expectedDataFiles = new[]
{
"개인.dat",
"외국인.dat",
"기관.dat",
"VI발동.dat"
};
var source = SnapshotFiles(sourceDirectory, expectedDataFiles);
var operating = SnapshotFiles(
operatingDirectory,
expectedDataFiles.Concat(new[]
{
LegacyManualOperatorDataImporter.MarkerFileName,
LegacyManualOperatorDataImporter.IntentFileName
}));
var markerPresent = operating[LegacyManualOperatorDataImporter.MarkerFileName] != "missing";
var intentPresent = operating[LegacyManualOperatorDataImporter.IntentFileName] != "missing";
var dataPresent = expectedDataFiles.Count(name => operating[name] != "missing");
var sourcePresent = expectedDataFiles.Count(name => source[name] != "missing");
var byteIdentical = expectedDataFiles.All(name =>
source[name] != "missing" &&
string.Equals(source[name], operating[name], StringComparison.Ordinal));
var stagedPresent = Directory.Exists(operatingDirectory) &&
Directory.EnumerateFileSystemEntries(
operatingDirectory,
".legacy-manual-import-v1-*",
SearchOption.TopDirectoryOnly).Any();
var runtimeState = LegacyManualOperatorDataRuntimeGuard
.Inspect(operatingDirectory)
.State;
var (decision, outcomeUnknown) = runtimeState switch
{
LegacyManualOperatorDataRuntimeState.InterruptedImport =>
("InterruptedImport", true),
LegacyManualOperatorDataRuntimeState.InvalidImportedState =>
("InvalidImportedState", false),
LegacyManualOperatorDataRuntimeState.ReadyImportedData =>
("AlreadyImported", false),
_ when dataPresent > 0 || stagedPresent =>
("DestinationNotEmpty", false),
_ when sourcePresent == expectedDataFiles.Length =>
("Eligible", false),
_ => ("SourceUnavailable", false)
};
Console.WriteLine(
$"MANUAL_IMPORT_AUDIT: PASS sourceFiles={sourcePresent.ToString(CultureInfo.InvariantCulture)}/4 " +
$"operatingFiles={dataPresent.ToString(CultureInfo.InvariantCulture)}/4 " +
$"sourceByteIdentical={byteIdentical.ToString().ToLowerInvariant()} " +
$"marker={markerPresent.ToString().ToLowerInvariant()} " +
$"intent={intentPresent.ToString().ToLowerInvariant()} " +
$"staged={stagedPresent.ToString().ToLowerInvariant()} " +
$"runtime={runtimeState} " +
$"decision={decision} outcomeUnknown={outcomeUnknown.ToString().ToLowerInvariant()} " +
"writesAttempted=false");
return 0;
}
catch (Exception exception)
{
Console.Error.WriteLine(
$"MANUAL_IMPORT_AUDIT: FAIL - {exception.GetType().Name}");
return 1;
}
}
static IReadOnlyDictionary<string, string> SnapshotFiles(
string directory,
IEnumerable<string> fileNames)
{
const long maximumAuditFileBytes = 32 * 1024;
var result = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var fileName in fileNames)
{
var path = Path.Combine(directory, fileName);
if (!File.Exists(path))
{
result.Add(fileName, "missing");
continue;
}
using var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite | FileShare.Delete);
if (stream.Length is < 0 or > maximumAuditFileBytes)
{
throw new InvalidDataException(
"A manual-import audit file exceeded its closed size boundary.");
}
var bytes = new byte[checked((int)stream.Length)];
stream.ReadExactly(bytes);
if (stream.ReadByte() != -1)
{
throw new InvalidDataException(
"A manual-import audit file changed beyond its closed size boundary.");
}
var hash = Convert.ToHexString(SHA256.HashData(bytes));
result.Add(
fileName,
bytes.Length.ToString(CultureInfo.InvariantCulture) + ":" + hash);
}
return result;
}
static bool SnapshotsEqual(
IReadOnlyDictionary<string, string> first,
IReadOnlyDictionary<string, string> second) =>
first.Count == second.Count && first.All(pair =>
second.TryGetValue(pair.Key, out var value) &&
string.Equals(pair.Value, value, StringComparison.Ordinal));
static void RemoveOwnedFixtureDirectory(string directory)
{
if (!Directory.Exists(directory))
{
return;
}
var temporaryRoot = Path.TrimEndingDirectorySeparator(
Path.GetFullPath(Path.GetTempPath()));
var resolved = Path.TrimEndingDirectorySeparator(Path.GetFullPath(directory));
var prefix = temporaryRoot + Path.DirectorySeparatorChar;
const string ownedNamePrefix = "MBN_STOCK_WEBVIEW-manual-import-fixture-";
var ownedName = Path.GetFileName(resolved);
var ownedSuffix = ownedName.StartsWith(ownedNamePrefix, StringComparison.Ordinal)
? ownedName[ownedNamePrefix.Length..]
: string.Empty;
if (!resolved.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) ||
ownedSuffix.Length != 32 ||
ownedSuffix.Any(static value => !char.IsAsciiHexDigit(value)) ||
(File.GetAttributes(resolved) & FileAttributes.ReparsePoint) != 0)
{
throw new InvalidOperationException(
"Refusing to remove a directory outside the owned fixture boundary.");
}
var pendingDirectories = new Stack<string>();
pendingDirectories.Push(resolved);
while (pendingDirectories.TryPop(out var current))
{
foreach (var entry in Directory.EnumerateFileSystemEntries(
current,
"*",
new EnumerationOptions
{
RecurseSubdirectories = false,
ReturnSpecialDirectories = false,
AttributesToSkip = 0
}))
{
var attributes = File.GetAttributes(entry);
if ((attributes & FileAttributes.ReparsePoint) != 0)
{
throw new InvalidOperationException(
"Refusing to recursively remove a fixture containing a reparse point.");
}
if ((attributes & FileAttributes.Directory) != 0)
{
pendingDirectories.Push(entry);
}
}
}
Directory.Delete(resolved, recursive: true);
}
static async Task VerifyOracleServerVersionAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
const string query =
"SELECT VERSION FROM PRODUCT_COMPONENT_VERSION " +
"WHERE PRODUCT LIKE 'Oracle Database%' FETCH FIRST 1 ROWS ONLY";
var table = await executor.ExecuteAsync(
DataSourceKind.Oracle,
"OracleVersion",
query,
cancellationToken);
if (table.Rows.Count == 0 || table.Columns.Count == 0)
{
throw new DatabaseConfigurationException(
"The Oracle server version query returned no version.");
}
var version = Convert.ToString(
table.Rows[0][0],
CultureInfo.InvariantCulture) ?? string.Empty;
var match = Regex.Match(
version,
@"(?<!\d)(?<major>\d{2})(?:\.\d+)",
RegexOptions.CultureInvariant);
if (!match.Success ||
!int.TryParse(
match.Groups["major"].Value,
NumberStyles.None,
CultureInfo.InvariantCulture,
out var major))
{
throw new DatabaseConfigurationException(
"The Oracle server major version could not be determined.");
}
Console.WriteLine($"Oracle server major version: {major}");
if (major < 19)
{
throw new DatabaseConfigurationException(
"Oracle.ManagedDataAccess.Core requires Oracle Database 19c or newer.");
}
}
static async Task VerifySnapshotAsync(
IMarketDataService service,
MarketDataView view,
CancellationToken cancellationToken)
{
var snapshot = await service.GetAsync(
view,
maximumRowsPerTable: 5,
cancellationToken);
if (snapshot.Tables.Count == 0)
{
throw new DatabaseConfigurationException(
$"{view} returned no database result set.");
}
foreach (var table in snapshot.Tables)
{
if (table.Columns.Count == 0)
{
throw new DatabaseConfigurationException(
$"{view}/{table.Name} returned no columns.");
}
Console.WriteLine(
$"{view}/{table.Source}/{table.Name}: " +
$"{table.TotalRowCount.ToString(CultureInfo.InvariantCulture)} rows");
}
}
static async Task VerifyOperatorCatalogSchemaAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
var result = await new LegacyOperatorCatalogSchemaValidationService(executor)
.ValidateAsync(cancellationToken);
var themePersistence = new LegacyThemeCatalogPersistenceService(executor);
_ = await themePersistence.SuggestNextCodeAsync(
ThemeMarket.Krx,
cancellationToken);
_ = await themePersistence.SuggestNextCodeAsync(
ThemeMarket.Nxt,
cancellationToken);
_ = await new LegacyExpertCatalogPersistenceService(executor)
.SuggestNextCodeAsync(cancellationToken);
Console.WriteLine(
"Operator catalog schema: " +
string.Join(
",",
result.ValidatedSources.Select(static source => source.ToString())) +
" (read-only zero-row preflight and code-range validation)");
}
static async Task VerifyStockSearchAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
var result = await new LegacyStockSearchService(executor).SearchAsync(
"삼성",
maximumResults: 100,
cancellationToken);
if (result.Items.Count == 0 ||
!result.Items.Any(item =>
item.Name.Contains("삼성", StringComparison.OrdinalIgnoreCase)))
{
throw new DatabaseConfigurationException(
"The production stock search returned no matching rows for the smoke selector.");
}
var counts = result.Items
.GroupBy(item => item.Market)
.OrderBy(group => group.Key)
.Select(group => $"{group.Key}={group.Count().ToString(CultureInfo.InvariantCulture)}");
Console.WriteLine(
$"StockSearch: {result.Items.Count.ToString(CultureInfo.InvariantCulture)} rows " +
$"({string.Join(", ", counts)})");
}
static async Task VerifyThemeSelectorAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
Console.WriteLine("ThemeSelector: BEGIN");
var service = new LegacyThemeSelectionService(executor);
var themes = await service.SearchAsync(
string.Empty,
ThemeSession.PreMarket,
maximumResults: 100,
cancellationToken);
if (themes.Items.Count == 0)
{
throw new DatabaseConfigurationException(
"The production theme selector returned no unambiguous canonical rows.");
}
var selected = themes.Items[0];
var preview = await service.PreviewAsync(
selected.Identity,
maximumItems: 100,
cancellationToken);
Console.WriteLine(
$"ThemeSelector: themes={themes.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
$"preview={preview.Items.Count.ToString(CultureInfo.InvariantCulture)}");
}
static async Task VerifyOverseasSelectorsAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
Console.WriteLine("OverseasIndustrySelector: BEGIN");
var industries = await new LegacyOverseasIndustryIndexSearchService(executor)
.SearchAsync(string.Empty, maximumResults: 100, cancellationToken);
Console.WriteLine("WorldStockSelector: BEGIN");
var stocks = await new LegacyWorldStockSearchService(executor)
.SearchAsync(string.Empty, maximumResults: 100, cancellationToken);
if (industries.Items.Count == 0 || stocks.Items.Count == 0)
{
throw new DatabaseConfigurationException(
"The production overseas selectors returned no rows.");
}
Console.WriteLine(
$"OverseasSelectors: industries={industries.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
$"stocks={stocks.Items.Count.ToString(CultureInfo.InvariantCulture)}");
}
static async Task VerifyExpertSelectorAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
Console.WriteLine("ExpertSelector: BEGIN");
var service = new LegacyExpertSelectionService(executor);
var experts = await service.SearchAsync(
string.Empty,
maximumResults: 100,
cancellationToken);
if (experts.Items.Count == 0)
{
throw new DatabaseConfigurationException(
"The production expert selector returned no rows.");
}
var preview = await service.PreviewAsync(
experts.Items[0].Identity,
maximumItems: 100,
cancellationToken);
Console.WriteLine(
$"ExpertSelector: experts={experts.Items.Count.ToString(CultureInfo.InvariantCulture)}, " +
$"preview={preview.Items.Count.ToString(CultureInfo.InvariantCulture)}");
}
static async Task VerifyTradingHaltSelectorAsync(
IDataQueryExecutor executor,
CancellationToken cancellationToken)
{
Console.WriteLine("TradingHaltSelector: BEGIN");
var result = await new LegacyTradingHaltSelectionService(executor).SearchAsync(
string.Empty,
maximumResults: 500,
cancellationToken);
Console.WriteLine(
$"TradingHaltSelector: {result.Items.Count.ToString(CultureInfo.InvariantCulture)} rows");
}
internal sealed record DatabaseSmokeRuntimeSelection(
DatabaseRuntime Runtime,
MariaDbEndpointReference ReferenceMariaDb);