From 73a6da8d121208d70f67ff20029d51d8c1a714a2 Mon Sep 17 00:00:00 2001 From: "Alice (OpenClaw)" Date: Wed, 13 May 2026 11:46:28 +0800 Subject: [PATCH] =?UTF-8?q?test:=20add=20core=20reply-end-controls=20unit?= =?UTF-8?q?=20tests=20/=20=E6=96=B0=E5=A2=9E=20core=20state-store-policy-c?= =?UTF-8?q?allback=20=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + tests/core.test.mjs | 104 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/core.test.mjs diff --git a/package.json b/package.json index b52dac4..e5f5d99 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,11 @@ "private": true, "type": "module", "scripts": { + "test": "tsx --test tests/**/*.test.mjs", "typecheck": "tsc --noEmit" }, "devDependencies": { + "tsx": "^4.20.6", "typescript": "^5.9.3" } } diff --git a/tests/core.test.mjs b/tests/core.test.mjs new file mode 100644 index 0000000..3624cb4 --- /dev/null +++ b/tests/core.test.mjs @@ -0,0 +1,104 @@ +import test from "node:test" +import assert from "node:assert/strict" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" + +import { buildReplyEndState, clearReplyEndStateForConversation, getReplyEndState, setReplyEndState } from "../src/core/state.ts" +import { evaluateReplyEndPolicy } from "../src/core/policy.ts" +import { parseTelegramCallbackData, normalizeTelegramCallback } from "../src/core/callback-contract.ts" +import { resolveReplyEndStateFile, readReplyEndStateMap, saveReplyEndState, loadReplyEndState, removeReplyEndState } from "../src/core/store.ts" + +test("parseTelegramCallbackData parses continue and stop", () => { + assert.equal(parseTelegramCallbackData("rec:continue"), "continue") + assert.equal(parseTelegramCallbackData("rec:stop"), "stop") + assert.equal(parseTelegramCallbackData("other"), null) +}) + +test("normalizeTelegramCallback returns normalized event", () => { + const event = normalizeTelegramCallback({ + callbackData: "rec:continue", + conversationId: "telegram:1", + sessionKey: "session-1", + sourceMessageId: "100", + sourceCallbackId: "cb-1", + timestamp: "2026-05-13T00:00:00Z", + }) + assert.deepEqual(event, { + choice: "continue", + conversationId: "telegram:1", + sessionKey: "session-1", + sourceMessageId: "100", + sourceCallbackId: "cb-1", + channel: "telegram", + timestamp: "2026-05-13T00:00:00Z", + }) +}) + +test("state helpers build and update reply-end state", () => { + const state = buildReplyEndState({ + choice: "stop", + conversationId: "telegram:1", + sessionKey: null, + sourceMessageId: "101", + sourceCallbackId: "cb-2", + channel: "telegram", + timestamp: "2026-05-13T00:01:00Z", + }) + let stateMap = {} + stateMap = setReplyEndState(stateMap, "telegram:1", state) + assert.equal(getReplyEndState(stateMap, "telegram:1")?.lastChoice, "stop") + stateMap = clearReplyEndStateForConversation(stateMap, "telegram:1") + assert.equal(getReplyEndState(stateMap, "telegram:1"), null) +}) + +test("policy suppresses continuation only for active stop without typed follow-up", () => { + assert.deepEqual(evaluateReplyEndPolicy({ state: null, hasTypedUserFollowup: false }), { + suppressProactiveContinuation: false, + }) + assert.deepEqual( + evaluateReplyEndPolicy({ + state: { + lastChoice: "stop", + lastChoiceAt: "2026-05-13T00:02:00Z", + sourceMessageId: "102", + sourceCallbackId: "cb-3", + active: true, + }, + hasTypedUserFollowup: false, + }), + { suppressProactiveContinuation: true } + ) + assert.deepEqual( + evaluateReplyEndPolicy({ + state: { + lastChoice: "stop", + lastChoiceAt: "2026-05-13T00:02:00Z", + sourceMessageId: "102", + sourceCallbackId: "cb-3", + active: true, + }, + hasTypedUserFollowup: true, + }), + { suppressProactiveContinuation: false } + ) +}) + +test("store helpers persist and load reply-end state", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "reply-end-controls-")) + const filePath = resolveReplyEndStateFile(tempDir) + const state = { + lastChoice: "continue", + lastChoiceAt: "2026-05-13T00:03:00Z", + sourceMessageId: "103", + sourceCallbackId: "cb-4", + active: true, + } + saveReplyEndState(filePath, "telegram:2", state) + const map = readReplyEndStateMap(filePath) + assert.equal(map["telegram:2"].lastChoice, "continue") + const loaded = loadReplyEndState(filePath, "telegram:2") + assert.equal(loaded?.sourceMessageId, "103") + removeReplyEndState(filePath, "telegram:2") + assert.equal(loadReplyEndState(filePath, "telegram:2"), null) +})