// Seeded temporary records only; no real account/report reads, writes or paid calls. import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawn, execSync } from 'node:child_process'; import { once } from 'node:events'; import { fileURLToPath, pathToFileURL } from 'node:url'; const { chromium } = await import(pathToFileURL(execSync('npm root -g', { encoding:'utf8' }).trim() + '/playwright/index.mjs').href); const root = fs.mkdtempSync(path.join(os.tmpdir(), 'juzi-beijing-test-')); const times = ['2026-10-13T15:59:59.999Z', '2026-10-13T16:00:00.000Z', '2026-10-14T06:34:42.839Z', '2026-10-14T15:59:59.999Z', '2026-10-14T16:00:00.000Z', '2026-10-12T16:00:00.000Z']; const records = times.map((created_at, i) => ({ record_id:'R20261014-TIME' + i, created_at, updated_at:created_at, detect_status:'成功', dims:{ firm:{ S:8 }, vita:{ S:9 }, stab:{ S:8.5 } }, cats:{}, total_score:85, photos:{}, notes:[] })); const file = path.join(root, 'records.jsonl'); const stored = records.map(r => JSON.stringify(r)).join('\n') + '\n'; fs.writeFileSync(file, stored); const child = spawn(process.execPath, ['-e', "Date.now=()=>Date.parse('2026-10-13T16:30:00.000Z');require('./server.js')"], { cwd:fileURLToPath(new URL('../', import.meta.url)), env:{ ...process.env, TZ:'America/New_York', PORT:'18117', HOST:'127.0.0.1', JUZI_DATA_ROOT:root, SKIN_API_KEY:'fixture', SKIN_API_SECRET:'fixture', PUBLIC_BASE_URL:'', WECHAT_APP_ID:'', WECHAT_APP_SECRET:'' }, stdio:['ignore','pipe','pipe'] }); let browser; try { await new Promise((resolve,reject) => { const timer=setTimeout(()=>reject(Error('startup timeout')),10000); child.stdout.on('data', b=>{ if(b.toString().includes('已启动')){clearTimeout(timer);resolve();} }); child.once('exit',code=>{clearTimeout(timer);reject(Error('server exited '+code));}); }); const base='http://127.0.0.1:18117/'; const read=async route=>{const r=await fetch(base+route);assert.equal(r.status,200);return r.json();}; const filtered=await read('api/admin/records?from=2026-10-14&to=2026-10-14'); assert.deepEqual(filtered.items.map(r=>r.record_id), [3,2,1].map(i=>records[i].record_id)); const early=await read('api/admin/records?to=2026-10-13');assert.equal(early.total,2); const late=await read('api/admin/records?from=2026-10-15');assert.equal(late.total,1); const exact=new URLSearchParams({from:times[2],to:'2026-10-14T14:34:42.839+08:00'}); assert.equal((await read('api/admin/records?'+exact)).total,1); for(const route of ['api/admin/records?from=2026-02-30','api/admin/export?to=bad']) assert.equal((await fetch(base+route)).status,400); const stats=await read('api/admin/stats');assert.equal(stats.today_total,3,'today is October14 in Beijing, not UTC October13'); assert.equal(stats.daily.at(-1).date,'2026-10-14');assert.equal(stats.daily.at(-1).total,3);assert.equal(stats.daily.at(-2).total,2); const csv=await(await fetch(base+'api/admin/export?from=2026-10-14&to=2026-10-14')).text(); assert(csv.includes('创建时间(北京时间)'));assert(csv.includes('2026-10-14 00:00:00'));assert(csv.includes('2026-10-14 14:34:42'));assert(csv.includes('2026-10-14 23:59:59')); assert(!csv.includes(records[0].record_id));assert(!csv.includes(records[4].record_id)); browser=await chromium.launch();const errors=[]; for(const timezoneId of ['America/Los_Angeles','UTC','Asia/Shanghai']){ const context=await browser.newContext({viewport:{width:1600,height:1000},timezoneId}); const page=await context.newPage();page.on('pageerror',e=>errors.push(e.message)); await page.route('**/*',route=>{assert.equal(route.request().method(),'GET');return route.continue();}); await page.goto(base+'admin.html');await page.waitForSelector('#tbl-records tbody tr'); const row=page.locator('#tbl-records tbody tr').filter({hasText:records[2].record_id}); assert((await row.textContent()).includes('2026-10-14 14:34:42')); await page.fill('#f-from','2026-10-14');await page.fill('#f-to','2026-10-14'); const response=page.waitForResponse(r=>r.url().includes('/api/admin/records?')); await page.click('#btn-search');assert.equal((await(await response).json()).total,3); await row.click();await page.waitForFunction(()=>document.querySelector('#d-body').textContent.includes('创建时间')); const body=await page.locator('#d-body').textContent();assert(body.includes('创建时间 2026-10-14 14:34:42'));assert(body.includes('更新时间 2026-10-14 14:34:42')); if(timezoneId==='Asia/Shanghai')await page.screenshot({path:'/tmp/juzi-admin-beijing-20261014.png'}); await context.close(); } assert.deepEqual(errors,[]);assert.equal(fs.readFileSync(file,'utf8'),stored,'original UTC records remain byte-identical'); console.log('PASS Beijing admin: list/detail across browser timezones; date/ISO filters, CSV, midnight today/daily stats, invalid dates; UTC records unchanged'); }finally{ if(browser)await browser.close();child.kill('SIGTERM');if(child.exitCode===null)await once(child,'exit'); fs.rmSync(root,{recursive:true,force:true}); }