57a9aadf53
Memind CI / Test, build, and release guards (push) Successful in 4m43s
- news002 image/structure audit no longer throws: prune empty sections and publish a best-effort page with a warning - WeChat push strips images that failed to upload instead of refusing the draft - forceGenerateOnce is cleared before running, so a failed run cannot loop; the archived page is restored when generation fails - degraded-page regeneration runs at most once per day - content safety rules cover vulgar sexual terms and gossip/adult sites Co-authored-by: Cursor <cursoragent@cursor.com>
544 lines
19 KiB
JavaScript
544 lines
19 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import { PUBLIC_ZONE_DIR, PUBLISH_ROOT_DIR } from './user-publish.mjs';
|
|
import { startWechatNewsMorningDraftWorker } from './wechat-news-morning-draft-worker.mjs';
|
|
import {
|
|
createWechatNewsMorningDraftService,
|
|
wechatNewsMorningDraftInternals,
|
|
} from './wechat-news-morning-draft.mjs';
|
|
|
|
const SAMPLE_HTML = `<!DOCTYPE html><html><body><h1>📰 每日新闻早报</h1></body></html>`;
|
|
|
|
const HEALTHY_TODAY_HTML = `${SAMPLE_HTML}${'<div class="card"><h3>条目</h3></div>'.repeat(8)}`;
|
|
|
|
function createPool() {
|
|
const rows = [];
|
|
return {
|
|
rows,
|
|
async query(sql, params = []) {
|
|
if (sql.includes('CREATE TABLE')) return [[], []];
|
|
if (sql.includes('FROM h5_wechat_admin_config')) return [[], []];
|
|
if (sql.includes('INSERT INTO h5_wechat_news_draft_runs')) {
|
|
rows.push({
|
|
id: params[0],
|
|
status: params[1],
|
|
pageSlug: params[2],
|
|
pageUrl: params[3],
|
|
draftMediaId: params[4],
|
|
errorMessage: params[5],
|
|
triggeredBy: params[6],
|
|
createdAt: Number(params[7]),
|
|
});
|
|
return [[], []];
|
|
}
|
|
if (sql.includes('FROM h5_wechat_news_draft_runs')) {
|
|
return [rows.slice().reverse(), []];
|
|
}
|
|
return [[], []];
|
|
},
|
|
};
|
|
}
|
|
|
|
test('news morning draft worker auto-pushes at configured time', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-worker-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), SAMPLE_HTML);
|
|
|
|
const pool = createPool();
|
|
const service = createWechatNewsMorningDraftService(pool, {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_AUTO: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_HOUR: '6',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_MINUTE: '0',
|
|
},
|
|
});
|
|
|
|
const pushCalls = [];
|
|
service.pushDraft = async (options = {}) => {
|
|
pushCalls.push(options);
|
|
return {
|
|
ok: true,
|
|
draftMediaId: 'draft-1',
|
|
preview: { page: { slug: 'daily-news-0911' } },
|
|
};
|
|
};
|
|
service.hasSuccessfulAutoPushToday = async () => false;
|
|
|
|
const fixedNow = Date.UTC(2026, 8, 10, 22, 0, 0);
|
|
const originalNow = Date.now;
|
|
Date.now = () => fixedNow;
|
|
|
|
try {
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1',
|
|
},
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
await worker.runOnce();
|
|
assert.equal(pushCalls.length, 1);
|
|
assert.equal(pushCalls[0].triggeredBy, 'auto-push');
|
|
} finally {
|
|
Date.now = originalNow;
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('runOnce auto-generates in the lead window even if auto-push is off', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-autogen-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
|
|
const pool = createPool();
|
|
const service = createWechatNewsMorningDraftService(pool, {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
},
|
|
});
|
|
service.getConfig = async () => ({
|
|
enabled: true,
|
|
sourceUserId: userId,
|
|
autoPushEnabled: false,
|
|
autoGenerateEnabled: true,
|
|
generateLeadMinutes: 60,
|
|
timezone: 'Asia/Shanghai',
|
|
pushHour: 6,
|
|
pushMinute: 0,
|
|
pageSlugPattern: 'daily-news-*',
|
|
});
|
|
const pushCalls = [];
|
|
service.pushDraft = async (options = {}) => {
|
|
pushCalls.push(options);
|
|
return { ok: true, draftMediaId: 'draft-should-not-fire' };
|
|
};
|
|
const executeCalls = [];
|
|
const fixedNow = Date.UTC(2026, 8, 10, 21, 15, 0);
|
|
const originalNow = Date.now;
|
|
Date.now = () => fixedNow;
|
|
|
|
try {
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: { H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1' },
|
|
executeTask: async () => {
|
|
executeCalls.push('generate');
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), `${SAMPLE_HTML}<!--lead-->`);
|
|
},
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
await worker.runOnce();
|
|
assert.equal(executeCalls.length, 1);
|
|
assert.equal(pushCalls.length, 0);
|
|
assert.match(fs.readFileSync(path.join(publicDir, 'daily-news-0911.html'), 'utf8'), /lead/);
|
|
} finally {
|
|
Date.now = originalNow;
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('runOnce honors forceGenerateOnce even when today page exists', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-flag-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), HEALTHY_TODAY_HTML);
|
|
|
|
const pool = createPool();
|
|
const service = createWechatNewsMorningDraftService(pool, {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
},
|
|
});
|
|
let config = {
|
|
enabled: true,
|
|
sourceUserId: userId,
|
|
autoPushEnabled: true,
|
|
autoGenerateEnabled: true,
|
|
forceGenerateOnce: true,
|
|
timezone: 'Asia/Shanghai',
|
|
pushHour: 6,
|
|
pushMinute: 0,
|
|
pageSlugPattern: 'daily-news-*',
|
|
};
|
|
service.getConfig = async () => config;
|
|
service.updateConfig = async (payload) => {
|
|
config = { ...config, ...payload };
|
|
return config;
|
|
};
|
|
const pushCalls = [];
|
|
service.pushDraft = async (options = {}) => {
|
|
pushCalls.push(options);
|
|
return {
|
|
ok: true,
|
|
draftMediaId: 'draft-force',
|
|
preview: { page: { slug: 'daily-news-0911' } },
|
|
};
|
|
};
|
|
|
|
const executeCalls = [];
|
|
const fixedNow = Date.UTC(2026, 8, 10, 22, 30, 0);
|
|
const originalNow = Date.now;
|
|
Date.now = () => fixedNow;
|
|
|
|
try {
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: { H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1' },
|
|
executeTask: async (task) => {
|
|
executeCalls.push(task.taskSpec);
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), `${SAMPLE_HTML}<!--forced-->`);
|
|
},
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
await worker.runOnce();
|
|
assert.equal(executeCalls.length, 1);
|
|
assert.match(executeCalls[0], /禁止复制/u);
|
|
assert.equal(config.forceGenerateOnce, false);
|
|
assert.equal(pushCalls.length, 1);
|
|
assert.equal(pushCalls[0].triggeredBy, 'manual-generate');
|
|
assert.equal(fs.existsSync(path.join(publicDir, 'daily-news-0911.html')), true);
|
|
assert.ok(
|
|
fs.readdirSync(publicDir).some((name) => name.startsWith('_archived-daily-news-0911-')),
|
|
);
|
|
} finally {
|
|
Date.now = originalNow;
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('failed forceGenerateOnce clears the flag, restores the original page and does not retry', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-force-fail-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
const pagePath = path.join(publicDir, 'daily-news-0911.html');
|
|
fs.writeFileSync(pagePath, HEALTHY_TODAY_HTML);
|
|
|
|
const service = createWechatNewsMorningDraftService(createPool(), {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
},
|
|
});
|
|
let config = {
|
|
enabled: true,
|
|
sourceUserId: userId,
|
|
autoPushEnabled: true,
|
|
autoGenerateEnabled: true,
|
|
forceGenerateOnce: true,
|
|
timezone: 'Asia/Shanghai',
|
|
pushHour: 6,
|
|
pushMinute: 0,
|
|
pageSlugPattern: 'daily-news-*',
|
|
};
|
|
service.getConfig = async () => config;
|
|
service.updateConfig = async (payload) => {
|
|
config = { ...config, ...payload };
|
|
return config;
|
|
};
|
|
const pushCalls = [];
|
|
service.pushDraft = async (options = {}) => {
|
|
pushCalls.push(options);
|
|
return { ok: true, draftMediaId: 'draft-x' };
|
|
};
|
|
service.hasSuccessfulAutoPushToday = async () => true;
|
|
|
|
let executeCount = 0;
|
|
const fixedNow = Date.UTC(2026, 8, 10, 22, 30, 0);
|
|
const originalNow = Date.now;
|
|
Date.now = () => fixedNow;
|
|
try {
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: { H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1' },
|
|
executeTask: async () => {
|
|
executeCount += 1;
|
|
throw new Error('agent timeout');
|
|
},
|
|
prefetchNewsSearch: async () => ({ brief: '', groups: [] }),
|
|
logger: { log() {}, warn() {} },
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
await worker.runOnce();
|
|
await worker.runOnce();
|
|
assert.equal(executeCount, 1);
|
|
assert.equal(config.forceGenerateOnce, false);
|
|
assert.equal(pushCalls.length, 0);
|
|
assert.equal(fs.readFileSync(pagePath, 'utf8'), HEALTHY_TODAY_HTML);
|
|
assert.equal(
|
|
fs.readdirSync(publicDir).some((name) => name.startsWith('_archived-daily-news-0911-')),
|
|
false,
|
|
);
|
|
} finally {
|
|
Date.now = originalNow;
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('news002 page that misses structure checks is still published with empty sections pruned', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-news002-degraded-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
|
|
const service = createWechatNewsMorningDraftService(createPool(), {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
H5_WECHAT_NEWS_MORNING_TEMPLATE_ID: 'news002',
|
|
},
|
|
});
|
|
const warnings = [];
|
|
const html = '<!DOCTYPE html><html><body><div class="masthead"><h1>每日新闻早报</h1></div>'
|
|
+ '<div class="section" id="domestic"><h2>国内</h2>'
|
|
+ '<div class="card" data-event-key="c919"><h3>国产大飞机交付第100架</h3>'
|
|
+ '<div class="body"><p>商飞公布最新交付数据。</p></div>'
|
|
+ '<a href="https://www.news.cn/a/20260911/c919.html">来源</a></div></div>'
|
|
+ '<div class="section" id="voices"><h2>声音</h2></div>'
|
|
+ '</body></html>';
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1',
|
|
MEMIND_NEWS_MORNING_SEARXNG_FALLBACK: '0',
|
|
},
|
|
executeTask: async () => {
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), html);
|
|
},
|
|
prefetchNewsSearch: async () => ({ brief: '', groups: [] }),
|
|
contentReviewer: async () => ({ ok: true, violations: [] }),
|
|
logger: { log() {}, warn: (...args) => warnings.push(args) },
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
try {
|
|
const result = await worker.generateToday({ now: Date.UTC(2026, 8, 10, 22, 0, 0), force: true });
|
|
assert.equal(result.skipped, false);
|
|
const saved = fs.readFileSync(path.join(publicDir, 'daily-news-0911.html'), 'utf8');
|
|
assert.match(saved, /国产大飞机交付第100架/u);
|
|
assert.doesNotMatch(saved, /id="voices"/);
|
|
assert.ok(warnings.some(([message]) => /publishing best-effort page/.test(String(message))));
|
|
} finally {
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('generateToday skips existing page unless force is set', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-force-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), SAMPLE_HTML);
|
|
|
|
const pool = createPool();
|
|
const service = createWechatNewsMorningDraftService(pool, {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_AUTO: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
H5_WECHAT_NEWS_MORNING_TEMPLATE_ID: 'news001',
|
|
},
|
|
});
|
|
|
|
const executeCalls = [];
|
|
const fixedNow = Date.UTC(2026, 8, 10, 22, 0, 0);
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: { H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1' },
|
|
executeTask: async (task) => {
|
|
executeCalls.push(task.taskSpec);
|
|
fs.writeFileSync(path.join(publicDir, 'daily-news-0911.html'), `${SAMPLE_HTML}<!--forced-->`);
|
|
},
|
|
prefetchNewsSearch: async () => ({
|
|
brief: '【专用联网搜索预取】国内国际热门事件',
|
|
groups: [],
|
|
}),
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
try {
|
|
const skipped = await worker.generateToday({ now: fixedNow, force: false });
|
|
assert.equal(skipped.skipped, true);
|
|
assert.equal(executeCalls.length, 0);
|
|
|
|
const forced = await worker.generateToday({ now: fixedNow, force: true });
|
|
assert.equal(forced.skipped, false);
|
|
assert.equal(forced.page.slug, 'daily-news-0911');
|
|
assert.equal(executeCalls.length, 1);
|
|
assert.match(executeCalls[0], /禁止复制/u);
|
|
assert.match(executeCalls[0], /tkmind_search/u);
|
|
assert.match(executeCalls[0], /专用联网搜索预取/u);
|
|
assert.match(executeCalls[0], /国内国际热门事件/u);
|
|
assert.match(fs.readFileSync(path.join(publicDir, 'daily-news-0911.html'), 'utf8'), /forced/);
|
|
assert.ok(
|
|
fs.readdirSync(publicDir).some((name) => name.startsWith('_archived-daily-news-0911-')),
|
|
);
|
|
} finally {
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('generateToday runs the content safety gate on the generated page without blocking it', async () => {
|
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'news-draft-safety-'));
|
|
const userId = 'user-news';
|
|
const publicDir = path.join(tmpRoot, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
|
fs.mkdirSync(publicDir, { recursive: true });
|
|
|
|
const service = createWechatNewsMorningDraftService(createPool(), {
|
|
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
|
h5Root: tmpRoot,
|
|
env: {
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_ENABLED: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_AUTO: '1',
|
|
H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID: userId,
|
|
H5_WECHAT_NEWS_MORNING_TEMPLATE_ID: 'news001',
|
|
},
|
|
});
|
|
const unsafeCard = '<div class="card"><h3>91每日大赛-吃瓜爆料平台,明星绯闻每日更新</h3><p>黑料</p>'
|
|
+ '<div class="source"><a href="https://example.com/2026/09/11/gossip-feed">来源</a></div></div>'
|
|
+ '<div class="card"><h3>国产大飞机交付第100架</h3><p>商飞公布最新交付数据。</p>'
|
|
+ '<div class="source"><a href="https://www.news.cn/a/20260911/c919.html">来源</a></div></div>';
|
|
const reviewerCalls = [];
|
|
const worker = startWechatNewsMorningDraftWorker({
|
|
wechatNewsMorningDraftService: service,
|
|
mpConfig: { enabled: true },
|
|
userAuth: { canUseChat: async () => ({ ok: true }) },
|
|
tkmindProxy: { id: 'proxy' },
|
|
h5Root: tmpRoot,
|
|
env: { H5_WECHAT_NEWS_MORNING_DRAFT_WORKER_ENABLED: '1' },
|
|
executeTask: async (task) => {
|
|
assert.match(task.taskSpec, /内容安全底线/u);
|
|
fs.writeFileSync(
|
|
path.join(publicDir, 'daily-news-0911.html'),
|
|
SAMPLE_HTML.replace('</body>', `${unsafeCard}</body>`),
|
|
);
|
|
},
|
|
prefetchNewsSearch: async () => ({ brief: '', groups: [] }),
|
|
contentReviewer: async (items) => {
|
|
reviewerCalls.push(items.length);
|
|
return { ok: true, violations: [] };
|
|
},
|
|
logger: { log() {}, warn() {} },
|
|
intervalMs: 30_000,
|
|
runOnStart: false,
|
|
setIntervalFn: () => ({ unref() {} }),
|
|
});
|
|
|
|
try {
|
|
const result = await worker.generateToday({ now: Date.UTC(2026, 8, 10, 22, 0, 0), force: true });
|
|
assert.equal(result.skipped, false);
|
|
const saved = fs.readFileSync(path.join(publicDir, 'daily-news-0911.html'), 'utf8');
|
|
assert.doesNotMatch(saved, /吃瓜爆料平台/u);
|
|
assert.match(saved, /国产大飞机交付第100架/u);
|
|
assert.match(saved, /name="memind-content-safety"/);
|
|
assert.match(saved, /本文由 AI 辅助整理/u);
|
|
assert.equal(reviewerCalls.length, 1, 'second pass reuses cached review verdicts');
|
|
} finally {
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('schedule helpers detect today page and due windows', () => {
|
|
const {
|
|
isNewsMorningPageForToday,
|
|
isLocalScheduleDue,
|
|
isWithinNewsMorningGenerateLeadWindow,
|
|
formatLocalDateParts,
|
|
} = wechatNewsMorningDraftInternals;
|
|
|
|
const now = Date.UTC(2026, 8, 10, 22, 0, 0);
|
|
const parts = formatLocalDateParts(new Date(now), 'Asia/Shanghai');
|
|
assert.equal(parts.mmdd, '0911');
|
|
|
|
assert.equal(
|
|
isNewsMorningPageForToday(
|
|
{ slug: 'daily-news-0911' },
|
|
{ date: new Date(now), timezone: 'Asia/Shanghai' },
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
isLocalScheduleDue(
|
|
{ hour: 6, minute: 0, timezone: 'Asia/Shanghai' },
|
|
now,
|
|
1,
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
isWithinNewsMorningGenerateLeadWindow(
|
|
{
|
|
pushHour: 6,
|
|
pushMinute: 0,
|
|
generateLeadMinutes: 60,
|
|
timezone: 'Asia/Shanghai',
|
|
},
|
|
Date.UTC(2026, 8, 10, 21, 15, 0),
|
|
),
|
|
true,
|
|
);
|
|
});
|