// Copyright (c) 未来飞马
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Trademark Notice:
// The MPL-2.0 license grants copyright permissions for source code only.
// It does NOT grant any rights to use trademarks including "未来飞马",
// "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
// Any use of these trademarks requires separate written permission.
/**
* 测试夹具生成器:现场合成 .docx / .pptx / .png,落盘到指定目录。
*
* 刻意不在仓库里存任何二进制素材(.gitignore 也排除了图片)——夹具全部即时生成,
* 内容为合成的假数据,不含任何真实 PII。
*/
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { ensureDir, resolvePython } from '../lib.mjs';
import { makeTinyPng } from '../case-intake.mjs';
const PY_BUILD = `
import sys, zipfile, os
target = sys.argv[1]
doc_xml = '''
IC 计算机科学 考前冲刺案例
客户在读帝国理工本科二年级,期末前两周找到我们,担心挂科。
跟进状态:异议处理中。客户觉得贵,家长不放心老师。
结果:期末提分 18 分,家长很满意,已续课。
查看成绩单
'''
doc_rels = '''
'''
pres_xml = '''
'''
pres_rels = '''
'''
def slide(title, body, with_pic, rid):
pic = ('' % rid) if with_pic else ''
return '''
%s
%s
%s
''' % (title, body, pic)
slide_rels = '''
'''
png = bytes.fromhex(
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
"0000000a49444154789c6300010000050001"
"0d0a2db40000000049454e44ae426082")
with zipfile.ZipFile(os.path.join(target, 'case.docx'), 'w') as zf:
zf.writestr('[Content_Types].xml', '')
zf.writestr('word/document.xml', doc_xml)
zf.writestr('word/_rels/document.xml.rels', doc_rels)
zf.writestr('word/media/image1.png', png)
with zipfile.ZipFile(os.path.join(target, 'deck.pptx'), 'w') as zf:
zf.writestr('[Content_Types].xml', '')
zf.writestr('ppt/presentation.xml', pres_xml)
zf.writestr('ppt/_rels/presentation.xml.rels', pres_rels)
zf.writestr('ppt/slides/slide1.xml', slide('案例合集', '英国本科 方案推荐中 导师匹配', True, 'rId2'))
zf.writestr('ppt/slides/slide2.xml', slide('结果与反馈', '期末提分 出分反馈 家长认可', False, 'rId2'))
zf.writestr('ppt/slides/_rels/slide1.xml.rels', slide_rels)
zf.writestr('ppt/media/image1.png', png)
print('ok')
`;
/**
* 生成夹具。
* @param {string} dir 目标目录
* @returns {{docx:string, pptx:string, images:string[]}}
*/
export function makeFixtures(dir) {
const target = ensureDir(dir);
const python = resolvePython();
const run = spawnSync(python, ['-c', PY_BUILD, target], { encoding: 'utf8' });
if (run.status !== 0) {
throw new Error(`夹具生成失败:${run.stderr || run.stdout}`);
}
const images = [];
// 九宫格:9 张同尺寸、同一批连发(时间戳相邻)→ 应判为同一组 3x3
const base = 1_700_000_000_000;
for (let i = 0; i < 9; i++) {
const file = path.join(target, `grid-${String(i + 1).padStart(2, '0')}.png`);
fs.writeFileSync(file, makeTinyPng(400, 400));
fs.utimesSync(file, new Date(base + i * 1000), new Date(base + i * 1000));
images.push(file);
}
// 另一批:不同尺寸 + 不同时间 → 不应并进九宫格
const other = path.join(target, 'other-01.png');
fs.writeFileSync(other, makeTinyPng(1600, 900));
fs.utimesSync(other, new Date(base - 3 * 86_400_000), new Date(base - 3 * 86_400_000));
images.push(other);
return { docx: path.join(target, 'case.docx'), pptx: path.join(target, 'deck.pptx'), images };
}