65 lines
5.0 KiB
JavaScript
65 lines
5.0 KiB
JavaScript
// 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});
|
|
}
|