fix(scheduled-task): deliver verified WeChat links only after page is ready
Memind CI / Test, build, and release guards (push) Successful in 8m42s
Memind CI / Test, build, and release guards (push) Successful in 8m42s
Wait for HTML materialization before releasing delivery contracts, pass verified MindSpace URLs through proactive WeChat sends, resend on reconcile when pages land late, and report deferred customer-service delivery as unsent. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
buildScheduledTaskExecutionPrompt,
|
||||
buildScheduledTaskVerifiedHtmlUrls,
|
||||
deliveryTextPromisesPublicHtml,
|
||||
extractPublicHtmlPathsFromText,
|
||||
extractScheduledTaskDeliveryText,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
formatScheduledTaskDeliveryMessage,
|
||||
looksLikeScheduledTaskNonDelivery,
|
||||
reconcileStuckStaticPageDeliveryContracts,
|
||||
resolveScheduledTaskDeliveryPollIntervalMs,
|
||||
resolveScheduledTaskDeliveryRetryDelaysMs,
|
||||
} from './scheduled-task-executor.mjs';
|
||||
|
||||
@@ -75,22 +77,45 @@ test('deliveryTextPromisesPublicHtml detects page delivery replies', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('resolveScheduledTaskDeliveryRetryDelaysMs reads env override', () => {
|
||||
test('resolveScheduledTaskDeliveryPollIntervalMs reads env override', () => {
|
||||
assert.equal(
|
||||
resolveScheduledTaskDeliveryPollIntervalMs({
|
||||
H5_SCHEDULED_TASK_DELIVERY_POLL_INTERVAL_MS: '7500',
|
||||
}),
|
||||
7500,
|
||||
);
|
||||
});
|
||||
|
||||
test('resolveScheduledTaskDeliveryRetryDelaysMs keeps backward-compatible shape', () => {
|
||||
assert.deepEqual(
|
||||
resolveScheduledTaskDeliveryRetryDelaysMs({
|
||||
H5_SCHEDULED_TASK_DELIVERY_RETRY_DELAYS_MS: '0,100,250',
|
||||
H5_SCHEDULED_TASK_DELIVERY_POLL_INTERVAL_MS: '100',
|
||||
}),
|
||||
[0, 100, 250],
|
||||
[0, 100, 100, 100],
|
||||
);
|
||||
});
|
||||
|
||||
test('buildScheduledTaskVerifiedHtmlUrls maps ready paths to public urls', () => {
|
||||
assert.deepEqual(
|
||||
buildScheduledTaskVerifiedHtmlUrls('user-1', ['public/news.html'], {
|
||||
publicBaseUrl: 'https://m.tkmind.cn',
|
||||
}),
|
||||
['https://m.tkmind.cn/MindSpace/user-1/public/news.html'],
|
||||
);
|
||||
});
|
||||
|
||||
test('finalizeScheduledTaskPageDelivery prepares and releases static contracts', async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-finalize-'));
|
||||
const publishDir = path.join(dir, 'MindSpace', 'user-1');
|
||||
const relativePath = 'public/news.html';
|
||||
await fs.mkdir(path.dirname(path.join(publishDir, relativePath)), { recursive: true });
|
||||
await fs.writeFile(path.join(publishDir, relativePath), '<html></html>', 'utf8');
|
||||
const calls = [];
|
||||
const pool = {
|
||||
async query(sql, params) {
|
||||
calls.push({ sql, params });
|
||||
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
||||
return [[{ workspace_relative_path: 'public/news.html' }]];
|
||||
return [[{ workspace_relative_path: relativePath }]];
|
||||
}
|
||||
if (sql.includes('SELECT id, data_mode, status')) {
|
||||
return [[{ id: 'c1', data_mode: 'static', status: 'preparing' }]];
|
||||
@@ -106,14 +131,43 @@ test('finalizeScheduledTaskPageDelivery prepares and releases static contracts',
|
||||
userId: 'user-1',
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
publishDir: '/tmp/publish',
|
||||
publishDir,
|
||||
deliveryText: 'public/news.html 已生成',
|
||||
logger: { warn() {}, info() {} },
|
||||
});
|
||||
assert.deepEqual(readyPaths, ['public/news.html']);
|
||||
assert.deepEqual(readyPaths, [relativePath]);
|
||||
assert.ok(calls.some((call) => call.sql.includes('INSERT INTO h5_page_delivery_contracts')));
|
||||
});
|
||||
|
||||
test('finalizeScheduledTaskPageDelivery skips release when html file is missing', async () => {
|
||||
const publishDir = path.join(await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-finalize-missing-')), 'MindSpace', 'user-1');
|
||||
await fs.mkdir(publishDir, { recursive: true });
|
||||
const pool = {
|
||||
async query(sql) {
|
||||
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
||||
return [[{ workspace_relative_path: 'public/news.html' }]];
|
||||
}
|
||||
if (sql.includes('SELECT id, data_mode, status')) {
|
||||
return [[{ id: 'c1', data_mode: 'static', status: 'preparing' }]];
|
||||
}
|
||||
if (sql.includes("SET status = 'ready'")) {
|
||||
throw new Error('should not mark ready without html file');
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
};
|
||||
const readyPaths = await finalizeScheduledTaskPageDelivery({
|
||||
pool,
|
||||
userId: 'user-1',
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
publishDir,
|
||||
deliveryText: 'public/news.html 已生成',
|
||||
logger: { warn() {}, info() {} },
|
||||
});
|
||||
assert.deepEqual(readyPaths, []);
|
||||
});
|
||||
|
||||
test('reconcileStuckStaticPageDeliveryContracts releases materialized static pages', async () => {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-'));
|
||||
const userId = 'user-1';
|
||||
@@ -137,7 +191,7 @@ test('reconcileStuckStaticPageDeliveryContracts releases materialized static pag
|
||||
h5Root: dir,
|
||||
logger: { info() {} },
|
||||
});
|
||||
assert.deepEqual(released, [relativePath]);
|
||||
assert.deepEqual(released, [{ userId, relativePath }]);
|
||||
});
|
||||
|
||||
test('reconcileStuckStaticPageDeliveryContracts fails orphan contracts without html', async () => {
|
||||
|
||||
Reference in New Issue
Block a user