const test = require("node:test"); const assert = require("node:assert/strict"); const workflow = require("../../Web/manual-financial-workflow.js"); const version = "A".repeat(64); function quarters() { return [ { quarter: "1Q", value: 10 }, { quarter: "2Q", value: -20 }, { quarter: "3Q", value: 30 }, { quarter: "4Q", value: 40 }, { quarter: "5Q", value: 50 }, { quarter: "6Q", value: 60 } ]; } function record(screen, stockName = "Alpha") { switch (screen) { case "revenue-composition": return { kind: screen, stockName, baseDate: "2026-06", slices: [ { label: "Semiconductor", percentageText: "50.0", percentage: 50 }, { label: "Service", percentageText: "-", percentage: 0 }, { label: "Overseas", percentageText: "25", percentage: 25 }, null, null ] }; case "growth-metrics": return { kind: screen, stockName, salesGrowth: [1, 2, 3, 4], operatingProfitGrowth: [5, 6, null, 8], netAssetGrowth: [9, 10, 11, 12], netIncomeGrowth: [13, 14, 15, 16], periods: ["1Q", "2Q", "3Q", "4Q"] }; case "sales": case "operating-profit": return { kind: screen, stockName, quarters: quarters() }; default: throw new Error("bad screen"); } } function listRequest(screen = "sales") { return workflow.createListRequest("list-1", screen, "Alpha", 25); } function listPayload(screen = "sales", records = [record(screen)]) { return { requestId: "list-1", screen, query: "Alpha", retrievedAt: "2026-07-12T00:10:00+09:00", totalRowCount: records.length, truncated: false, items: records.map((item, index) => ({ stockName: item.stockName, rowVersion: String.fromCharCode(65 + index).repeat(64), record: item })) }; } function trustedSnapshot(screen = "sales", stockName = "Alpha") { const request = workflow.createListRequest("list-1", screen, "Alpha", 25); const response = workflow.normalizeListResponse( listPayload(screen, [record(screen, stockName)]), request); assert.ok(response); return response.items[0]; } function stockPayload(results = [{ market: "kospi", source: "oracle", name: "Alpha", code: "000001" }]) { return { requestId: "stock-1", query: "Alpha", retrievedAt: "2026-07-12T00:11:00+09:00", totalRowCount: results.length, truncated: false, results }; } function verifiedStock(recordValue = record("sales"), results) { const response = workflow.normalizeStockSearchResponse(stockPayload(results)); assert.ok(response); const verified = workflow.verifyStockForRecord( recordValue, response, "kospi", "000001"); assert.ok(verified); return verified; } function trustedSelection(snapshot, stock, requestId = "selection-1") { const request = workflow.createSelectionRequest(requestId, snapshot, stock); const profile = workflow.getScreenDefinition(snapshot.screen); const groupCode = { kospi: "KOSPI", kosdaq: "KOSDAQ", "nxt-kospi": "NXT_KOSPI", "nxt-kosdaq": "NXT_KOSDAQ" }[stock.market]; const response = workflow.normalizeSelectionResponse({ requestId, screen: snapshot.screen, stockName: snapshot.stockName, rowVersion: snapshot.rowVersion, builderKey: profile.builderKey, code: profile.code, aliases: [profile.code], selection: { groupCode, subject: snapshot.stockName, graphicType: profile.graphicType, subtype: "", dataCode: "" }, currentPage: 1, totalPages: 1 }, request); assert.ok(response); return response; } function trustedLoad(snapshot, requestId = "load-1") { const request = workflow.createLoadRequest(requestId, snapshot.screen, snapshot.stockName); const response = workflow.normalizeLoadResponse({ requestId, screen: snapshot.screen, retrievedAt: "2026-07-12T00:20:00+09:00", snapshot: { stockName: snapshot.stockName, rowVersion: snapshot.rowVersion, record: snapshot.record } }, request); assert.ok(response); return response; } test("workflow pins GraphE Oracle tables, CRUD bridge and four scene actions", () => { assert.deepEqual(workflow.bridgeContract, { requestErrorType: "manual-financial-request-error", listRequestType: "request-manual-financial-list", listResultType: "manual-financial-list-results", listErrorType: "manual-financial-list-error", loadRequestType: "request-manual-financial-load", loadResultType: "manual-financial-load-results", loadErrorType: "manual-financial-load-error", selectionRequestType: "request-manual-financial-selection", selectionResultType: "manual-financial-selection-results", selectionErrorType: "manual-financial-selection-error", createRequestType: "create-manual-financial-record", updateRequestType: "update-manual-financial-record", deleteRequestType: "delete-manual-financial-record", deleteAllRequestType: "delete-all-manual-financial-records", mutationResultType: "manual-financial-mutation-result", mutationErrorType: "manual-financial-mutation-error", defaultMaximumResults: 200, maximumResults: 1000, maximumQueryLength: 64 }); assert.equal(workflow.sourceContract.originalForm, "Form/GraphE.cs"); assert.equal(workflow.sourceContract.provider, "oracle"); assert.equal(workflow.sourceContract.operatorSchemaVersion, 1); assert.equal(workflow.sourceContract.restoreRequiresNativeLoad, true); assert.equal(workflow.sourceContract.rawNamedPlaylistRowPlayable, false); assert.equal(workflow.sourceContract.writeSafety.outcomeUnknownAutomaticRetry, false); assert.deepEqual(workflow.actions.map(action => [action.screen, action.builderKey, action.code]), [ ["revenue-composition", "s5076", "5076"], ["growth-metrics", "s5079", "5079"], ["sales", "s5080", "5080"], ["operating-profit", "s5081", "5081"] ]); }); test("generic native request errors are exact and operation-correlated", () => { const error = { requestId: "", operation: "load", code: "INVALID_REQUEST", message: "Invalid screen." }; assert.deepEqual(workflow.normalizeRequestError(error, "load"), error); assert.equal(workflow.normalizeRequestError(error, "list"), null); assert.equal(workflow.normalizeRequestError({ ...error, extra: true }, "load"), null); assert.equal(workflow.normalizeRequestError({ ...error, requestId: "bad id" }, "load"), null); assert.equal(workflow.normalizeRequestError({ ...error, operation: "unknown" }), null); assert.equal(workflow.normalizeRequestError({ ...error, code: "OTHER" }), null); assert.equal(workflow.normalizeRequestError({ ...error, message: "bad\nmessage" }), null); }); test("all closed records serialize exactly to the existing underscore loader shapes", () => { assert.deepEqual(workflow.serializeRecordForStorage( "revenue-composition", record("revenue-composition")), { table: "INPUT_PIE", columns: ["STOCK_NAME", "BASE_DATE", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "GUSUNG_5"], values: ["Alpha", "2026-06", "Semiconductor_50.0", "Service_-", "Overseas_25", "", ""] }); assert.deepEqual(workflow.serializeRecordForStorage( "growth-metrics", record("growth-metrics")), { table: "INPUT_GROW", columns: ["STOCK_NAME", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "BASE_DATE"], values: ["Alpha", "1_2_3_4", "5_6__8", "9_10_11_12", "13_14_15_16", "1Q_2Q_3Q_4Q"] }); assert.deepEqual(workflow.serializeRecordForStorage("sales", record("sales")).values, [ "Alpha", "1Q_10", "2Q_-20", "3Q_30", "4Q_40", "5Q_50", "6Q_60" ]); assert.equal(workflow.serializeRecordForStorage( "operating-profit", record("operating-profit")).table, "INPUT_PROFIT"); }); test("closed DTO validation rejects malformed counts, separators and unsafe numbers", () => { const revenue = record("revenue-composition"); assert.equal(workflow.normalizeRecord("revenue-composition", { ...revenue, slices: revenue.slices.slice(0, 4) }), null); assert.equal(workflow.normalizeRecord("revenue-composition", { ...revenue, slices: [{ label: "bad_label", percentageText: "50", percentage: 50 }, ...revenue.slices.slice(1)] }), null); assert.equal(workflow.normalizeRecord("revenue-composition", { ...revenue, slices: [{ label: "A", percentageText: "50", percentage: 49 }, ...revenue.slices.slice(1)] }), null); assert.equal(workflow.normalizeRecord("growth-metrics", { ...record("growth-metrics"), salesGrowth: [1, 2, Number.POSITIVE_INFINITY, 4] }), null); assert.equal(workflow.normalizeRecord("sales", { ...record("sales"), quarters: quarters().slice(1) }), null); assert.equal(workflow.normalizeRecord("sales", { ...record("sales"), stockName: "bad\nname" }), null); }); test("list requests normalize blank/search text and enforce exact screen bounds", () => { assert.deepEqual(workflow.createListRequest("list-1", "sales", " "), { requestId: "list-1", screen: "sales", query: "" }); assert.deepEqual(listRequest(), { requestId: "list-1", screen: "sales", query: "Alpha", maximumResults: 25 }); assert.throws(() => workflow.createListRequest("bad id", "sales"), /request id/); assert.throws(() => workflow.createListRequest("list", "unknown"), /screen/); assert.throws(() => workflow.createListRequest("list", "sales", "", 1001), /limited/); }); test("native list responses create trusted snapshots and reject duplicate or reordered names", () => { const request = workflow.createListRequest("list-1", "sales", "Alpha", 25); const payload = listPayload("sales", [record("sales", "Alpha"), record("sales", "Beta")]); const normalized = workflow.normalizeListResponse(payload, request); assert.ok(normalized); assert.equal(normalized.items[0].rowVersion, version); assert.ok(Object.isFrozen(normalized.items)); assert.equal(workflow.normalizeListResponse({ ...payload, items: [...payload.items].reverse() }, request), null); assert.equal(workflow.normalizeListResponse(listPayload("sales", [ record("sales", "Alpha"), record("sales", "ALPHA") ]), request), null); assert.equal(workflow.normalizeListResponse({ ...payload, requestId: "stale" }, request), null); assert.equal(workflow.normalizeListResponse({ ...payload, items: [{ ...payload.items[0], rowVersion: "bad" }, payload.items[1]] }, request), null); }); test("list errors are exact and correlated", () => { const request = listRequest(); assert.deepEqual(workflow.normalizeListError({ requestId: "list-1", screen: "sales", query: "Alpha", message: "Database unavailable" }, request), { requestId: "list-1", screen: "sales", query: "Alpha", message: "Database unavailable" }); assert.equal(workflow.normalizeListError({ requestId: "other", screen: "sales", query: "Alpha", message: "Database unavailable" }, request), null); }); test("load responses are exact native refreshes and never trust a stale request", () => { const snapshot = trustedSnapshot("sales"); const request = workflow.createLoadRequest("load-1", "sales", "Alpha"); assert.deepEqual(request, { requestId: "load-1", screen: "sales", stockName: "Alpha" }); const loaded = trustedLoad(snapshot); assert.deepEqual(loaded.snapshot.record, snapshot.record); assert.equal(workflow.normalizeLoadResponse({ requestId: "load-1", screen: "sales", retrievedAt: "2026-07-12T00:20:00+09:00", snapshot: { stockName: "Beta", rowVersion: snapshot.rowVersion, record: record("sales", "Beta") } }, request), null); assert.deepEqual(workflow.normalizeLoadError({ requestId: "load-1", screen: "sales", stockName: "Alpha", code: "NOT_FOUND", message: "Missing", retryable: false }, request).code, "NOT_FOUND"); }); test("stock master normalization enforces provider identity and rejects duplicate rows", () => { const normalized = workflow.normalizeStockSearchResponse(stockPayload()); assert.ok(normalized); assert.equal(normalized.results[0].source, "oracle"); assert.equal(workflow.normalizeStockSearchResponse(stockPayload([{ market: "kospi", source: "mariaDb", name: "Alpha", code: "000001" }])), null); assert.equal(workflow.normalizeStockSearchResponse(stockPayload([ { market: "kospi", source: "oracle", name: "Alpha", code: "000001" }, { market: "kospi", source: "oracle", name: "Alpha", code: "000001" } ])), null); }); test("verification rejects same-name market ambiguity and fabricated responses", () => { const value = record("sales"); assert.equal(workflow.verifyStockForRecord(value, stockPayload(), "kospi", "000001"), null); const response = workflow.normalizeStockSearchResponse(stockPayload([ { market: "kospi", source: "oracle", name: "Alpha", code: "000001" }, { market: "kosdaq", source: "oracle", name: "Alpha", code: "000002" } ])); assert.ok(response); assert.equal(workflow.verifyStockForRecord(value, response, "kospi", "000001"), null); const one = workflow.normalizeStockSearchResponse(stockPayload()); assert.equal(workflow.verifyStockForRecord(value, one, "kosdaq", "000001"), null); }); test("selection requests bind snapshot version and freshly verified stock to native result", () => { const snapshot = trustedSnapshot("sales"); const stock = verifiedStock(snapshot.record); const request = workflow.createSelectionRequest("selection-1", snapshot, stock); assert.deepEqual(request, { requestId: "selection-1", screen: "sales", stockName: "Alpha", rowVersion: version, stock: { market: "kospi", source: "oracle", name: "Alpha", code: "000001" } }); const response = trustedSelection(snapshot, stock); assert.equal(response.builderKey, "s5080"); assert.equal(workflow.normalizeSelectionResponse({ ...response, rowVersion: "B".repeat(64) }, request), null); assert.deepEqual(workflow.normalizeSelectionError({ requestId: "selection-1", screen: "sales", stockName: "Alpha", code: "STALE_ROW", message: "Reload", retryable: false }, request).code, "STALE_ROW"); }); test("create requires a verified exact stock and sends no row version", () => { const value = record("sales"); const stock = verifiedStock(value); assert.deepEqual(workflow.createCreateRequest("create-1", value, stock), { requestId: "create-1", screen: "sales", stock: { market: "kospi", source: "oracle", name: "Alpha", code: "000001" }, record: value }); assert.throws(() => workflow.createCreateRequest("create-1", value, { market: "kospi", source: "oracle", name: "Alpha", code: "000001" }), /verified stock/); assert.throws(() => workflow.createCreateRequest( "create-1", record("sales", "Beta"), stock), /verified stock/); }); test("update and single delete require trusted snapshots and optimistic row version", () => { const snapshot = trustedSnapshot("sales"); const replacement = record("sales"); assert.deepEqual(workflow.createUpdateRequest("update-1", snapshot, replacement), { requestId: "update-1", screen: "sales", stockName: "Alpha", expectedRowVersion: version, record: replacement }); assert.deepEqual(workflow.createDeleteRequest("delete-1", snapshot), { requestId: "delete-1", screen: "sales", stockName: "Alpha", expectedRowVersion: version }); assert.throws(() => workflow.createUpdateRequest( "update-1", snapshot, record("sales", "Beta")), /cannot rename/); assert.throws(() => workflow.createDeleteRequest("delete-1", { ...snapshot }), /native-normalized/); }); test("delete-all requires the exact table-specific confirmation token", () => { assert.deepEqual(workflow.createDeleteAllRequest( "delete-all-1", "sales", "DELETE_ALL_INPUT_SELL"), { requestId: "delete-all-1", screen: "sales", confirmationToken: "DELETE_ALL_INPUT_SELL" }); assert.throws(() => workflow.createDeleteAllRequest( "delete-all-1", "sales", "DELETE_ALL_INPUT_PROFIT"), /confirmation token/); }); test("mutation results correlate and errors can never request automatic retry", () => { const snapshot = trustedSnapshot("sales"); const request = workflow.createDeleteRequest("delete-1", snapshot); assert.deepEqual(workflow.normalizeMutationResult({ requestId: "delete-1", operationId: "operation-1", operation: "delete-one", screen: "sales", stockName: "Alpha", committedAt: "2026-07-12T00:12:00+09:00", affectedRows: 1 }, request), { requestId: "delete-1", operationId: "operation-1", operation: "delete-one", screen: "sales", stockName: "Alpha", committedAt: "2026-07-12T00:12:00+09:00", affectedRows: 1 }); assert.deepEqual(workflow.normalizeMutationError({ requestId: "delete-1", operation: "delete-one", screen: "sales", stockName: "Alpha", code: "OUTCOME_UNKNOWN", message: "Do not retry automatically", retryable: false, outcomeUnknown: true }, request).outcomeUnknown, true); assert.equal(workflow.normalizeMutationResult({ requestId: "delete-1", operationId: "operation-1", operation: "update", screen: "sales", stockName: "Alpha", committedAt: "2026-07-12T00:12:00+09:00", affectedRows: 1 }, request), null); assert.equal(workflow.normalizeMutationError({ requestId: "delete-1", operation: "delete-one", screen: "sales", stockName: "Alpha", code: "OUTCOME_UNKNOWN", message: "retry", retryable: true, outcomeUnknown: true }, request), null); }); test("only trusted DB snapshots and verified master rows create the four exact one-page cuts", () => { const expectations = [ ["revenue-composition", "s5076", "5076", "REVENUE_COMPOSITION"], ["growth-metrics", "s5079", "5079", "GROWTH_METRICS"], ["sales", "s5080", "5080", "SALES"], ["operating-profit", "s5081", "5081", "OPERATING_PROFIT"] ]; for (const [screen, builderKey, code, graphicType] of expectations) { const snapshot = trustedSnapshot(screen); const stock = verifiedStock(snapshot.record); assert.deepEqual(workflow.buildSelection(snapshot, stock), { builderKey, code, aliases: [code], selection: { groupCode: "KOSPI", subject: "Alpha", graphicType, subtype: "", dataCode: "" }, currentPage: 1, pageCount: 1 }); } const rawSnapshot = listPayload("sales").items[0]; const stock = verifiedStock(record("sales")); assert.equal(workflow.buildSelection(rawSnapshot, stock), null); }); test("playlist entry has the complete standard operator metadata and native trust", () => { const snapshot = trustedSnapshot("operating-profit"); const stock = verifiedStock(snapshot.record); const selection = trustedSelection(snapshot, stock); const entry = workflow.createPlaylistEntry(snapshot, stock, selection, { id: "profit-1", fadeDuration: 8 }); assert.deepEqual(entry, { id: "profit-1", builderKey: "s5081", code: "5081", aliases: ["5081"], title: "Alpha", detail: "영업이익", category: "manual-financial", market: "kospi", stockName: "Alpha", stockCode: "000001", isNxt: false, selection: { groupCode: "KOSPI", subject: "Alpha", graphicType: "OPERATING_PROFIT", subtype: "", dataCode: "" }, enabled: true, fadeDuration: 8, graphicType: "OPERATING_PROFIT", subtype: "", currentPage: 1, pageCount: 1, operator: { source: "manual-financial-workflow", schemaVersion: 1, screen: "operating-profit", snapshot: { screen: "operating-profit", stockName: "Alpha", rowVersion: version, record: snapshot.record }, verifiedStock: { market: "kospi", source: "oracle", name: "Alpha", code: "000001" } } }); assert.equal(workflow.isPlaylistEntryPlayable(entry), true); assert.equal(workflow.isPlaylistEntryPlayable(JSON.parse(JSON.stringify(entry))), false); assert.throws(() => workflow.createPlaylistEntry(snapshot, stock, { ...selection }, { id: "forged" }), /native-confirmed/); }); test("stored entries restore only as non-playable refresh-required descriptors", () => { const snapshot = trustedSnapshot("sales"); const stock = verifiedStock(snapshot.record); const selection = trustedSelection(snapshot, stock); const live = workflow.createPlaylistEntry(snapshot, stock, selection, { id: "sales-restore", fadeDuration: 7, enabled: false }); const stored = JSON.parse(JSON.stringify(live)); const pending = workflow.restorePlaylistEntry(stored); assert.ok(pending); assert.equal(pending.status, "refresh-required"); assert.equal(pending.playable, false); assert.equal(workflow.isPlaylistEntryPlayable(pending.entry), false); assert.deepEqual(workflow.createRestoreLoadRequest("restore-load", pending), { requestId: "restore-load", screen: "sales", stockName: "Alpha" }); assert.equal(workflow.restorePlaylistEntry({ ...stored, selection: { ...stored.selection, dataCode: "tampered" } }), null); assert.equal(workflow.restorePlaylistEntry({ ...stored, operator: { ...stored.operator, schemaVersion: 2 } }), null); }); test("native load, fresh stock verification and native selection materialize a stored entry", () => { const originalSnapshot = trustedSnapshot("sales"); const originalStock = verifiedStock(originalSnapshot.record); const originalSelection = trustedSelection(originalSnapshot, originalStock); const stored = JSON.parse(JSON.stringify(workflow.createPlaylistEntry( originalSnapshot, originalStock, originalSelection, { id: "sales-restore", fadeDuration: 7, enabled: false }))); const pending = workflow.restorePlaylistEntry(stored); const loadRequest = workflow.createRestoreLoadRequest("restore-load", pending); const loadResponse = workflow.normalizeLoadResponse({ requestId: "restore-load", screen: "sales", retrievedAt: "2026-07-12T00:20:00+09:00", snapshot: { stockName: stored.operator.snapshot.stockName, rowVersion: stored.operator.snapshot.rowVersion, record: stored.operator.snapshot.record } }, loadRequest); assert.ok(loadResponse); const freshStockResponse = workflow.normalizeStockSearchResponse(stockPayload()); const freshStock = workflow.verifyStockForRecord( loadResponse.snapshot.record, freshStockResponse, pending.verifiedStock.market, pending.verifiedStock.code); assert.ok(freshStock); const selectionRequest = workflow.createRestoreSelectionRequest( "restore-selection", pending, loadResponse, freshStock); const profile = workflow.getScreenDefinition("sales"); const selectionResponse = workflow.normalizeSelectionResponse({ requestId: "restore-selection", screen: "sales", stockName: "Alpha", rowVersion: version, builderKey: profile.builderKey, code: profile.code, aliases: [profile.code], selection: { groupCode: "KOSPI", subject: "Alpha", graphicType: profile.graphicType, subtype: "", dataCode: "" }, currentPage: 1, totalPages: 1 }, selectionRequest); const restored = workflow.materializeRestoredPlaylistEntry( pending, loadResponse, freshStock, selectionResponse); assert.deepEqual(restored, stored); assert.equal(workflow.isPlaylistEntryPlayable(restored), true); }); test("row-version or record drift keeps a stored entry permanently unplayable", () => { const snapshot = trustedSnapshot("sales"); const stock = verifiedStock(snapshot.record); const selection = trustedSelection(snapshot, stock); const stored = JSON.parse(JSON.stringify(workflow.createPlaylistEntry( snapshot, stock, selection, { id: "stale-entry" }))); const pending = workflow.restorePlaylistEntry(stored); const request = workflow.createRestoreLoadRequest("restore-load", pending); const changed = workflow.normalizeLoadResponse({ requestId: "restore-load", screen: "sales", retrievedAt: "2026-07-12T00:20:00+09:00", snapshot: { stockName: "Alpha", rowVersion: "B".repeat(64), record: record("sales") } }, request); const freshStockResponse = workflow.normalizeStockSearchResponse(stockPayload()); const freshStock = workflow.verifyStockForRecord( changed.snapshot.record, freshStockResponse, "kospi", "000001"); assert.throws(() => workflow.createRestoreSelectionRequest( "restore-selection", pending, changed, freshStock), /did not reproduce/); assert.equal(workflow.materializeRestoredPlaylistEntry( pending, changed, freshStock, selection), null); assert.equal(workflow.isPlaylistEntryPlayable(stored), false); });