78b7d546c2
Let users bind their own official account credentials in M 配置 and push MindSpace pages to the WeChat draft box during or after publish, reusing the existing draft conversion pipeline. Co-authored-by: Cursor <cursoragent@cursor.com>
112 lines
3.5 KiB
JavaScript
112 lines
3.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
createMindSpaceWechatMpConfigService,
|
|
fetchWechatMpAccessToken,
|
|
mindspaceWechatMpConfigInternals,
|
|
} from './mindspace-wechat-mp-config.mjs';
|
|
|
|
function createPool() {
|
|
const rows = new Map();
|
|
return {
|
|
rows,
|
|
async query(sql, params = []) {
|
|
if (sql.includes('CREATE TABLE')) return [[]];
|
|
if (sql.includes('INSERT INTO h5_user_wechat_mp_configs')) {
|
|
rows.set(params[0], {
|
|
user_id: params[0],
|
|
app_id: params[1],
|
|
account_type: params[2],
|
|
label: params[3],
|
|
author: params[4],
|
|
secret_ciphertext: params[5],
|
|
secret_iv: params[6],
|
|
secret_tag: params[7],
|
|
verified_at: params[8],
|
|
created_at: params[9],
|
|
updated_at: params[10],
|
|
});
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
if (sql.includes('FROM h5_user_wechat_mp_configs')) {
|
|
const row = rows.get(params[0]);
|
|
return [[row ?? null].filter(Boolean)];
|
|
}
|
|
if (sql.startsWith('UPDATE h5_user_wechat_mp_configs')) {
|
|
const row = rows.get(params[2]);
|
|
if (row) {
|
|
row.verified_at = params[0];
|
|
row.updated_at = params[1];
|
|
}
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
if (sql.startsWith('DELETE FROM h5_user_wechat_mp_configs')) {
|
|
rows.delete(params[0]);
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
throw new Error(`unexpected sql: ${sql}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
test('upsertConfig stores encrypted secret and returns masked config', async () => {
|
|
const pool = createPool();
|
|
const service = createMindSpaceWechatMpConfigService(pool, {
|
|
env: { H5_SETTINGS_ENCRYPTION_KEY: 'test-key' },
|
|
wechatFetch: async () => ({
|
|
ok: true,
|
|
text: async () => JSON.stringify({ access_token: 'token-1', expires_in: 7200 }),
|
|
}),
|
|
});
|
|
const config = await service.upsertConfig('user-1', {
|
|
appId: 'wx1234567890',
|
|
appSecret: 'secret-value-1234567890',
|
|
accountType: 'service',
|
|
label: '我的服务号',
|
|
author: 'TKMind',
|
|
});
|
|
assert.equal(config.configured, true);
|
|
assert.equal(config.appId, 'wx1234567890');
|
|
assert.ok(config.appSecretMasked.includes('*'));
|
|
assert.ok(config.appSecretMasked.endsWith('7890'));
|
|
assert.equal(config.label, '我的服务号');
|
|
assert.ok(config.verifiedAt);
|
|
});
|
|
|
|
test('verifyConfig refreshes verifiedAt', async () => {
|
|
const pool = createPool();
|
|
const service = createMindSpaceWechatMpConfigService(pool, {
|
|
env: { H5_SETTINGS_ENCRYPTION_KEY: 'test-key' },
|
|
wechatFetch: async () => ({
|
|
ok: true,
|
|
text: async () => JSON.stringify({ access_token: 'token-2', expires_in: 7200 }),
|
|
}),
|
|
});
|
|
await service.upsertConfig('user-1', {
|
|
appId: 'wx1234567890',
|
|
appSecret: 'secret-value-1234567890',
|
|
});
|
|
const verified = await service.verifyConfig('user-1');
|
|
assert.ok(verified.verifiedAt);
|
|
});
|
|
|
|
test('fetchWechatMpAccessToken surfaces upstream error', async () => {
|
|
await assert.rejects(
|
|
() => fetchWechatMpAccessToken(
|
|
{ appId: 'bad', appSecret: 'bad' },
|
|
{
|
|
wechatFetch: async () => ({
|
|
ok: true,
|
|
text: async () => JSON.stringify({ errcode: 40013, errmsg: 'invalid appid' }),
|
|
}),
|
|
},
|
|
),
|
|
/invalid appid/,
|
|
);
|
|
});
|
|
|
|
test('normalizeAccountType defaults to service', () => {
|
|
assert.equal(mindspaceWechatMpConfigInternals.normalizeAccountType('subscription'), 'subscription');
|
|
assert.equal(mindspaceWechatMpConfigInternals.normalizeAccountType('unknown'), 'service');
|
|
});
|