feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+62
View File
@@ -0,0 +1,62 @@
// Real React UI, mocked Tencent SDK/API: no SMS or chargeable verification calls.
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { mkdir } from 'node:fs/promises';
const require = createRequire(process.env.PLAYWRIGHT_REQUIRE_FROM ?? import.meta.url);
const { chromium } = require('playwright');
const { createServer } = await import(pathToFileURL(resolve('web/node_modules/vite/dist/node/index.js')));
const server = await createServer({ configFile: false, root: resolve('web'), oxc: { jsx: { runtime: 'automatic' } }, logLevel: 'error', server: { host: '127.0.0.1', port: 5325, strictPort: true }, plugins: [{ name: 'captcha-test-harness', resolveId(id) { if (id === '/captcha-harness.jsx') return id; }, load(id) { if (id === '/captcha-harness.jsx') return `import '/src/styles.css';import '/src/maintenance-console.css';import React from 'react';import {createRoot} from 'react-dom/client';import {CaptchaPolicySettings} from '/src/components/CaptchaPolicySettings.tsx';createRoot(document.getElementById('root')).render(<CaptchaPolicySettings tenant="test-tenant" token="test-token"/>);`; } }] });
await server.listen(); let browser, currentPage; const errors = [], checks = [];
try {
browser = await chromium.launch({ headless: true, executablePath: process.env.CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' });
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } }); currentPage = page; page.on('pageerror', e => errors.push(e.message));
await page.route('https://turing.captcha.qcloud.com/TJCaptcha.js', r => r.fulfill({ contentType: 'text/javascript', body: `window.TencentCaptcha=class{constructor(id,cb){window.lastCaptchaApp=id;this.cb=cb;}show(){setTimeout(()=>this.cb(window.captchaResult||{ret:0,ticket:'web-ticket',randstr:'random'}),0)}destroy(){}}` }));
let generation = 0, sends = 0, passwords = 0, mode = 'tencent';
let expireNextCaptcha = true;
let policy = { saas_mode: 'image', mini_mode: 'image', version: 1, saas_configured: true, mini_configured: false }, writes = 0;
await page.route('**/api/v1/**', async route => {
const req = route.request(), path = new URL(req.url()).pathname, body = req.method() === 'POST' ? req.postDataJSON() : undefined;
let status = 200, code = 'OK', message = '', data = {};
if (path.endsWith('/account/session')) { status = 401; code = 'LOGIN_REQUIRED'; }
else if (path.endsWith('/login/session')) data = { sms_enabled: true, csrf_token: 'login-csrf', password_enabled: true };
else if (path.endsWith('/login/captcha')) { data = { captcha_id: 'captcha-' + ++generation, mode, captcha_app_id: mode === 'tencent' ? '123' : undefined, image: mode === 'image' ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jRZkAAAAASUVORK5CYII=' : undefined, expires_at_ms: Date.now() + (expireNextCaptcha ? -1000 : 120000) }; expireNextCaptcha = false; }
else if (path.endsWith('/sms/send')) { assert.equal(body.captcha_ticket, 'web-ticket'); assert.equal(body.captcha_randstr, 'random'); assert.equal(body.captcha_code, undefined); assert.equal(body.captcha_id, 'captcha-' + generation); sends++; data = { sms_challenge_id: 'sms', expires_at_ms: Date.now() + 300000, resend_after_ms: 60000 }; }
else if (path.endsWith('/login/password')) { assert.equal(body.captcha_ticket, 'web-ticket'); assert.equal(body.captcha_randstr, 'random'); passwords++; status = 401; code = 'PASSWORD_INVALID'; message = '手机号或密码不正确'; }
else if (path === '/api/v1/ops/login-captcha-policy') {
assert.equal(req.headers()['x-tenant-id'], 'test-tenant'); assert.equal(req.headers().authorization, 'Bearer test-token');
if (body) { assert.equal(body.version, policy.version); assert.equal(body.saas_mode, 'tencent'); assert.equal(body.mini_mode, 'image'); writes++; policy = { ...policy, ...body, version: policy.version + 1 }; }
data = policy;
} else throw new Error('Unexpected API ' + path);
await route.fulfill({ status, contentType: 'application/json', body: JSON.stringify({ ok: status === 200, code, message, data }) });
});
await page.goto('http://127.0.0.1:5325/'); await page.getByLabel('手机号', { exact: true }).waitFor();
assert.equal(await page.locator('#customer-image-code').count(), 0); await page.getByLabel('手机号', { exact: true }).fill('13800000000');
assert.equal(await page.getByText('安全验证', { exact: true }).count(), 0);
assert.equal(await page.getByRole('button', { name: '刷新验证', exact: true }).count(), 0);
assert.equal(await page.evaluate(() => !!window.TencentCaptcha), false);
const beforeExpiredRetry = generation;
await page.evaluate(() => { window.captchaResult = { ret: 2 }; }); await page.getByRole('button', { name: '获取验证码', exact: true }).click(); await page.getByText('已取消验证', { exact: true }).waitFor(); assert.equal(sends, 0);
assert(generation > beforeExpiredRetry, 'Expired hidden challenge is renewed when send is clicked');
checks.push('no security panel or SDK on initial render; clicking send renews an expired challenge and opens Tencent verification');
await page.evaluate(() => { window.captchaResult = { ret: 0, ticket: 'trerror_123', randstr: 'random' }; }); await page.getByRole('button', { name: '获取验证码', exact: true }).click(); await page.getByText('人机验证未通过,请重试', { exact: true }).waitFor(); assert.equal(sends, 0);
await page.evaluate(() => { window.captchaResult = undefined; }); await page.getByRole('button', { name: '获取验证码', exact: true }).click(); await page.getByText(/验证码已发送/).waitFor(); assert.equal(sends, 1); assert.equal(await page.evaluate(() => window.lastCaptchaApp), '123');
checks.push('SaaS SDK ticket submitted only after successful verification; cancel and disaster tickets cannot send SMS');
await page.getByRole('button', { name: '密码登录', exact: true }).click(); await page.getByLabel('密码', { exact: true }).fill('WrongPassword123!'); await page.locator('.sms-login-form').getByRole('button', { name: /^登\s*录$/ }).click(); await page.getByText('手机号或密码不正确', { exact: true }).waitFor(); assert.equal(passwords, 1);
checks.push('password login uses Tencent proof and refreshes after failure');
assert.equal(await page.getByRole('button', { name: '忘记密码', exact: true }).count(), 1);
mode = 'image'; await page.reload(); await page.getByLabel('图片验证码', { exact: true }).waitFor();
checks.push('server policy switch returns numeric image UI without changing the client');
await page.route('**/__captcha-settings', r => r.fulfill({ contentType: 'text/html', body: '<div id="root"></div><script type="module" src="/captcha-harness.jsx"></script>' }));
await page.goto('http://127.0.0.1:5325/__captcha-settings'); await page.getByText('登录与短信安全验证', { exact: true }).waitFor();
const web = page.getByRole('radiogroup', { name: 'SaaS / 管理端验证码方式' }), mini = page.getByRole('radiogroup', { name: '微信小程序验证码方式' });
assert(await mini.getByRole('radio', { name: /^腾讯云验证码/ }).isDisabled());
await web.getByRole('radio', { name: /^腾讯云验证码/ }).check(); await page.getByRole('button', { name: '保存验证码设置' }).click(); await page.getByText(/已保存。新验证立即生效/).waitFor(); assert.equal(writes, 1);
await page.reload(); await page.getByText('登录与短信安全验证', { exact: true }).waitFor(); assert(await web.getByRole('radio', { name: /^腾讯云验证码/ }).isChecked());
await mkdir(resolve('.design-evidence/tencent-captcha'), { recursive: true }); await page.screenshot({ path: resolve('.design-evidence/tencent-captcha/settings.png'), fullPage: true });
await page.setViewportSize({ width: 375, height: 850 }); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
checks.push('admin settings keep clients independent, disable unconfigured Tencent mode, save and reload; mobile layout has no horizontal overflow');
assert.deepEqual(errors, []); console.log(JSON.stringify({ checks, errors }));
} catch (error) { console.error((await currentPage?.locator('body').innerText())?.slice(0,2000)); console.error(await currentPage?.locator('button').evaluateAll(items=>items.map(x=>({text:x.textContent,html:x.outerHTML.slice(0,800)})))); console.error(await currentPage?.evaluate(()=>({sdk:window.TencentCaptcha?.toString().slice(0,150),result:window.captchaResult,body:document.body.getAttribute('aria-hidden')}))); console.error(errors); throw error; }
finally { await browser?.close(); await server.close(); }