test: add core reply-end-controls unit tests / 新增 core state-store-policy-callback 測試
This commit is contained in:
@@ -4,9 +4,11 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "tsx --test tests/**/*.test.mjs",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsx": "^4.20.6",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
tests/core.test.mjs
Normal file
104
tests/core.test.mjs
Normal file
@@ -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)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user