Files
memind/wechat-cursor-channel.test.mjs
T
john 644f7fc632
Memind CI / Test, build, and release guards (push) Has been cancelled
fix(wechat): add cursor channel modules required by wechat-mp imports
Ship the WeChat Cursor executor helpers referenced by the page delivery path so tests and runtime imports resolve consistently.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 21:14:36 +08:00

342 lines
13 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { createWechatMpService as createProductionWechatMpService } from './wechat-mp.mjs';
import { prepareWechatHtmlDeliveryAtWorkspace } from './mindspace-wechat-html-delivery.mjs';
import {
isWechatCursorChannelReply,
prepareWechatCursorPageDelivery,
} from './wechat-cursor-page-delivery.mjs';
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
function sha1(parts) {
return crypto.createHash('sha1').update([...parts].sort().join('')).digest('hex');
}
function signatureFor(token, timestamp, nonce) {
return sha1([token, timestamp, nonce]);
}
function inboundXml({ fromUser = 'openid-test-1', content = '帮我做个测试页面' } = {}) {
return [
'<xml>',
'<ToUserName><![CDATA[gh_test]]></ToUserName>',
`<FromUserName><![CDATA[${fromUser}]]></FromUserName>`,
'<CreateTime>1710000000</CreateTime>',
'<MsgType><![CDATA[text]]></MsgType>',
`<Content><![CDATA[${content}]]></Content>`,
'<MsgId>msg-cursor-test-1</MsgId>',
'</xml>',
].join('');
}
function createWechatMpService(options) {
const resolvePublishDir = async (userId) =>
options.userAuth?.resolveWorkingDir?.(userId) ?? `/tmp/${userId}`;
const buildCanonicalUrl = (userId, relativePath) => {
const base = String(options.config?.publicBaseUrl ?? 'https://example.com').replace(/\/+$/, '');
const normalized = String(relativePath).replace(/^\/+/, '').split('/').map(encodeURIComponent).join('/');
return `${base}/MindSpace/${encodeURIComponent(userId)}/${normalized}`;
};
const htmlDeliveryAuthority = options.htmlDeliveryAuthority ?? {
async prepareWechatHtmlDelivery(input) {
const publishDir = await resolvePublishDir(input.userId);
return prepareWechatHtmlDeliveryAtWorkspace({
reply: input.reply,
intent: input.intent,
publishDir,
requestStartedAt: input.requestStartedAt,
allowRecentArtifacts: input.allowRecentArtifacts,
buildCanonicalUrl: (relativePath) => buildCanonicalUrl(input.userId, relativePath),
});
},
async ensureWechatFreshPageThumbnails() {
return { artifacts: [], images: [] };
},
};
return createProductionWechatMpService({
...options,
htmlDeliveryAuthority,
});
}
function createBoundWechatService(overrides = {}) {
const apiCalls = [];
const wechatCalls = [];
const cursorCalls = [];
const token = overrides.token ?? 'test-token';
const userId = overrides.userId ?? 'user-cursor-test';
const agentRunGateway = overrides.agentRunGateway ?? {
async createRun(runUserId, payload) {
cursorCalls.push(['createRun', runUserId, payload]);
const touchNow = Date.now();
fs.utimesSync(htmlPath, touchNow / 1000, touchNow / 1000);
return { id: 'run-cursor-1', status: 'queued' };
},
dispatchRun(runId) {
cursorCalls.push(['dispatchRun', runId]);
},
async getRunForUser(runUserId, runId) {
cursorCalls.push(['getRunForUser', runUserId, runId]);
return { id: runId, status: 'succeeded', error: null };
},
async listRunEventsForUser(runUserId, runId) {
cursorCalls.push(['listRunEventsForUser', runUserId, runId]);
return {
events: [{
id: 'evt-1',
eventType: 'tool_gateway_result',
data: {
executor: 'cursor',
stdoutTail: '已生成 public/cursor-wechat-test-page.html',
},
createdAt: Date.now(),
}],
};
},
};
const wechatCursorExecutorPolicyService = overrides.wechatCursorExecutorPolicyService ?? {
async getEffectivePolicy() {
return {
enabled: false,
userAllowed: false,
userAllowlist: [],
intentAllowlist: ['page.generate'],
fallbackToDeepseek: true,
};
},
};
const h5Root = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-cursor-h5-'));
const publishDir = path.join(h5Root, PUBLISH_ROOT_DIR, userId);
fs.mkdirSync(path.join(publishDir, 'public'), { recursive: true });
const htmlPath = path.join(publishDir, 'public', 'cursor-wechat-test-page.html');
fs.writeFileSync(htmlPath, '<html><head><title>Cursor Page</title></head><body><main>hi</main></body></html>');
const now = Date.now();
fs.utimesSync(htmlPath, now / 1000, now / 1000);
const service = createWechatMpService({
h5Root,
config: {
enabled: true,
appId: 'wx_local_test_only',
appSecret: 'local-secret',
token,
publicBaseUrl: 'https://example.com',
mediaPublicBaseUrl: 'https://example.com',
bindPath: '/auth/wechat/authorize?intent=login',
ackText: 'ack',
unsupportedText: 'unsupported',
unboundTextPrefix: '请先绑定',
progressDelayMs: 0,
requireFreshPageThumbnail: false,
...(overrides.config ?? {}),
},
userAuth: {
async findWechatUserByOpenid(_appId, openid) {
return {
userId,
status: 'active',
nickname: '测试用户',
username: 'test-user',
openid,
};
},
async getWechatAgentRoute() {
return { agentSessionId: 'session-cursor-1', isNewSession: false };
},
async clearWechatAgentRoute() {},
async canUseChat() {
return { ok: true };
},
async resolveWorkingDir() {
return publishDir;
},
async getAgentSessionPolicy() {
return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
},
async getUserPublishLayout() {
return { displayName: '测试用户', username: 'test-user', slug: 'test-user', constraints: null };
},
async registerAgentSession() {},
async upsertWechatAgentRoute() {},
async billSessionUsage() {},
async recordWechatMpMessage() {
return { inserted: true };
},
async finishWechatMpMessage() {},
async insertWechatMpMessageDetail() {},
...(overrides.userAuth ?? {}),
},
agentRunGateway,
wechatCursorExecutorPolicyService,
sessionApiFetch: async (sessionId, pathname, init = {}) => {
apiCalls.push([pathname, init.method ?? 'GET']);
if (pathname.endsWith('/events')) {
return new Response(
[
'data: {"type":"Message","request_id":"req-fallback-1","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"DeepSeek 回退页面已完成\\nhttps://example.com/MindSpace/user-cursor-test/public/fallback-page.html"}]}}\n\n',
'data: {"type":"Finish","request_id":"req-fallback-1","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
].join(''),
{ status: 200, headers: { 'Content-Type': 'text/event-stream' } },
);
}
if (pathname.endsWith('/reply')) {
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
}
if (pathname.includes('/agent/harness_remember') || pathname.includes('/agent/session/reconcile') || pathname.includes('/agent/harness_bootstrap')) {
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
}
throw new Error(`unexpected api path: ${pathname} session=${sessionId}`);
},
wechatFetch: async (url, init = {}) => {
wechatCalls.push([String(url), init.method ?? 'GET', init]);
if (String(url).includes('/cgi-bin/stable_token')) {
return new Response(JSON.stringify({ access_token: 'local-access-token', expires_in: 7200 }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
if (String(url).includes('/cgi-bin/message/custom/send')) {
return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
throw new Error(`unexpected wechat url: ${url}`);
},
linkExists: async (urlText) => String(urlText).includes('cursor-wechat-test-page.html')
|| String(urlText).includes('fallback-page.html'),
...(overrides.extra ?? {}),
});
return {
service,
apiCalls,
wechatCalls,
cursorCalls,
publishDir,
token,
};
}
test('non-allowlisted wechat user keeps deepseek session reply path', async () => {
const { service, apiCalls, cursorCalls, token } = createBoundWechatService();
const timestamp = '1710000000';
const nonce = 'nonce-no-cursor';
const result = await service.handleInboundMessage(
inboundXml({ content: '帮我做一个简单测试页面' }),
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
);
assert.equal(result.status, 200);
await result.task;
assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), false);
assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), true);
});
test('allowlisted wechat user uses cursor agent-run path without session reply', async () => {
const { service, apiCalls, cursorCalls, wechatCalls, token } = createBoundWechatService({
wechatCursorExecutorPolicyService: {
async getEffectivePolicy(_userId, user) {
return {
enabled: true,
userAllowed: true,
userAllowlist: [user?.userId ?? 'user-cursor-test'],
intentAllowlist: ['page.generate'],
fallbackToDeepseek: true,
};
},
},
});
const timestamp = '1710000001';
const nonce = 'nonce-cursor';
const result = await service.handleInboundMessage(
inboundXml({ content: '帮我做一个简单测试页面' }),
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
);
assert.equal(result.status, 200);
await result.task;
assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), true);
assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), false);
const customSend = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
assert.ok(customSend, 'expected wechat custom send');
const sendBody = JSON.parse(String(customSend[2]?.body ?? '{}'));
const outboundText = String(sendBody?.text?.content ?? '');
assert.doesNotMatch(outboundText, /没有按服务号页面技能真正生成成功/);
assert.match(outboundText, /cursor-wechat-test-page\.html/);
});
test('allowlisted user falls back to deepseek when cursor run fails', async () => {
const { service, apiCalls, cursorCalls, token } = createBoundWechatService({
agentRunGateway: {
async createRun(runUserId, payload) {
cursorCalls.push(['createRun', runUserId, payload]);
return { id: 'run-fail-1', status: 'queued' };
},
dispatchRun(runId) {
cursorCalls.push(['dispatchRun', runId]);
},
async getRunForUser(runUserId, runId) {
cursorCalls.push(['getRunForUser', runUserId, runId]);
return { id: runId, status: 'failed', error: 'simulated cursor failure' };
},
async listRunEventsForUser() {
return { events: [] };
},
},
wechatCursorExecutorPolicyService: {
async getEffectivePolicy() {
return {
enabled: true,
userAllowed: true,
userAllowlist: ['user-cursor-test'],
intentAllowlist: ['page.generate'],
fallbackToDeepseek: true,
};
},
},
});
const timestamp = '1710000002';
const nonce = 'nonce-fallback';
const result = await service.handleInboundMessage(
inboundXml({ content: '帮我做一个简单测试页面' }),
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
);
assert.equal(result.status, 200);
await result.task;
assert.equal(cursorCalls.some(([kind]) => kind === 'createRun'), true);
assert.equal(apiCalls.some(([pathname]) => String(pathname).endsWith('/reply')), true);
});
test('production-like openid in script default is not used when policy disabled', async () => {
const prodLikeOpenid = 'ooil-0VFj68QK1tkHl39uL610et8';
const { service, cursorCalls, token } = createBoundWechatService({
userId: '1c99b83b-0454-474f-a5d2-129d34506a32',
userAuth: {
async findWechatUserByOpenid(_appId, openid) {
assert.equal(openid, prodLikeOpenid);
return {
userId: '1c99b83b-0454-474f-a5d2-129d34506a32',
status: 'active',
nickname: 'John',
username: 'john',
};
},
},
});
const timestamp = '1710000003';
const nonce = 'nonce-prod-openid';
const result = await service.handleInboundMessage(
inboundXml({ fromUser: prodLikeOpenid, content: '你好' }),
{ timestamp, nonce, signature: signatureFor(token, timestamp, nonce) },
);
assert.equal(result.status, 200);
await result.task;
assert.equal(cursorCalls.length, 0);
});