fix(wechat): 早报必须搜当日新闻,禁止用旧稿充草稿
生成说明强制联网搜索当天内容,pushDraft 拒绝非当日页面;forceGenerateOnce 用于发版后重跑今日早报。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Set news_morning_draft.forceGenerateOnce so the 103 Portal worker
|
||||
* regenerates today's daily-news page on the next scan (~30s).
|
||||
* Dry-run by default; pass --apply to persist.
|
||||
*/
|
||||
import process from 'node:process';
|
||||
import mysql from 'mysql2/promise';
|
||||
import { loadH5Environment } from './load-env.mjs';
|
||||
|
||||
loadH5Environment(import.meta.dirname);
|
||||
|
||||
const CONFIG_KEY = 'news_morning_draft';
|
||||
const CONFIG_TABLE = 'h5_wechat_admin_config';
|
||||
|
||||
async function main() {
|
||||
const apply = process.argv.includes('--apply');
|
||||
if (!process.env.DATABASE_URL) {
|
||||
console.error('DATABASE_URL missing');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
|
||||
const [rows] = await pool.query(
|
||||
`SELECT config_json FROM ${CONFIG_TABLE} WHERE config_key = ? LIMIT 1`,
|
||||
[CONFIG_KEY],
|
||||
);
|
||||
const current = rows[0]?.config_json
|
||||
? (typeof rows[0].config_json === 'string'
|
||||
? JSON.parse(rows[0].config_json)
|
||||
: rows[0].config_json)
|
||||
: {};
|
||||
const next = { ...current, forceGenerateOnce: true, enabled: current.enabled !== false };
|
||||
|
||||
console.log(JSON.stringify({
|
||||
apply,
|
||||
currentForceGenerateOnce: Boolean(current.forceGenerateOnce),
|
||||
sourceUserId: current.sourceUserId ?? null,
|
||||
enabled: next.enabled,
|
||||
}, null, 2));
|
||||
|
||||
if (apply) {
|
||||
const now = Date.now();
|
||||
await pool.query(
|
||||
`INSERT INTO ${CONFIG_TABLE} (config_key, config_json, updated_by, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_json = VALUES(config_json),
|
||||
updated_by = VALUES(updated_by),
|
||||
updated_at = VALUES(updated_at)`,
|
||||
[CONFIG_KEY, JSON.stringify(next), 'manual-force-generate', now],
|
||||
);
|
||||
console.log('forceGenerateOnce=true persisted');
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -62,11 +62,11 @@ export function startWechatNewsMorningDraftWorker({
|
||||
let running = false;
|
||||
let generationInFlightDateKey = null;
|
||||
|
||||
const ensureTodayPage = async (config, dateKey) => {
|
||||
const ensureTodayPage = async (config, dateKey, { now = Date.now(), force = false } = {}) => {
|
||||
if (generationInFlightDateKey === dateKey) return null;
|
||||
generationInFlightDateKey = dateKey;
|
||||
try {
|
||||
const task = buildNewsMorningAutoGenerationTask(config);
|
||||
const task = buildNewsMorningAutoGenerationTask(config, { now });
|
||||
await executeTask(task, {
|
||||
userAuth,
|
||||
tkmindProxy,
|
||||
@@ -78,7 +78,7 @@ export function startWechatNewsMorningDraftWorker({
|
||||
timeoutMs: executionTimeoutMs,
|
||||
logger,
|
||||
});
|
||||
const page = resolveTodayPage(config, h5Root);
|
||||
const page = resolveTodayPage(config, h5Root, now);
|
||||
if (!page) {
|
||||
throw new Error('新闻早报页面生成未完成');
|
||||
}
|
||||
@@ -89,6 +89,7 @@ export function startWechatNewsMorningDraftWorker({
|
||||
logger.log?.('[NewsMorningDraft] auto-generated today page', {
|
||||
slug: page.slug,
|
||||
userId: config.sourceUserId,
|
||||
force,
|
||||
});
|
||||
return page;
|
||||
} catch (error) {
|
||||
@@ -111,7 +112,7 @@ export function startWechatNewsMorningDraftWorker({
|
||||
running = true;
|
||||
try {
|
||||
const config = await wechatNewsMorningDraftService.getConfig();
|
||||
if (!config.enabled || !config.autoPushEnabled || !config.sourceUserId) return;
|
||||
if (!config.enabled || !config.sourceUserId) return;
|
||||
|
||||
const now = Date.now();
|
||||
const timezone = config.timezone;
|
||||
@@ -119,6 +120,31 @@ export function startWechatNewsMorningDraftWorker({
|
||||
const toleranceMinutes = Math.max(1, Math.ceil(Number(intervalMs) / 60_000));
|
||||
let todayPage = resolveTodayPage(config, h5Root, now);
|
||||
|
||||
if (config.forceGenerateOnce && generationInFlightDateKey !== dateKey) {
|
||||
try {
|
||||
todayPage = await ensureTodayPage(config, dateKey, { now, force: true });
|
||||
await wechatNewsMorningDraftService.updateConfig(
|
||||
{ forceGenerateOnce: false },
|
||||
{ updatedBy: 'auto-generate' },
|
||||
);
|
||||
if (config.autoPushEnabled && todayPage) {
|
||||
const result = await wechatNewsMorningDraftService.pushDraft({
|
||||
triggeredBy: 'manual-generate',
|
||||
now,
|
||||
});
|
||||
logger.log?.('[NewsMorningDraft] force generate push succeeded', {
|
||||
draftMediaId: result.draftMediaId,
|
||||
pageSlug: result.preview?.page?.slug ?? todayPage.slug,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// keep the flag so the next scan retries
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.autoPushEnabled) return;
|
||||
|
||||
const shouldGenerate =
|
||||
config.autoGenerateEnabled !== false
|
||||
&& !todayPage
|
||||
@@ -133,7 +159,7 @@ export function startWechatNewsMorningDraftWorker({
|
||||
|
||||
if (shouldGenerate && generationInFlightDateKey !== dateKey) {
|
||||
try {
|
||||
todayPage = await ensureTodayPage(config, dateKey);
|
||||
todayPage = await ensureTodayPage(config, dateKey, { now });
|
||||
} catch {
|
||||
// keep trying during generate window on later scans
|
||||
}
|
||||
@@ -164,6 +190,7 @@ export function startWechatNewsMorningDraftWorker({
|
||||
|
||||
const result = await wechatNewsMorningDraftService.pushDraft({
|
||||
triggeredBy: 'auto-push',
|
||||
now,
|
||||
});
|
||||
logger.log?.('[NewsMorningDraft] auto push succeeded', {
|
||||
draftMediaId: result.draftMediaId,
|
||||
@@ -182,6 +209,29 @@ export function startWechatNewsMorningDraftWorker({
|
||||
|
||||
return {
|
||||
runOnce,
|
||||
async generateToday({ force = false, now = Date.now(), pushDraft = false } = {}) {
|
||||
const config = await wechatNewsMorningDraftService.getConfig();
|
||||
if (!config.enabled || !config.sourceUserId) {
|
||||
throw new Error('新闻早报未配置来源用户或未启用');
|
||||
}
|
||||
const dateKey = localDateKey(now, config.timezone);
|
||||
const existing = resolveTodayPage(config, h5Root, now);
|
||||
if (existing && !force) {
|
||||
return { page: existing, skipped: true, reason: 'today-page-already-exists' };
|
||||
}
|
||||
const page = await ensureTodayPage(config, dateKey, { now, force: true });
|
||||
if (!page) {
|
||||
throw new Error('新闻早报页面正在生成中');
|
||||
}
|
||||
let draft = null;
|
||||
if (pushDraft) {
|
||||
draft = await wechatNewsMorningDraftService.pushDraft({
|
||||
triggeredBy: 'manual-generate',
|
||||
now,
|
||||
});
|
||||
}
|
||||
return { page, skipped: false, draft };
|
||||
},
|
||||
stop() {
|
||||
stopped = true;
|
||||
clearInterval(timer);
|
||||
|
||||
@@ -92,13 +92,138 @@ test('news morning draft worker auto-pushes at configured time', async () => {
|
||||
|
||||
await worker.runOnce();
|
||||
assert.equal(pushCalls.length, 1);
|
||||
assert.deepEqual(pushCalls[0], { triggeredBy: 'auto-push' });
|
||||
assert.equal(pushCalls[0].triggeredBy, 'auto-push');
|
||||
} 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'), 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_USER_ID: userId,
|
||||
},
|
||||
});
|
||||
let config = {
|
||||
enabled: true,
|
||||
sourceUserId: userId,
|
||||
autoPushEnabled: true,
|
||||
autoGenerateEnabled: true,
|
||||
forceGenerateOnce: true,
|
||||
timezone: 'Asia/Shanghai',
|
||||
pushHour: 6,
|
||||
pushMinute: 0,
|
||||
};
|
||||
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);
|
||||
},
|
||||
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');
|
||||
} finally {
|
||||
Date.now = originalNow;
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
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);
|
||||
},
|
||||
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], /联网搜索/u);
|
||||
} finally {
|
||||
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('schedule helpers detect today page and due windows', () => {
|
||||
const {
|
||||
isNewsMorningPageForToday,
|
||||
|
||||
@@ -22,7 +22,7 @@ const DEFAULT_DRAFT_ADD_URL = 'https://api.weixin.qq.com/cgi-bin/draft/add';
|
||||
const DEFAULT_MATERIAL_ADD_URL = 'https://api.weixin.qq.com/cgi-bin/material/add_material';
|
||||
const DEFAULT_UPLOAD_IMG_URL = 'https://api.weixin.qq.com/cgi-bin/media/uploadimg';
|
||||
const MAX_THUMB_BYTES = 64 * 1024;
|
||||
export const NEWS_MORNING_TEMPLATE_VERSION = '2026-09-10';
|
||||
export const NEWS_MORNING_TEMPLATE_VERSION = '2026-09-15';
|
||||
|
||||
/** 0910 新闻早报页面结构说明,供定时任务 taskSpec 与草稿转换共用。 */
|
||||
export const NEWS_MORNING_TEMPLATE_0910_SPEC = [
|
||||
@@ -36,6 +36,16 @@ export const NEWS_MORNING_TEMPLATE_0910_SPEC = [
|
||||
'7. 推送微信草稿时转为内联 style HTML(参考公众号排版),禁止整页长图;正文需控制在 2 万字符内。',
|
||||
].join('\n');
|
||||
|
||||
/** 每日正文必须新搜,禁止把历史早报当新闻来源。 */
|
||||
export const NEWS_MORNING_FRESH_CONTENT_SPEC = [
|
||||
'内容硬约束(必须遵守,优先于版式参考):',
|
||||
'1. 必须先联网搜索「执行当日」国内外要闻、财经、科技、体育等,再写页面;不得凭记忆编造。',
|
||||
'2. 禁止复制、改写、微调昨日或任何历史 daily-news-*.html 的新闻标题与正文。',
|
||||
'3. 最近一版 daily-news 只允许当作版式参考(hero / section / card 结构);读完后必须丢弃其中的条目内容。',
|
||||
'4. 每条卡片必须是当日可核验事件,并写明来源;title、date-badge、导语必须写明当日日期。',
|
||||
'5. 若搜索失败,明确写出未能获取当日新闻,禁止用旧稿充数。',
|
||||
].join('\n');
|
||||
|
||||
function normalizeBoolean(value, fallback = false) {
|
||||
if (value == null || value === '') return fallback;
|
||||
if (typeof value === 'boolean') return value;
|
||||
@@ -138,9 +148,10 @@ export function buildNewsMorningAutoGenerationTask(config, { now = Date.now() }
|
||||
userId: config.sourceUserId,
|
||||
title: '每日新闻早报页面(自动)',
|
||||
taskSpec: [
|
||||
NEWS_MORNING_FRESH_CONTENT_SPEC,
|
||||
NEWS_MORNING_TEMPLATE_0910_SPEC,
|
||||
`今日日期:${dateLabel.iso}。`,
|
||||
`输出文件名必须包含今日 MMDD:daily-news-${dateLabel.mmdd}.html。`,
|
||||
`今日日期:${dateLabel.iso}(${dateLabel.year}年${dateLabel.month}月${dateLabel.day}日)。正文必须是这一天的新闻,不能是其它日期的旧稿。`,
|
||||
`输出文件名必须是 daily-news-${dateLabel.mmdd}.html(可覆盖同名旧文件)。`,
|
||||
].join('\n'),
|
||||
recurrence: 'once',
|
||||
timezone,
|
||||
@@ -687,6 +698,7 @@ function defaultsFromEnv(env = process.env) {
|
||||
sourceUserId: String(env.H5_WECHAT_NEWS_MORNING_DRAFT_USER_ID ?? '').trim() || null,
|
||||
pageSlugPattern: String(env.H5_WECHAT_NEWS_MORNING_DRAFT_SLUG ?? 'daily-news-*').trim() || 'daily-news-*',
|
||||
author: String(env.H5_WECHAT_NEWS_MORNING_DRAFT_AUTHOR ?? 'TKMind').trim() || 'TKMind',
|
||||
forceGenerateOnce: false,
|
||||
templateVersion: NEWS_MORNING_TEMPLATE_VERSION,
|
||||
};
|
||||
}
|
||||
@@ -743,6 +755,7 @@ export function createWechatNewsMorningDraftService(
|
||||
sourceUserId: String(stored.sourceUserId ?? defaults.sourceUserId ?? '').trim() || null,
|
||||
pageSlugPattern: String(stored.pageSlugPattern ?? defaults.pageSlugPattern).trim() || defaults.pageSlugPattern,
|
||||
author: String(stored.author ?? defaults.author).trim().slice(0, 8) || defaults.author,
|
||||
forceGenerateOnce: normalizeBoolean(stored.forceGenerateOnce, false),
|
||||
templateVersion: NEWS_MORNING_TEMPLATE_VERSION,
|
||||
};
|
||||
}
|
||||
@@ -822,7 +835,7 @@ export function createWechatNewsMorningDraftService(
|
||||
article,
|
||||
template: {
|
||||
version: NEWS_MORNING_TEMPLATE_VERSION,
|
||||
spec: NEWS_MORNING_TEMPLATE_0910_SPEC,
|
||||
spec: [NEWS_MORNING_FRESH_CONTENT_SPEC, NEWS_MORNING_TEMPLATE_0910_SPEC].join('\n'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -879,7 +892,7 @@ export function createWechatNewsMorningDraftService(
|
||||
getTemplate() {
|
||||
return {
|
||||
version: NEWS_MORNING_TEMPLATE_VERSION,
|
||||
spec: NEWS_MORNING_TEMPLATE_0910_SPEC,
|
||||
spec: [NEWS_MORNING_FRESH_CONTENT_SPEC, NEWS_MORNING_TEMPLATE_0910_SPEC].join('\n'),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -924,6 +937,10 @@ export function createWechatNewsMorningDraftService(
|
||||
payload.author === undefined
|
||||
? current.author
|
||||
: String(payload.author ?? current.author).trim().slice(0, 8) || current.author,
|
||||
forceGenerateOnce:
|
||||
payload.forceGenerateOnce === undefined
|
||||
? current.forceGenerateOnce
|
||||
: normalizeBoolean(payload.forceGenerateOnce, current.forceGenerateOnce),
|
||||
templateVersion: NEWS_MORNING_TEMPLATE_VERSION,
|
||||
};
|
||||
const now = Date.now();
|
||||
@@ -1007,13 +1024,18 @@ export function createWechatNewsMorningDraftService(
|
||||
};
|
||||
},
|
||||
|
||||
async pushDraft({ triggeredBy = null, dryRun = false } = {}) {
|
||||
async pushDraft({
|
||||
triggeredBy = null,
|
||||
dryRun = false,
|
||||
requireToday = true,
|
||||
now = Date.now(),
|
||||
} = {}) {
|
||||
const config = await this.getConfig();
|
||||
if (!config.enabled) {
|
||||
throw new Error('新闻早报草稿推送未启用');
|
||||
}
|
||||
let preview = null;
|
||||
preview = await resolvePreview(config);
|
||||
preview = await resolvePreview(config, { requireToday, now });
|
||||
if (dryRun) {
|
||||
return {
|
||||
dryRun: true,
|
||||
@@ -1027,6 +1049,7 @@ export function createWechatNewsMorningDraftService(
|
||||
h5Root,
|
||||
userId: config.sourceUserId,
|
||||
slugPattern: config.pageSlugPattern,
|
||||
date: new Date(now),
|
||||
timezone: config.timezone,
|
||||
});
|
||||
if (page?.thumbPath) {
|
||||
@@ -1138,6 +1161,10 @@ export function createWechatNewsMorningDraftService(
|
||||
return rows.length > 0;
|
||||
},
|
||||
|
||||
async requestForceGenerate({ updatedBy = null } = {}) {
|
||||
return this.updateConfig({ forceGenerateOnce: true }, { updatedBy });
|
||||
},
|
||||
|
||||
async recordAutoGenerationRun({
|
||||
status,
|
||||
pageSlug = null,
|
||||
@@ -1169,6 +1196,7 @@ export const wechatNewsMorningDraftInternals = {
|
||||
isLocalScheduleDue,
|
||||
isWithinNewsMorningGenerateLeadWindow,
|
||||
buildNewsMorningAutoGenerationTask,
|
||||
NEWS_MORNING_FRESH_CONTENT_SPEC,
|
||||
formatLocalDateParts,
|
||||
localDateKey,
|
||||
};
|
||||
|
||||
@@ -161,12 +161,16 @@ test('news morning draft service persists config and records failed push', async
|
||||
assert.equal(updated.sourceUserId, userId);
|
||||
assert.equal(updated.templateVersion, NEWS_MORNING_TEMPLATE_VERSION);
|
||||
|
||||
const preview = await service.preview();
|
||||
const preview = await service.preview({ now: Date.parse('2026-09-10T08:00:00+08:00') });
|
||||
assert.equal(preview.page.slug, 'daily-news-0910');
|
||||
assert.match(preview.template.spec, /0910|2026-09-10/u);
|
||||
assert.match(preview.template.spec, /禁止复制/u);
|
||||
|
||||
await assert.rejects(
|
||||
() => service.pushDraft({ triggeredBy: 'admin-1' }),
|
||||
() => service.pushDraft({
|
||||
triggeredBy: 'admin-1',
|
||||
now: Date.parse('2026-09-10T08:00:00+08:00'),
|
||||
}),
|
||||
/微信服务号未启用/u,
|
||||
);
|
||||
const runs = await service.listRuns();
|
||||
@@ -199,6 +203,41 @@ test('getTodayStatus and previewToday require today dated page', async () => {
|
||||
assert.equal(preview.page.slug, 'daily-news-0911');
|
||||
});
|
||||
|
||||
test('buildNewsMorningAutoGenerationTask requires fresh daily search', () => {
|
||||
const task = wechatNewsMorningDraftInternals.buildNewsMorningAutoGenerationTask(
|
||||
{ sourceUserId: 'user-1', timezone: 'Asia/Shanghai' },
|
||||
{ now: Date.parse('2026-09-15T05:00:00+08:00') },
|
||||
);
|
||||
assert.match(task.taskSpec, /禁止复制/u);
|
||||
assert.match(task.taskSpec, /联网搜索/u);
|
||||
assert.match(task.taskSpec, /2026-09-15/u);
|
||||
assert.match(task.taskSpec, /daily-news-0915\.html/u);
|
||||
assert.doesNotMatch(task.taskSpec, /可以用昨日正文/u);
|
||||
});
|
||||
|
||||
test('pushDraft refuses yesterday page when requireToday is on', async () => {
|
||||
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'news-draft-stale-'));
|
||||
const userId = 'user-stale';
|
||||
const publicDir = path.join(root, PUBLISH_ROOT_DIR, userId, PUBLIC_ZONE_DIR);
|
||||
await fs.promises.mkdir(publicDir, { recursive: true });
|
||||
await fs.promises.writeFile(path.join(publicDir, 'daily-news-0914.html'), SAMPLE_HTML);
|
||||
|
||||
const service = createWechatNewsMorningDraftService(createPool(), {
|
||||
mpConfig: { enabled: true, appId: 'app', appSecret: 'secret' },
|
||||
h5Root: root,
|
||||
env: {},
|
||||
});
|
||||
await service.updateConfig({ enabled: true, sourceUserId: userId });
|
||||
|
||||
await assert.rejects(
|
||||
() => service.pushDraft({
|
||||
triggeredBy: 'admin-1',
|
||||
now: Date.parse('2026-09-15T08:00:00+08:00'),
|
||||
}),
|
||||
/今日(2026-09-15)新闻早报尚未生成/u,
|
||||
);
|
||||
});
|
||||
|
||||
test('internals normalize booleans consistently', () => {
|
||||
assert.equal(wechatNewsMorningDraftInternals.normalizeBoolean('1', false), true);
|
||||
assert.equal(wechatNewsMorningDraftInternals.normalizeBoolean('off', true), false);
|
||||
|
||||
Reference in New Issue
Block a user