211 lines
8.9 KiB
JavaScript
211 lines
8.9 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const workflow = require("../../Web/legacy-fixed-workflow.js");
|
|
|
|
const knownAliases = new Map([
|
|
["s5001", new Set(["5001"])],
|
|
["s5016", new Set(["5016"])],
|
|
["s50160", new Set(["50160"])],
|
|
["s5023", new Set(["5023"])],
|
|
["s5024", new Set(["5024"])],
|
|
["s5025", new Set(["5025"])],
|
|
["s5074", new Set(["5074"])],
|
|
["s5077", new Set(["5077"])],
|
|
["s5078", new Set(["5078"])],
|
|
["s5082", new Set(["5082"])],
|
|
["s5083", new Set(["5083"])],
|
|
["s5084", new Set(["5084"])],
|
|
["s5085", new Set(["5085"])],
|
|
["s5086", new Set(["5086"])],
|
|
["s50860", new Set(["50860"])],
|
|
["s5088", new Set(["5088"])],
|
|
["s6001", new Set(["6001"])],
|
|
["s6067", new Set(["6067"])],
|
|
["s8010", new Set(["8035", "8061", "8040", "8046", "8051", "8056"])],
|
|
["s8067", new Set(["8067", "5068", "5070", "5072"])]
|
|
]);
|
|
|
|
function action(market, section, label) {
|
|
const result = workflow.actions.find(value =>
|
|
value.market === market && value.section === section && value.label === label);
|
|
assert.ok(result, `${market}/${section}/${label} action is missing`);
|
|
return result;
|
|
}
|
|
|
|
test("runtime overseas, exchange and index assets expose all 328 leaf actions", () => {
|
|
assert.equal(workflow.actions.length, 328);
|
|
assert.equal(workflow.actionsForMarket("overseas").length, 78);
|
|
assert.equal(workflow.actionsForMarket("exchange").length, 46);
|
|
assert.equal(workflow.actionsForMarket("index").length, 204);
|
|
assert.equal(workflow.actions.filter(value => value.available).length, 322);
|
|
assert.equal(workflow.actions.filter(value => !value.available).length, 6);
|
|
assert.equal(new Set(workflow.actions.map(value => value.id)).size, 328);
|
|
assert.equal(workflow.runtimeAssets.overseas.sha256, "DBDC4EAC28BED9FD0DEABED7FE880F21CB49E48503FD3CCB95C0A29128818B2A");
|
|
assert.equal(workflow.runtimeAssets.exchange.sha256, "7A99F8DFB5DD8D8DB514605F232A0A7973545CA6A4A46ABE2D102ADD025B586C");
|
|
assert.equal(workflow.runtimeAssets.index.sha256, "E90ED317FD3ECC1A716AD17DD9BC21FE8B838574E1C20854BD0A886A96BA17E6");
|
|
});
|
|
|
|
test("every available fixed action creates a closed registered builder and alias", () => {
|
|
for (const value of workflow.actions) {
|
|
if (!value.available) {
|
|
assert.throws(() => workflow.createFixedPlaylistEntry(value.id, { id: `entry-${value.id}` }));
|
|
continue;
|
|
}
|
|
const entry = workflow.createFixedPlaylistEntry(value.id, {
|
|
id: `entry-${value.id}`,
|
|
now: new Date(2026, 6, 11, 9, 30, 0)
|
|
});
|
|
assert.equal(entry.operator.source, "legacy-fixed-workflow");
|
|
assert.equal(entry.operator.actionId, value.id);
|
|
assert.ok(knownAliases.get(entry.builderKey)?.has(entry.code), `${entry.builderKey}/${entry.code}`);
|
|
assert.deepEqual(entry.aliases, [entry.code]);
|
|
assert.equal(typeof entry.selection.groupCode, "string");
|
|
assert.equal(typeof entry.selection.subject, "string");
|
|
assert.equal(typeof entry.selection.graphicType, "string");
|
|
assert.equal(typeof entry.selection.subtype, "string");
|
|
assert.equal(typeof entry.selection.dataCode, "string");
|
|
}
|
|
});
|
|
|
|
test("representative overseas and exchange actions preserve closed native selectors", () => {
|
|
const gold = workflow.createFixedPlaylistEntry(
|
|
action("overseas", "1열판기본", "국제 금").id,
|
|
{ id: "gold" });
|
|
assert.equal(gold.builderKey, "s5001");
|
|
assert.deepEqual(gold.selection, {
|
|
groupCode: "COMMODITY", subject: "InternationalGold",
|
|
graphicType: "1열판기본", subtype: "CURRENT", dataCode: ""
|
|
});
|
|
|
|
const fx = workflow.createFixedPlaylistEntry(
|
|
action("exchange", "라인 그래프", "원달러_20일").id,
|
|
{ id: "fx" });
|
|
assert.equal(fx.builderKey, "s50860");
|
|
assert.equal(fx.selection.subject, "WonDollar");
|
|
assert.equal(fx.selection.subtype, "TwentyDays");
|
|
|
|
const map = workflow.createFixedPlaylistEntry(
|
|
action("overseas", "글로벌 증시 지도", "미국 증시 지도").id,
|
|
{ id: "map" });
|
|
assert.equal(map.builderKey, "s8067");
|
|
assert.equal(map.code, "5068");
|
|
});
|
|
|
|
test("index variants map current, candle, trend and paged rows without free text", () => {
|
|
const current = workflow.createFixedPlaylistEntry(
|
|
action("index", "코스피200", "1열판기본_현재지수").id,
|
|
{ id: "current" });
|
|
assert.deepEqual(current.selection, {
|
|
groupCode: "KOSPI200", subject: "INDEX",
|
|
graphicType: "1열판기본", subtype: "CURRENT", dataCode: ""
|
|
});
|
|
|
|
const candle = workflow.createFixedPlaylistEntry(
|
|
action("index", "선물", "캔들그래프(예상지수)_240일").id,
|
|
{ id: "candle" });
|
|
assert.equal(candle.builderKey, "s8010");
|
|
assert.equal(candle.code, "8056");
|
|
assert.equal(candle.selection.groupCode, "FUTURES_INDEX");
|
|
assert.equal(candle.selection.subtype, "EXPECTED");
|
|
|
|
const trend = workflow.createFixedPlaylistEntry(
|
|
action("index", "매매동향", "외국인 매매동향_라인그래프").id,
|
|
{ id: "trend" });
|
|
assert.equal(trend.selection.graphicType, "FOREIGN_TRADING_TREND");
|
|
|
|
const page = workflow.createFixedPlaylistEntry(
|
|
action("index", "12종목 현재가", "코스닥 52주 신저가").id,
|
|
{ id: "page", now: new Date(2026, 6, 11, 13, 0, 0) });
|
|
assert.equal(page.builderKey, "s5088");
|
|
assert.equal(page.selection.groupCode, "PAGED_DOMESTIC_KOSDAQ");
|
|
assert.equal(page.selection.subtype, "52_WEEK_LOW");
|
|
assert.equal(page.selection.dataCode, "2026-07-11");
|
|
});
|
|
|
|
test("fixed candle actions carry the original global 5-day and 20-day flags", () => {
|
|
const candleAction = workflow.actions.find(value => value.builderKey === "s8010");
|
|
assert.ok(candleAction);
|
|
const both = workflow.createFixedPlaylistEntry(candleAction.id, {
|
|
id: "candle-both",
|
|
ma5: true,
|
|
ma20: true
|
|
});
|
|
assert.equal(both.selection.graphicType, "MA5,MA20");
|
|
assert.deepEqual(both.operator.movingAverages, { ma5: true, ma20: true });
|
|
assert.deepEqual(workflow.restoreFixedPlaylistEntry(both), both);
|
|
|
|
const ma20 = workflow.createFixedPlaylistEntry(candleAction.id, {
|
|
id: "candle-ma20",
|
|
ma5: false,
|
|
ma20: true
|
|
});
|
|
assert.equal(ma20.selection.graphicType, "MA20");
|
|
assert.deepEqual(workflow.restoreFixedPlaylistEntry(ma20), ma20);
|
|
assert.equal(workflow.restoreFixedPlaylistEntry({
|
|
...both,
|
|
selection: { ...both.selection, graphicType: "" }
|
|
}), null);
|
|
});
|
|
|
|
test("pre-global-option fixed candle entries restore once through a closed flag migration", () => {
|
|
const candleAction = workflow.actions.find(value => value.builderKey === "s8010");
|
|
const legacy = workflow.createFixedPlaylistEntry(candleAction.id, { id: "legacy-candle" });
|
|
const withoutMetadata = {
|
|
...legacy,
|
|
operator: Object.freeze(Object.fromEntries(
|
|
Object.entries(legacy.operator).filter(([key]) => key !== "movingAverages")))
|
|
};
|
|
const restored = workflow.restoreFixedPlaylistEntry(withoutMetadata);
|
|
assert.deepEqual(restored.operator.movingAverages, { ma5: false, ma20: false });
|
|
});
|
|
|
|
test("NXT session and domestic date refresh at restore instead of remaining stale", () => {
|
|
const nxtAction = action("index", "5단 표그래프", "코스피_NXT 거래량 상위");
|
|
const original = workflow.createFixedPlaylistEntry(nxtAction.id, {
|
|
id: "nxt-page", now: new Date(2026, 6, 11, 9, 0, 0)
|
|
});
|
|
assert.equal(original.selection.graphicType, "PRE_MARKET");
|
|
const refreshed = workflow.restoreFixedPlaylistEntry(original, {
|
|
now: new Date(2026, 6, 11, 15, 0, 0)
|
|
});
|
|
assert.equal(refreshed.selection.graphicType, "AFTER_MARKET");
|
|
|
|
const domesticAction = action("index", "6종목 현재가", "코스피 시가총액");
|
|
const domestic = workflow.createFixedPlaylistEntry(domesticAction.id, {
|
|
id: "domestic-page", now: new Date(2026, 6, 10, 23, 50, 0)
|
|
});
|
|
const nextDay = workflow.restoreFixedPlaylistEntry(domestic, {
|
|
now: new Date(2026, 6, 11, 0, 10, 0)
|
|
});
|
|
assert.equal(nextDay.selection.dataCode, "2026-07-11");
|
|
});
|
|
|
|
test("stored fixed entries fail closed after action, mapping, asset or selection tampering", () => {
|
|
const value = workflow.createFixedPlaylistEntry(
|
|
action("exchange", "1열판기본", "원엔").id,
|
|
{ id: "stored" });
|
|
assert.ok(workflow.restoreFixedPlaylistEntry(value));
|
|
assert.equal(workflow.restoreFixedPlaylistEntry({ ...value, code: "5006" }), null);
|
|
assert.equal(workflow.restoreFixedPlaylistEntry({
|
|
...value,
|
|
selection: { ...value.selection, subject: "WonDollar" }
|
|
}), null);
|
|
assert.equal(workflow.restoreFixedPlaylistEntry({
|
|
...value,
|
|
operator: { ...value.operator, assetSha256: "0".repeat(64) }
|
|
}), null);
|
|
assert.equal(workflow.restoreFixedPlaylistEntry({
|
|
...value,
|
|
operator: { ...value.operator, actionId: "fixed-999" }
|
|
}), null);
|
|
});
|
|
|
|
test("all three VI placeholders preserve the original VILIST five-row outcome", () => {
|
|
const vi = workflow.actions.filter(value => value.label === "VI 발동(수동)");
|
|
assert.equal(vi.length, 3);
|
|
assert.ok(vi.every(value => !value.available));
|
|
assert.ok(vi.every(value => value.builderKey === "s5074" && value.code === "5074"));
|
|
});
|