Show authenticated daily operation records

This commit is contained in:
wuyang
2026-11-03 20:57:42 +08:00
parent 84fc19c965
commit ad86a1661b
5 changed files with 95 additions and 5 deletions
+36
View File
@@ -125,3 +125,39 @@ test('evolution is private, readable after login, and empty when absent', async
const empty = await request('/api/evolution', { headers: { Cookie: cookie } });
assert.equal(empty.status, 200); assert.deepEqual(await empty.json(), { schema_version: 1, events: [] });
});
test('daily records require authentication, sort by date, validate input and remain read-only', async t => {
const { directory, request, login } = await fixture(t);
await mkdir(path.join(directory, 'daily'));
const write = (name, value) => writeFile(path.join(directory, 'daily', name), value);
await write('2026-11-02.json', JSON.stringify({ date: '2026-11-02', planned: ['<script>private</script>'], costs: ['未知'] }));
await write('2026-11-03.json', JSON.stringify({ date: '2026-11-03', executed: ['完成'], evidence: ['证据'] }));
await write('2026-11-04.json', '{broken');
await write('2026-11-05.json', JSON.stringify({ date: '2026-11-05', planned: 'wrong type' }));
await write('2026-02-30.json', JSON.stringify({ date: '2026-02-30' }));
await write('ignored.json', 'bad');
const anonymous = await request('/api/daily'); assert.equal(anonymous.status, 401); assert.doesNotMatch(await anonymous.text(), /private|完成/);
assert.equal((await request('/daily/2026-11-02.json')).status, 404);
const cookie = (await login()).headers.get('set-cookie').split(';')[0];
const headers = { Cookie: cookie };
const result = await request('/api/daily', { headers }); assert.equal(result.status, 200);
const body = await result.json(); assert.equal(body.invalid_count, 3);
assert.deepEqual(body.records.map(r => r.date), ['2026-11-03', '2026-11-02']);
assert.deepEqual(body.records[0].planned, []); assert.deepEqual(body.records[0].executed, ['完成']);
assert.deepEqual(body.records[1].costs, ['未知']);
assert.equal((await request('/api/daily', { method: 'POST', headers: { ...headers, Origin: origin }, body: '{}' })).status, 404);
await rm(path.join(directory, 'daily'), { recursive: true });
assert.deepEqual(await (await request('/api/daily', { headers })).json(), { records: [], invalid_count: 0 });
});
test('daily symlinks cannot disclose files outside DATA_DIR', async t => {
const { directory, request, login } = await fixture(t);
const outside = await mkdtemp(path.join(tmpdir(), 'pawplay-daily-outside-')); t.after(() => rm(outside, { recursive: true, force: true }));
await mkdir(path.join(directory, 'daily'));
await writeFile(path.join(outside, 'secret.json'), JSON.stringify({ date: '2026-11-03', executed: ['OUTSIDE-SECRET'] }));
await symlink(path.join(outside, 'secret.json'), path.join(directory, 'daily/2026-11-03.json'));
const cookie = (await login()).headers.get('set-cookie').split(';')[0];
const response = await request('/api/daily', { headers: { Cookie: cookie } });
assert.equal(response.status, 200); const text = await response.text(); assert.doesNotMatch(text, /OUTSIDE-SECRET/);
assert.deepEqual(JSON.parse(text), { records: [], invalid_count: 1 });
});