test(工時報表): 釘住週次歸屬的跨月、跨年與五個週五
時區在測試裡固定為 Asia/Taipei:週界是以人在的時區切的,不釘住時區就等於沒釘住答案。 --today 讓「本週」在 CLI 接縫上釘得住,否則預設期間會跟著系統時鐘漂走。 寫入請求那則斷言誠實列出唯一的非 GET——四層前置檢查的寫入權探針, 而不是先把它濾掉再宣稱整趟沒有寫入。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 工時報表的流程正本與輸出模板。
|
||||
*
|
||||
* 報表的難處是規則而不是程式:期間怎麼切、落差怎麼讀、印到哪裡為止。
|
||||
* 規則寫在正本裡,錯了不會有任何測試自己爆掉,所以在這裡釘住。
|
||||
*/
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
assertNeutralPrompt,
|
||||
assertTemplateSections,
|
||||
readPrompt,
|
||||
readTemplate,
|
||||
} from './helpers/prompt-doc.js';
|
||||
|
||||
const template = readTemplate('report');
|
||||
const prompt = readPrompt('sdlc-report');
|
||||
|
||||
/** 報表的四個段落,順序即印出來的順序 */
|
||||
const SECTIONS = ['總計', '分段小計', '逐議題', '附註'];
|
||||
|
||||
// ── 輸出模板 ───────────────────────────────────────────────────────
|
||||
|
||||
test('模板依序包含四個段落', () => {
|
||||
assertTemplateSections(template, SECTIONS);
|
||||
});
|
||||
|
||||
test('模板以 {{變數}} 佔位,不留任何空白待填欄位', () => {
|
||||
const placeholders = [...template.matchAll(/\{\{([^}]+)\}\}/g)].map((m) => m[1]);
|
||||
assert.ok(placeholders.length >= SECTIONS.length, '每個段落至少要有一個佔位');
|
||||
for (const name of placeholders) {
|
||||
assert.match(name, /^[a-z一-龥]+$/u, `佔位名稱 ${name} 應為單一詞,不含空白或符號`);
|
||||
}
|
||||
});
|
||||
|
||||
test('模板不含邏輯:沒有任何條件或迴圈語法', () => {
|
||||
assert.equal(/\{\{[#/^]/.test(template), false, '分段與逐議題由呼叫端展開,模板不做迴圈');
|
||||
});
|
||||
|
||||
test('總計把實際、估算與落差擺在同一列,落差不必自己算', () => {
|
||||
const totals = template.slice(template.indexOf('## 總計'), template.indexOf('## 分段小計'));
|
||||
for (const placeholder of ['{{實際工時}}', '{{估算人天}}', '{{已估實際}}', '{{落差}}']) {
|
||||
assert.ok(totals.includes(placeholder), `總計缺少 ${placeholder}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ── 流程正本 ───────────────────────────────────────────────────────
|
||||
|
||||
test('正本平台中立,description 前綴正確', () => {
|
||||
assertNeutralPrompt(prompt, 'sdlc-report');
|
||||
});
|
||||
|
||||
test('正本交代三種期間,並指明預設是本週', () => {
|
||||
assert.match(prompt, /--week/);
|
||||
assert.match(prompt, /--month YYYY-MM/);
|
||||
assert.match(prompt, /--year YYYY/);
|
||||
assert.match(prompt, /預設/);
|
||||
});
|
||||
|
||||
test('正本釘住期間定義的三句話', () => {
|
||||
assert.match(prompt, /一週為週一至週日/);
|
||||
assert.match(prompt, /該週週五所屬月份/);
|
||||
assert.match(prompt, /W1[–-]W5.*第幾個週五/s);
|
||||
});
|
||||
|
||||
test('正本以實例說明跨月與跨年那一週落在哪邊', () => {
|
||||
assert.match(prompt, /2025-12-29/, '跨年的例子要寫出具體日期,規則才驗得出來');
|
||||
assert.match(prompt, /2026-02-01/, '跨月的例子要寫出具體日期');
|
||||
});
|
||||
|
||||
test('正本指名由 report.js 取數字,並要求直接用算好的時分', () => {
|
||||
assert.match(prompt, /report\.js/);
|
||||
assert.match(prompt, /不要自己再乘|已經算好/);
|
||||
});
|
||||
|
||||
test('正本明令只印在終端,不張貼到任何管道', () => {
|
||||
assert.match(prompt, /只印在終端/);
|
||||
assert.match(prompt, /不(要)?張貼/);
|
||||
});
|
||||
|
||||
test('正本說明落差的正負方向,以及一人天等於幾小時', () => {
|
||||
assert.match(prompt, /正數.*超出估算/);
|
||||
assert.match(prompt, /8\s*小時/);
|
||||
assert.match(prompt, /--day-hours/);
|
||||
});
|
||||
|
||||
test('正本交代總計的落差只涵蓋有估算的議題', () => {
|
||||
assert.match(prompt, /總計的落差只涵蓋有估算的議題/);
|
||||
assert.match(prompt, /已估實際秒/, '要指名分子是哪一個欄位,否則會被讀成全部實際');
|
||||
});
|
||||
|
||||
test('正本交代沒寫估算時落差是空的,不是零', () => {
|
||||
assert.match(prompt, /不是零|非零|空的/);
|
||||
assert.match(prompt, /null/);
|
||||
});
|
||||
|
||||
test('正本交代略過的工時筆數要講出來', () => {
|
||||
assert.match(prompt, /略過/);
|
||||
});
|
||||
|
||||
test('正本的邊界寫明這是唯讀流程', () => {
|
||||
const boundary = prompt.slice(prompt.indexOf('## 邊界'));
|
||||
assert.match(boundary, /不寫入|只發 GET/);
|
||||
assert.match(boundary, /不補登|不動碼錶/);
|
||||
});
|
||||
|
||||
test('正本交代週報沒有分段小計時整段不印', () => {
|
||||
assert.match(prompt, /週報沒有分段/);
|
||||
});
|
||||
Reference in New Issue
Block a user