Files
WechatExplorer/scripts/test-knowledge-worker.cjs
T
2026-08-06 20:29:25 +08:00

154 lines
4.4 KiB
JavaScript

const { fork } = require('node:child_process')
const { existsSync, mkdtempSync } = require('node:fs')
const { rm } = require('node:fs/promises')
const { tmpdir } = require('node:os')
const { join } = require('node:path')
const { randomUUID, createHash } = require('node:crypto')
const workerPath = join(__dirname, '..', 'out', 'main', 'knowledgeWorker.js')
if (!existsSync(workerPath)) throw new Error(`Knowledge worker build is missing: ${workerPath}`)
const root = mkdtempSync(join(tmpdir(), 'wxe-knowledge-worker-'))
const child = fork(workerPath, [], {
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
serialization: 'advanced',
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }
})
const pending = new Map()
function request(type, payload) {
const requestId = randomUUID()
return new Promise((resolve, reject) => {
pending.set(requestId, { resolve, reject })
child.send({ version: 1, type, requestId, payload }, (error) => {
if (error) reject(error)
})
})
}
child.on('message', (message) => {
if (!message || message.type === 'progress') return
const current = pending.get(message.requestId)
if (!current) return
pending.delete(message.requestId)
if (message.type === 'error') current.reject(new Error(message.error))
else current.resolve(message.payload)
})
function fts(profileId) {
return {
profileId,
tokenizer: 'trigram',
contentMode: 'external',
detail: 'full',
columnsize: 1
}
}
function conversation(accountId, id) {
return {
conversationId: `conversation-${id}`,
completeSnapshot: true,
messages: [
{
accountId,
conversationId: `conversation-${id}`,
messageId: `message-${id}`,
createTime: 1,
senderId: 'fixture-member',
senderName: '脱敏成员',
kind: 'text',
text: `脱敏索引内容 ${id}`
}
]
}
}
function accountPath(accountId) {
const key = createHash('sha256')
.update(`knowledge-account-v1:${accountId}`)
.digest('hex')
.slice(0, 32)
return join(root, key, 'knowledge.sqlite')
}
async function main() {
try {
const chunker = {
version: 'conversation-v1',
maxGapMs: 600000,
maxMessages: 12,
maxCharacters: 1200,
overlapMessages: 3
}
const accountA = 'worker-fixture-a'
const accountB = 'worker-fixture-b'
const first = await request('index', {
accountId: accountA,
databaseRoot: root,
conversations: [conversation(accountA, 'a')],
chunker,
fts: fts('worker-a')
})
await request('index', {
accountId: accountB,
databaseRoot: root,
conversations: [conversation(accountB, 'b')],
chunker,
fts: fts('worker-b')
})
if (
!first ||
first.cancelled ||
!existsSync(accountPath(accountA)) ||
!existsSync(accountPath(accountB))
) {
throw new Error('Knowledge worker did not create isolated derived databases')
}
const search = await request('search', {
accountId: accountA,
databaseRoot: root,
fts: fts('worker-a'),
text: '查询脱敏索引内容 a',
terms: ['脱敏索引内容', 'a'],
limit: 10
})
const evidence = search?.evidence?.[0]
if (
search?.state !== 'ready' ||
!evidence ||
evidence.messageId !== 'message-a' ||
evidence.conversationId !== 'conversation-a' ||
evidence.sender !== '脱敏成员' ||
typeof evidence.timestamp !== 'number'
) {
throw new Error('Knowledge worker search did not return message-level evidence')
}
await request('remove', { accountId: accountA, databaseRoot: root })
if (existsSync(accountPath(accountA)) || !existsSync(accountPath(accountB))) {
throw new Error('Knowledge worker removal crossed an account boundary')
}
const unavailable = await request('search', {
accountId: accountA,
databaseRoot: root,
fts: fts('worker-a'),
text: '查询脱敏索引内容 a',
terms: ['脱敏索引内容'],
limit: 10
})
if (unavailable?.state !== 'unavailable' || unavailable.evidence?.length) {
throw new Error('Knowledge worker did not report unavailable index after removal')
}
await request('close', {})
console.log('Knowledge worker integration check passed')
} finally {
child.kill()
await rm(root, { recursive: true, force: true })
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})