"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")); });