Files
MBN_STOCK_WEBVIEW/tests/Web/manual-lists-bridge-integration.test.cjs

119 lines
6.8 KiB
JavaScript

"use strict";
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const assert = require("node:assert/strict");
const root = path.resolve(__dirname, "..", "..");
const native = fs.readFileSync(path.join(root, "MainWindow.ManualLists.cs"), "utf8");
const dispatch = fs.readFileSync(path.join(root, "MainWindow.xaml.cs"), "utf8");
const importer = fs.readFileSync(path.join(
root,
"src",
"MBN_STOCK_WEBVIEW.Infrastructure",
"Playout",
"LegacyManualOperatorDataImporter.cs"), "utf8");
const playout = fs.readFileSync(path.join(root, "MainWindow.Playout.cs"), "utf8");
const factory = fs.readFileSync(path.join(root, "LegacySceneRuntimeFactory.cs"), "utf8");
const app = fs.readFileSync(path.join(root, "Web/app.js"), "utf8");
test("generic catalog cannot add the s5025 sample outside the trusted manual-list workflow", () => {
assert.match(app, /operatorInputBuilderKeys[\s\S]*"s5025"/u);
assert.match(app,
/item\.builderKey === "s5025"[\s\S]*투자자별 수동 목록 화면에서 추가/u);
});
test("native VI responses expose the canonical version and no-op persistence receipt", () => {
assert.match(native, /ReadSnapshotAsync\(_lifetimeCancellation\.Token\)/u);
assert.match(native, /version = snapshot\.RowVersion/u);
assert.match(native, /persisted = result\.Persisted/u);
assert.match(native, /version = result\.Snapshot\.RowVersion/u);
});
test("VI writes require a closed expected version and compare it under the data gate before mutation", () => {
assert.match(native, /HasOnlyProperties\(payload, "requestId", "expectedVersion", "items"\)/u);
assert.match(native, /TrustedViManualReference\.IsRowVersion\(expectedVersion\)/u);
const methodStart = native.indexOf("private async Task HandleViManualListWriteAsync");
const methodEnd = native.indexOf("private async Task<bool> TryEnterManualMutationGatesAsync", methodStart);
const method = native.slice(methodStart, methodEnd);
const gate = method.indexOf("TryEnterManualMutationGatesAsync");
const reread = method.indexOf("var currentSnapshot = await store.ReadSnapshotAsync");
const compare = method.indexOf("currentSnapshot.RowVersion");
const stale = method.indexOf('"STALE_DATA"');
const mutation = method.indexOf("var result = await store.WriteAsync");
assert.ok(methodStart >= 0 && methodEnd > methodStart);
assert.ok(gate >= 0 && gate < reread && reread < compare && compare < stale && stale < mutation);
assert.match(method.slice(stale, mutation), /retryable: false,[\s\S]*outcomeUnknown: false/u);
});
test("different post-error readback always enters OutcomeUnknown quarantine", () => {
assert.equal(native.includes('"SAVE_FAILED"'), false);
assert.match(native, /수동 순매도 저장 중 파일 내용이 달라져 결과를 확정할 수 없습니다/u);
assert.match(native, /VI 수동 목록 저장 중 파일 내용이 달라져 결과를 확정할 수 없습니다/u);
assert.ok((native.match(/PostManualOperatorQuarantine\(requestId, "save-/gu) || []).length >= 4);
});
test("configured external operator paths are disabled before any directory creation", () => {
const configuredGuard = native.indexOf("if (!string.IsNullOrWhiteSpace(configuredDirectory))");
const privatePrepare = native.indexOf("TrustedManualDirectory.PreparePrivateWritableDirectory");
assert.ok(configuredGuard >= 0);
assert.ok(privatePrepare > configuredGuard);
assert.equal(native.includes("Directory.CreateDirectory(directory)"), false);
});
test("interrupted import guard runs before either manual store can be constructed", () => {
const inspection = native.indexOf("LegacyManualOperatorDataRuntimeGuard.Inspect(directory)");
const readyGuard = native.indexOf("if (!inspection.IsReady)", inspection);
const failClosedReturn = native.indexOf("return;", readyGuard);
const netStore = native.indexOf("new S5025TrustedManualFileStore(directory)", inspection);
const viStore = native.indexOf("new ViTrustedManualFileStore(directory)", inspection);
assert.ok(inspection >= 0 && readyGuard > inspection && failClosedReturn > readyGuard);
assert.ok(netStore > failClosedReturn && viStore > failClosedReturn);
assert.match(importer, /if \(EntryExists\(intentPath\)\)[\s\S]*Do not read any production file/u);
});
test("legacy import commit has a durable intent before moves and marker remains last", () => {
const commitStart = importer.indexOf("private void CommitAndVerify");
const commitEnd = importer.indexOf("private void WriteIntentDurably", commitStart);
const commit = importer.slice(commitStart, commitEnd);
const intent = commit.indexOf("WriteIntentDurably(intentPath, intentBytes)");
const dataMove = commit.indexOf("File.Move(file.TemporaryPath, file.FinalPath");
const readback = commit.indexOf("VerifyImportedData(snapshot)");
const markerMove = commit.indexOf("File.Move(marker.TemporaryPath, marker.FinalPath");
const intentRemoval = commit.indexOf("DeleteOwnedFile(", markerMove);
assert.ok(intent >= 0 && intent < dataMove && dataMove < readback);
assert.ok(readback < markerMove && markerMove < intentRemoval);
assert.match(importer, /FileOptions\.WriteThrough[\s\S]*Flush\(flushToDisk: true\)/u);
});
test("trusted VI source is injected and checked for both page plan and page load", () => {
assert.match(playout, /trustedViSource: _viManualListStore/u);
assert.match(factory, /ITrustedViStockNameSnapshotSource\? trustedViSource/u);
assert.ok((factory.match(/TrustedViManualReference\.ResolveAsync/gu) || []).length >= 2);
});
test("legacy manual import bridge is closed, fixed under UserProfile, and never accepts a path", () => {
assert.match(dispatch, /case "import-legacy-manual-operator-data"/u);
const methodStart = native.indexOf("private async Task HandleLegacyManualOperatorImportAsync");
const methodEnd = native.indexOf("private async Task HandleManualNetSellReadAsync", methodStart);
const method = native.slice(methodStart, methodEnd);
assert.ok(methodStart >= 0 && methodEnd > methodStart);
assert.match(method, /HasOnlyProperties\(payload, "requestId"\)/u);
assert.equal(/GetProperty\([^)]*(?:path|directory|source)/iu.test(method), false);
assert.match(importer, /Environment\.SpecialFolder\.UserProfile/u);
assert.match(importer, /FixedRelativeDirectory/u);
assert.equal(importer.includes("MBN_STOCK_OPERATOR_DATA_DIRECTORY"), false);
assert.equal(importer.includes("C:\\Users\\MD"), false);
});
test("legacy manual import returns only a versioned receipt and terminal no-retry errors", () => {
assert.match(native, /PostMessage\("legacy-manual-operator-import-result"/u);
assert.match(native, /markerVersion = result\.MarkerVersion/u);
assert.match(native, /sourceSha256 = result\.SourceSha256/u);
assert.match(native, /retryable: false,[\s\S]*outcomeUnknown: false/u);
assert.match(native, /PostManualOperatorQuarantine\(requestId, operation\)/u);
assert.equal(native.includes("result.SourcePath"), false);
});