243 lines
10 KiB
JavaScript
243 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const workflow = require("../../Web/theme-workflow.js");
|
|
|
|
const krxTheme = Object.freeze({
|
|
market: "krx",
|
|
themeCode: "00000001",
|
|
title: "AI 반도체",
|
|
displayTitle: "AI 반도체",
|
|
session: null,
|
|
itemCount: 17
|
|
});
|
|
const nxtPreTheme = Object.freeze({
|
|
market: "nxt",
|
|
themeCode: "00000002",
|
|
title: "로봇",
|
|
displayTitle: "로봇(NXT)",
|
|
session: "PRE_MARKET",
|
|
itemCount: 121
|
|
});
|
|
const nxtAfterTheme = Object.freeze({
|
|
...nxtPreTheme,
|
|
session: "AFTER_MARKET"
|
|
});
|
|
|
|
test("theme workflow exposes the UC4 and closed resolver contract", () => {
|
|
assert.deepEqual(workflow.sourceContract, {
|
|
originalControl: "Control/UC4.cs",
|
|
krxResolverGroup: "PAGED_THEME",
|
|
nxtResolverGroup: "PAGED_NXT_THEME",
|
|
maximumPageCount: 20,
|
|
maximumPreviewItems: 240
|
|
});
|
|
assert.deepEqual(workflow.themeSorts, [
|
|
{ id: "INPUT_ORDER", label: "입력순" },
|
|
{ id: "GAIN_DESC", label: "상승률순" },
|
|
{ id: "GAIN_ASC", label: "하락률순" }
|
|
]);
|
|
assert.deepEqual(workflow.nxtSessions.map(value => value.id), ["PRE_MARKET", "AFTER_MARKET"]);
|
|
assert.deepEqual(workflow.actions.map(value => [value.id, value.builderKey, value.code, value.pageSize]), [
|
|
["five-row-current", "s5074", "5074", 5],
|
|
["five-row-expected", "s5074", "5074", 5],
|
|
["six-row-current", "s5077", "5077", 6],
|
|
["twelve-row-current", "s5088", "5088", 12]
|
|
]);
|
|
});
|
|
|
|
test("KRX and NXT database identities normalize without inferring market from a suffix", () => {
|
|
assert.deepEqual(workflow.normalizeTheme({
|
|
Market: "Krx", ThemeCode: "00000001", ThemeTitle: "AI 반도체", Session: "NotApplicable",
|
|
previewItemCount: 17
|
|
}), krxTheme);
|
|
assert.deepEqual(workflow.normalizeTheme({
|
|
market: "NXT", themeCode: "00000002", title: "로봇", session: "PreMarket", items: new Array(121)
|
|
}), nxtPreTheme);
|
|
assert.deepEqual(workflow.normalizeTheme({
|
|
market: "nxt", code: "00000002", themeTitle: "로봇", session: "after-market", itemCount: 121
|
|
}), nxtAfterTheme);
|
|
assert.equal(workflow.normalizeTheme({
|
|
market: "nxt", themeCode: "00000002", title: "로봇(NXT)", session: "PRE_MARKET"
|
|
}), null);
|
|
assert.equal(workflow.normalizeTheme({
|
|
market: "krx", themeCode: "00000001", title: " AI 반도체 "
|
|
}), null);
|
|
});
|
|
|
|
test("typed theme validation reports clear market, code, session and preview failures", () => {
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
market: "other", themeCode: "00000001", title: "AI"
|
|
}, "INPUT_ORDER").reason, "invalid-theme-market");
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
market: "krx", themeCode: "1", title: "AI"
|
|
}, "INPUT_ORDER").reason, "invalid-theme-code");
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
market: "krx", themeCode: " 00000001 ", title: "AI"
|
|
}, "INPUT_ORDER").reason, "invalid-theme-code");
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
market: "nxt", themeCode: "00000001", title: "AI"
|
|
}, "INPUT_ORDER").reason, "nxt-session-required");
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
market: "krx", themeCode: "00000001", title: "AI", session: "PRE_MARKET"
|
|
}, "INPUT_ORDER").reason, "krx-session-must-be-empty");
|
|
assert.equal(workflow.getCompatibility("five-row-current", {
|
|
...krxTheme, itemCount: 241
|
|
}, "INPUT_ORDER").reason, "invalid-theme-item-count");
|
|
assert.equal(workflow.getCompatibility("five-row-current", krxTheme, "RANDOM").reason,
|
|
"invalid-theme-sort");
|
|
});
|
|
|
|
test("only three current row sizes and KRX five-row expected are compatible", () => {
|
|
for (const actionId of ["five-row-current", "six-row-current", "twelve-row-current"]) {
|
|
for (const sort of workflow.themeSorts) {
|
|
assert.equal(workflow.isActionAllowed(actionId, krxTheme, sort.id), true);
|
|
assert.equal(workflow.isActionAllowed(actionId, nxtPreTheme, sort.id), true);
|
|
assert.equal(workflow.isActionAllowed(actionId, nxtAfterTheme, sort.id), true);
|
|
}
|
|
}
|
|
assert.equal(workflow.isActionAllowed("five-row-expected", krxTheme, "INPUT_ORDER"), true);
|
|
assert.equal(workflow.getCompatibility("five-row-expected", nxtPreTheme, "INPUT_ORDER").reason,
|
|
"expected-theme-is-krx-only");
|
|
assert.equal(workflow.getCompatibility("six-row-expected", krxTheme, "INPUT_ORDER").reason,
|
|
"unknown-action");
|
|
assert.equal(workflow.getCompatibility("five-row-current", { ...krxTheme, itemCount: 0 }, "INPUT_ORDER").reason,
|
|
"theme-preview-is-empty");
|
|
});
|
|
|
|
test("KRX current and expected selections map sort and value kind to separate fields", () => {
|
|
const current = workflow.buildThemeSelection("five-row-current", krxTheme, "GAIN_DESC");
|
|
assert.equal(current.builderKey, "s5074");
|
|
assert.equal(current.code, "5074");
|
|
assert.deepEqual(current.aliases, ["5074"]);
|
|
assert.deepEqual(current.selection, {
|
|
groupCode: "PAGED_THEME",
|
|
subject: "AI 반도체",
|
|
graphicType: "GAIN_DESC",
|
|
subtype: "CURRENT",
|
|
dataCode: "00000001"
|
|
});
|
|
|
|
const expected = workflow.buildThemeSelection("five-row-expected", krxTheme, "GAIN_ASC");
|
|
assert.equal(expected.builderKey, "s5074");
|
|
assert.deepEqual(expected.selection, {
|
|
groupCode: "PAGED_THEME",
|
|
subject: "AI 반도체",
|
|
graphicType: "GAIN_ASC",
|
|
subtype: "EXPECTED",
|
|
dataCode: "00000001"
|
|
});
|
|
});
|
|
|
|
test("NXT selections map explicit session to GraphicType and sort to Subtype", () => {
|
|
const five = workflow.buildThemeSelection("five-row-current", nxtPreTheme, "INPUT_ORDER");
|
|
assert.deepEqual(five.selection, {
|
|
groupCode: "PAGED_NXT_THEME",
|
|
subject: "로봇(NXT)",
|
|
graphicType: "PRE_MARKET",
|
|
subtype: "INPUT_ORDER",
|
|
dataCode: "00000002"
|
|
});
|
|
|
|
const six = workflow.buildThemeSelection("six-row-current", nxtAfterTheme, "GAIN_DESC");
|
|
assert.equal(six.builderKey, "s5077");
|
|
assert.equal(six.code, "5077");
|
|
assert.equal(six.selection.graphicType, "AFTER_MARKET");
|
|
assert.equal(six.selection.subtype, "GAIN_DESC");
|
|
|
|
const twelve = workflow.buildThemeSelection("twelve-row-current", nxtAfterTheme, "GAIN_ASC");
|
|
assert.equal(twelve.builderKey, "s5088");
|
|
assert.equal(twelve.code, "5088");
|
|
assert.deepEqual(twelve.aliases, ["5088"]);
|
|
assert.equal(twelve.selection.subtype, "GAIN_ASC");
|
|
});
|
|
|
|
test("item-count previews calculate 5, 6 and 12 row pages within the 20-page boundary", () => {
|
|
assert.deepEqual(workflow.calculatePagePreview("five-row-current", { ...krxTheme, itemCount: 17 }), {
|
|
itemCount: 17, accessibleItemCount: 17, pageSize: 5,
|
|
pageCount: 4, maximumPageCount: 20, isTruncated: false
|
|
});
|
|
assert.deepEqual(workflow.calculatePagePreview("five-row-current", { ...krxTheme, itemCount: 101 }), {
|
|
itemCount: 101, accessibleItemCount: 100, pageSize: 5,
|
|
pageCount: 20, maximumPageCount: 20, isTruncated: true
|
|
});
|
|
assert.deepEqual(workflow.calculatePagePreview("six-row-current", { ...krxTheme, itemCount: 120 }), {
|
|
itemCount: 120, accessibleItemCount: 120, pageSize: 6,
|
|
pageCount: 20, maximumPageCount: 20, isTruncated: false
|
|
});
|
|
assert.deepEqual(workflow.calculatePagePreview("twelve-row-current", { ...krxTheme, itemCount: 240 }), {
|
|
itemCount: 240, accessibleItemCount: 240, pageSize: 12,
|
|
pageCount: 20, maximumPageCount: 20, isTruncated: false
|
|
});
|
|
assert.deepEqual(workflow.calculatePagePreview("five-row-current", {
|
|
market: "krx", themeCode: "00000001", title: "AI 반도체"
|
|
}), {
|
|
itemCount: null, accessibleItemCount: null, pageSize: 5,
|
|
pageCount: null, maximumPageCount: 20, isTruncated: false
|
|
});
|
|
});
|
|
|
|
test("playlist entries preserve the alias, preview and explicit theme identity", () => {
|
|
const entry = workflow.createThemePlaylistEntry("six-row-current", nxtAfterTheme, "GAIN_DESC", {
|
|
id: "nxt-theme", fadeDuration: 4
|
|
});
|
|
assert.equal(entry.builderKey, "s5077");
|
|
assert.equal(entry.code, "5077");
|
|
assert.equal(entry.title, "로봇(NXT)");
|
|
assert.equal(entry.detail, "6종목 현재가 · 상승률순");
|
|
assert.equal(entry.pageSize, 6);
|
|
assert.equal(entry.pagePreview.pageCount, 20);
|
|
assert.equal(entry.pagePreview.isTruncated, true);
|
|
assert.equal(entry.fadeDuration, 4);
|
|
assert.deepEqual(entry.operator.theme, nxtAfterTheme);
|
|
assert.equal(entry.operator.resolverGroup, "PAGED_NXT_THEME");
|
|
});
|
|
|
|
test("trusted theme restore reconstructs fields and rejects action, identity and preview tampering", () => {
|
|
const original = workflow.createThemePlaylistEntry("five-row-expected", krxTheme, "GAIN_ASC", {
|
|
id: "stored-theme", fadeDuration: 8
|
|
});
|
|
assert.ok(workflow.restoreThemePlaylistEntry(original));
|
|
assert.equal(workflow.restoreThemePlaylistEntry({ ...original, enabled: false }).enabled, false);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({ ...original, code: "5088" }), null);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({
|
|
...original,
|
|
selection: { ...original.selection, subtype: "CURRENT" }
|
|
}), null);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({
|
|
...original,
|
|
pagePreview: { ...original.pagePreview, pageCount: 20 }
|
|
}), null);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({
|
|
...original,
|
|
operator: { ...original.operator, schemaVersion: 2 }
|
|
}), null);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({
|
|
...original,
|
|
operator: { ...original.operator, theme: { ...original.operator.theme, themeCode: "00000003" } }
|
|
}), null);
|
|
assert.equal(workflow.restoreThemePlaylistEntry({
|
|
...original,
|
|
operator: { ...original.operator, resolverGroup: "PAGED_NXT_THEME" }
|
|
}), null);
|
|
});
|
|
|
|
test("unknown actions, unsafe ids, invalid fades and unsupported known-empty themes cannot be created", () => {
|
|
assert.throws(() => workflow.createThemePlaylistEntry("unknown", krxTheme, "INPUT_ORDER", {
|
|
id: "unknown"
|
|
}), /unknown/i);
|
|
assert.throws(() => workflow.createThemePlaylistEntry("five-row-current", krxTheme, "INPUT_ORDER", {
|
|
id: "bad id"
|
|
}), /safe theme playlist id/i);
|
|
assert.throws(() => workflow.createThemePlaylistEntry("five-row-current", krxTheme, "INPUT_ORDER", {
|
|
id: "bad-fade", fadeDuration: 61
|
|
}), /0 through 60/i);
|
|
assert.throws(() => workflow.createThemePlaylistEntry(
|
|
"five-row-current", { ...krxTheme, itemCount: 0 }, "INPUT_ORDER", { id: "empty" }),
|
|
/theme-preview-is-empty/i);
|
|
assert.throws(() => workflow.createThemePlaylistEntry(
|
|
"five-row-expected", nxtPreTheme, "INPUT_ORDER", { id: "nxt-expected" }),
|
|
/expected-theme-is-krx-only/i);
|
|
});
|