103 lines
5.7 KiB
JavaScript
103 lines
5.7 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const test = require("node:test");
|
|
|
|
const root = path.resolve(__dirname, "../..");
|
|
const read = relativePath => fs.readFileSync(path.join(root, relativePath), "utf8");
|
|
const host = read("MainWindow.xaml.cs");
|
|
const playout = read("MainWindow.Playout.cs");
|
|
const integration = read("MainWindow.NativeOperatorLog.cs");
|
|
const writer = read("src/MBN_STOCK_WEBVIEW.Infrastructure/Diagnostics/NativeOperatorLogWriter.cs");
|
|
const transitions = read("src/MBN_STOCK_WEBVIEW.Infrastructure/Diagnostics/NativeOperatorLogTransitions.cs");
|
|
|
|
function sliceBetween(source, startMarker, endMarker) {
|
|
const start = source.indexOf(startMarker);
|
|
const end = source.indexOf(endMarker, start + startMarker.length);
|
|
assert.ok(start >= 0, `${startMarker} must exist`);
|
|
assert.ok(end > start, `${endMarker} must follow ${startMarker}`);
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
test("lifecycle and provider transitions are wired with closed scalars", () => {
|
|
const constructor = sliceBetween(host, "public MainWindow()", "private void InitializeDatabaseRuntime(");
|
|
assert.match(constructor,
|
|
/InitializeComponent\(\);\s*LogNativeOperatorStartup\(\);\s*EnsureCloseConfirmationAttached\(\);/u);
|
|
const closed = sliceBetween(host, "private void OnClosed(", "private sealed record WebRequest(");
|
|
assert.match(closed, /LogNativeOperatorShutdown\(\);/u);
|
|
assert.match(integration,
|
|
/Interlocked\.Exchange\(ref _nativeStartupRecorded, 1\) == 0/u);
|
|
assert.match(integration,
|
|
/Interlocked\.Exchange\(ref _nativeShutdownRecorded, 1\) == 0/u);
|
|
|
|
const database = sliceBetween(
|
|
host,
|
|
"private async Task QueryAndPostDatabaseStatusAsync(",
|
|
"private void PostDatabaseStatus(");
|
|
assert.match(database,
|
|
/ObserveNativeDatabaseState\(DataSourceKind\.Oracle, DatabaseHealthState\.NotConfigured\)/u);
|
|
assert.match(database,
|
|
/ObserveNativeDatabaseState\(DataSourceKind\.MariaDb, DatabaseHealthState\.NotConfigured\)/u);
|
|
assert.match(database,
|
|
/foreach \(var status in statuses\)[\s\S]*ObserveNativeDatabaseState\(status\.Source, status\.State\)/u);
|
|
assert.match(database,
|
|
/ObserveNativeDatabaseState\(DataSourceKind\.Oracle, DatabaseHealthState\.Unhealthy\)/u);
|
|
assert.doesNotMatch(database, /ObserveNativeDatabaseState\([^\n]*(?:Message|Sql|Path|RequestId)/u);
|
|
|
|
assert.match(playout,
|
|
/_playoutEngine\.StatusChanged \+= OnPlayoutStatusChanged;\s*ObserveNativeTornadoState\(_playoutEngine\.Status\.State\);/u);
|
|
assert.match(playout,
|
|
/catch \(Exception exception\)\s*\{\s*ObserveNativeTornadoState\(PlayoutConnectionState\.Faulted\);/u);
|
|
assert.match(playout,
|
|
/private void OnPlayoutStatusChanged[\s\S]*?ObserveNativeTornadoState\(args\.Current\.State\);/u);
|
|
assert.match(transitions, /_oracleState != mapped/u);
|
|
assert.match(transitions, /_mariaDbState != mapped/u);
|
|
assert.match(transitions, /if \(_tornadoState == mapped\)/u);
|
|
});
|
|
|
|
test("only gate-entered commands receive one request and a closed terminal outcome", () => {
|
|
const commandHandler = sliceBetween(
|
|
playout,
|
|
"private async Task HandlePlayoutCommandAsync(",
|
|
"private async Task HandlePlayoutTimeoutQuarantineAsync(");
|
|
const gate = commandHandler.indexOf("_playoutCommandGate.WaitAsync(0)");
|
|
const requested = commandHandler.indexOf("LogNativePlayoutCommandRequested(request.Command)");
|
|
assert.ok(gate >= 0 && requested > gate, "request logging must happen only after gate entry");
|
|
assert.equal(commandHandler.indexOf("LogNativePlayoutCommandRequested(request.Command)", requested + 1), -1);
|
|
assert.match(commandHandler,
|
|
/var result = await ExecutePlayoutCommandAsync[\s\S]*LogNativePlayoutCommandCompleted\(request\.Command, result\.Code\);/u);
|
|
assert.match(commandHandler,
|
|
/catch \(OperationCanceledException\)[\s\S]*NativeOperatorPlayoutCompletion\.Cancelled/u);
|
|
assert.match(commandHandler,
|
|
/catch \(DatabaseOperationException exception\)[\s\S]*NativeOperatorPlayoutCompletion\.Unavailable/u);
|
|
assert.match(commandHandler,
|
|
/LegacySceneDataException or ArgumentException\)[\s\S]*NativeOperatorPlayoutCompletion\.Rejected/u);
|
|
assert.match(commandHandler, /catch\s*\{\s*LogNativePlayoutOutcomeUnknown\(request\.Command\);/u);
|
|
assert.match(playout,
|
|
/QuarantineForBrowserCorrelationLossAsync[\s\S]*LogCurrentNativePlayoutOutcomeUnknown\(\);/u);
|
|
|
|
assert.match(integration, /"prepare" => NativeOperatorPlayoutCommand\.Prepare/u);
|
|
assert.match(integration, /"take-in" => NativeOperatorPlayoutCommand\.TakeIn/u);
|
|
assert.match(integration, /"next" => NativeOperatorPlayoutCommand\.Next/u);
|
|
assert.match(integration, /"take-out" => NativeOperatorPlayoutCommand\.TakeOut/u);
|
|
for (const result of ["Success", "Rejected", "Cancelled", "Unavailable", "Failed"]) {
|
|
assert.match(integration, new RegExp(`PlayoutResultCode\\.${result} => NativeOperatorPlayoutCompletion\\.`));
|
|
}
|
|
assert.match(integration,
|
|
/result is PlayoutResultCode\.OutcomeUnknown or PlayoutResultCode\.TimedOut[\s\S]*TryMarkOutcomeUnknown/u);
|
|
assert.match(transitions, /if \(_currentCommand != command \|\| _terminalRecorded\)/u);
|
|
});
|
|
|
|
test("native boundary cannot receive or persist operational free-form data", () => {
|
|
assert.match(writer, /public readonly record struct NativeOperatorLogRecord/u);
|
|
assert.doesNotMatch(writer,
|
|
/public\s+(?:string|Exception|JsonElement|object|long|int)\??\s+\w+\s*\{\s*get;/u);
|
|
assert.doesNotMatch(integration, /(?:RequestId|Scene|Title|Path|Sql|Message)/u);
|
|
assert.match(integration, /_nativeOperatorLogWriter\.TryWrite\(record\)/u);
|
|
assert.match(integration,
|
|
/try[\s\S]*_nativeOperatorLogWriter\.TryWrite\(record\);[\s\S]*catch/u);
|
|
assert.doesNotMatch(integration, /TryWrite\((?:command|result|source|state|exception|payload)/u);
|
|
});
|