Files
MBN_STOCK_WEBVIEW/tests/Web/named-nxt-theme-restore-workflow.test.cjs

144 lines
5.5 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const workflow = require("../../Web/named-nxt-theme-restore-workflow.js");
function raw(overrides = {}) {
return {
itemIndex: 4,
enabled: true,
groupCode: "테마_NXT",
subject: "반도체(NXT)",
graphicType: "5단 표그래프",
subtype: "테마-현재가(입력순)",
pageText: "1/9",
dataCode: "0123",
...overrides
};
}
function resolved(overrides = {}) {
return {
itemIndex: 4,
status: "resolved",
actionId: "five-row-current",
builderKey: "s5074",
cutCode: "5074",
pageSize: 5,
sort: "INPUT_ORDER",
session: "PRE_MARKET",
themeTitle: "반도체",
storedThemeCode: "0123",
currentThemeCode: "87654321",
previewItemCount: 7,
liveItemCount: 6,
previewIsTruncated: false,
codeWasRemapped: true,
...overrides
};
}
function response(row) {
return {
requestId: "load-1",
retrievedAt: "2026-07-12T12:59:00+09:00",
totalRowCount: 1,
rows: [row]
};
}
test("classify routes every exact NXT theme group row for native fail-closed verification", () => {
const pending = workflow.classify(raw({
subject: "",
graphicType: "unsupported",
subtype: "",
pageText: "",
dataCode: ""
}), { id: "db-4", fadeDuration: 6 });
assert.equal(workflow.isPending(pending), true);
assert.equal(pending.rawSelection.groupCode, "테마_NXT");
assert.equal(workflow.classify(raw({ groupCode: "테마" }), { id: "db-4", fadeDuration: 6 }), null);
});
test("normalizeResponse accepts one exact correlated current identity", () => {
const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
const result = workflow.normalizeResponse(response(resolved()), "load-1", [pending]);
assert.equal(result.rows[0].currentThemeCode, "87654321");
assert.equal(result.rows[0].liveItemCount, 6);
});
test("normalizeResponse rejects expanded, stale, uncorrelated, or impossible identities", () => {
const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
assert.equal(workflow.normalizeResponse(response({ ...resolved(), extra: true }), "load-1", [pending]), null);
assert.equal(workflow.normalizeResponse(response(resolved({ storedThemeCode: "999" })), "load-1", [pending]), null);
assert.equal(workflow.normalizeResponse(response(resolved({ themeTitle: "다른제목" })), "load-1", [pending]), null);
assert.equal(workflow.normalizeResponse(response(resolved({ liveItemCount: 8 })), "load-1", [pending]), null);
assert.equal(workflow.normalizeResponse(response(resolved({ previewItemCount: 241, previewIsTruncated: false })), "load-1", [pending]), null);
});
test("failed rows preserve a closed no-retry reason", () => {
const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
for (const failure of [
"INVALID_ROW", "UNSUPPORTED_ACTION", "IDENTITY_NOT_FOUND", "IDENTITY_AMBIGUOUS",
"PREVIEW_EMPTY", "LIVE_ITEMS_EMPTY", "DATABASE_DATA_INVALID", "RESTORE_FAILED"
]) {
const result = workflow.normalizeResponse(response({
itemIndex: 4,
status: "failed",
failure
}), "load-1", [pending]);
assert.equal(result.rows[0].failure, failure);
}
assert.equal(workflow.normalizeError({
requestId: "load-1",
code: "DATABASE_UNAVAILABLE",
message: "재검증 실패",
retryable: false
}, "load-1").retryable, false);
});
test("materialize uses only the current code while retaining the old raw identity for lossless save", () => {
const sourceRaw = raw({ enabled: false });
const pending = workflow.classify(sourceRaw, { id: "db-4", fadeDuration: 6 });
const outcome = workflow.normalizeResponse(response(resolved()), "load-1", [pending]).rows[0];
const entry = workflow.materialize(pending, outcome);
assert.equal(workflow.isMaterializedEntry(entry), true);
assert.equal(entry.enabled, false);
assert.equal(entry.selection.groupCode, "PAGED_NXT_THEME");
assert.equal(entry.selection.dataCode, "87654321");
assert.equal(entry.operator.theme.themeCode, "87654321");
assert.equal(entry.pagePreview.itemCount, 6);
assert.deepEqual(workflow.legacySelectionForEntry(entry), {
groupCode: "테마_NXT",
subject: "반도체(NXT)",
graphicType: "5단 표그래프",
subtype: "테마-현재가(입력순)",
dataCode: "0123"
});
assert.equal(workflow.matchesRaw(entry, sourceRaw), true);
});
test("dynamic NXT session changes exactly at local 13:00 without losing native proof", () => {
const sourceRaw = raw();
const pending = workflow.classify(sourceRaw, { id: "db-4", fadeDuration: 6 });
const outcome = workflow.normalizeResponse(response(resolved()), "load-1", [pending]).rows[0];
const entry = workflow.materialize(pending, outcome);
const atTwelve = workflow.refreshDynamicSession(entry, new Date(2026, 6, 12, 12, 59));
const atThirteen = workflow.refreshDynamicSession(atTwelve, new Date(2026, 6, 12, 13, 0));
assert.equal(atTwelve.selection.graphicType, "PRE_MARKET");
assert.equal(atThirteen.selection.graphicType, "AFTER_MARKET");
assert.equal(workflow.isMaterializedEntry(atThirteen), true);
assert.equal(workflow.matchesRaw(atThirteen, sourceRaw), true);
});
test("materialization refuses a failed outcome and non-pending objects", () => {
const pending = workflow.classify(raw(), { id: "db-4", fadeDuration: 6 });
assert.equal(workflow.materialize(pending, {
itemIndex: 4,
status: "failed",
failure: "LIVE_ITEMS_EMPTY"
}), null);
assert.equal(workflow.materialize({}, resolved()), null);
});