137 lines
5.6 KiB
JavaScript
137 lines
5.6 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const foreignStock = require("../../Web/named-foreign-stock-restore-workflow.js");
|
|
const legacyRows = require("../../Web/legacy-named-row-workflow.js");
|
|
const namedPlaylists = require("../../Web/named-playlist-workflow.js");
|
|
|
|
const root = path.resolve(__dirname, "../..");
|
|
const app = fs.readFileSync(path.join(root, "Web/app.js"), "utf8");
|
|
const index = fs.readFileSync(path.join(root, "Web/index.html"), "utf8");
|
|
const bridge = fs.readFileSync(
|
|
path.join(root, "MainWindow.NamedForeignStockRestore.cs"), "utf8");
|
|
const namedBridge = fs.readFileSync(path.join(root, "MainWindow.NamedPlaylists.cs"), "utf8");
|
|
const core = fs.readFileSync(
|
|
path.join(root, "src/MBN_STOCK_WEBVIEW.Core/Data/LegacyNamedForeignStockRestore.cs"),
|
|
"utf8");
|
|
|
|
function rawRow(enabled = false) {
|
|
return {
|
|
itemIndex: 0,
|
|
enabled,
|
|
groupCode: "해외종목",
|
|
subject: "NVIDIA",
|
|
graphicType: "캔들그래프",
|
|
subtype: "240일",
|
|
pageText: "1/1",
|
|
dataCode: ""
|
|
};
|
|
}
|
|
|
|
function materializedEntry() {
|
|
const raw = rawRow();
|
|
const pending = foreignStock.classify(raw, {
|
|
id: "db-foreign-stock-240",
|
|
fadeDuration: 6,
|
|
ma5: true,
|
|
ma20: false
|
|
});
|
|
const response = foreignStock.normalizeResponse({
|
|
requestId: "foreign-stock-load",
|
|
retrievedAt: "2026-07-12T12:34:56+09:00",
|
|
totalRowCount: 1,
|
|
rows: [{
|
|
itemIndex: 0,
|
|
status: "resolved",
|
|
actionId: "stock-candle-240d",
|
|
inputName: "NVIDIA",
|
|
symbol: "NAS@NVDA",
|
|
nationCode: "US",
|
|
foreignDomesticCode: "1",
|
|
builderKey: "s8010",
|
|
cutCode: "8056",
|
|
valueType: "PRICE",
|
|
periodDays: 240
|
|
}]
|
|
}, "foreign-stock-load", [pending]);
|
|
return foreignStock.materialize(pending, response.rows[0]);
|
|
}
|
|
|
|
test("named load connects one correlated native foreign-stock restore batch end to end", () => {
|
|
assert.match(index, /<script src="named-foreign-stock-restore-workflow\.js"><\/script>[\s\S]*<script src="legacy-named-row-workflow\.js"><\/script>/u);
|
|
assert.match(app, /MbnNamedForeignStockRestoreWorkflow/u);
|
|
assert.match(app, /namedForeignStockRestoreWorkflow\.classify/u);
|
|
assert.match(app, /namedForeignStockRestoreWorkflow\.normalizeResponse/u);
|
|
assert.match(app, /namedForeignStockRestoreWorkflow\.materialize/u);
|
|
assert.match(app, /case "named-foreign-stock-restore-results"/u);
|
|
assert.match(app, /case "named-foreign-stock-restore-error"/u);
|
|
assert.match(namedBridge, /await PostNamedForeignStockRestoreAsync\(/u);
|
|
assert.match(bridge, /PostMessage\("named-foreign-stock-restore-results"/u);
|
|
assert.match(bridge, /PostMessage\("named-foreign-stock-restore-error"/u);
|
|
});
|
|
|
|
test("Core uses one exact bound SELECT and native bridge never writes or calls playout", () => {
|
|
assert.match(core, /F_INPUT_NAME = :input_name/u);
|
|
assert.match(core, /F_FDTC = '1'/u);
|
|
assert.match(core, /F_NATC IN \('US', 'TW'\)/u);
|
|
assert.match(core, /WHERE ROWNUM <= 2/u);
|
|
assert.match(core, /new DataQueryParameter\("input_name"/u);
|
|
assert.doesNotMatch(core, /\bLIKE\b/iu);
|
|
assert.doesNotMatch(core, /\b(?:INSERT\s+INTO|UPDATE\s+[A-Z_]|DELETE\s+FROM|MERGE\s+INTO)\b/iu);
|
|
assert.doesNotMatch(bridge, /Tornado|TakeIn|PrepareAsync|ExecuteNonQuery/iu);
|
|
assert.match(bridge, /retryable = false/u);
|
|
assert.match(bridge, /자동 재시도하지 않습니다/u);
|
|
});
|
|
|
|
test("pending state blocks edits and timeout is terminal without an automatic retry", () => {
|
|
assert.match(app, /state\.namedPlaylist\.foreignStockRestorePending/u);
|
|
assert.match(app, /armNamedForeignStockRestoreTimeout/u);
|
|
assert.match(app, /setTimeout\(\(\) => \{[\s\S]*자동 재시도하지 않습니다\.[\s\S]*\}, 30000\)/u);
|
|
assert.match(app, /comparisonRestorePending \|\|[\s\S]*foreignStockRestorePending \|\|[\s\S]*foreignIndexCandleRestorePending/u);
|
|
assert.match(app, /pendingForeignStocks\.length === 0/u);
|
|
assert.match(app, /clearNamedForeignStockRestoreTimeout\(record\)/u);
|
|
assert.doesNotMatch(app, /retryNamedForeignStock|requestNamedForeignStockRestore/u);
|
|
});
|
|
|
|
test("verified identity and moving averages preserve the exact original seven fields for save", () => {
|
|
const entry = materializedEntry();
|
|
const raw = rawRow();
|
|
assert.ok(entry);
|
|
assert.equal(legacyRows.matchesCanonicalEntry(entry, raw), true);
|
|
const selection = legacyRows.legacySelectionForEntry(entry);
|
|
assert.deepEqual(selection, {
|
|
groupCode: "해외종목",
|
|
subject: "NVIDIA",
|
|
graphicType: "캔들그래프",
|
|
subtype: "240일",
|
|
dataCode: ""
|
|
});
|
|
assert.deepEqual(entry.operator.movingAverages, { ma5: true, ma20: false });
|
|
assert.deepEqual(namedPlaylists.toLegacySevenFields({ ...entry, selection }, "1/1"), [
|
|
"0", "해외종목", "NVIDIA", "캔들그래프", "240일", "1/1", ""
|
|
]);
|
|
assert.match(app, /해외종목 항목의 원본 7필드 서명을 안전하게 왕복할 수 없습니다/u);
|
|
assert.match(app, /namedForeignStockRestoreWorkflow\.matchesRaw\(restoredForeignStock, rawRow\)/u);
|
|
assert.match(app, /item\.operator\?\.source === namedForeignStockRestoreWorkflow\.source\) return item/u);
|
|
});
|
|
|
|
test("canonical FOREIGN_STOCK remains on the existing workflow and cannot collide", () => {
|
|
assert.equal(foreignStock.classify({
|
|
...rawRow(true),
|
|
groupCode: "FOREIGN_STOCK",
|
|
graphicType: "MA5",
|
|
subtype: "PRICE",
|
|
dataCode: "NAS@NVDA|US|1"
|
|
}, {
|
|
id: "canonical-does-not-route",
|
|
fadeDuration: 6,
|
|
ma5: true,
|
|
ma20: false
|
|
}), null);
|
|
assert.match(app, /addOverseasNamedCandidates/u);
|
|
assert.match(app, /rawRow\.groupCode !== "FOREIGN_STOCK"/u);
|
|
});
|