WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const http = require('node:http');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
|
||||
|
||||
const directory = path.resolve(__dirname, '../../acceptance');
|
||||
const base = 'http://127.0.0.1:18080/api/v1';
|
||||
const sql = statement => execFileSync('docker', ['--context', 'colima-yawan', 'exec', 'WeKnora-postgres', 'psql', '-U', 'postgres', '-d', 'yawan_native_acceptance_20260915', '-v', 'ON_ERROR_STOP=1', '-Atc', statement], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
||||
const literal = value => "'" + String(value).replaceAll("'", "''") + "'";
|
||||
const checks = [], calls = [], browserErrors = [];
|
||||
const check = (name, passed) => { checks.push({ name, passed: !!passed }); assert.ok(passed, name); };
|
||||
let auth, model, human, browser;
|
||||
|
||||
const server = http.createServer(async (request, response) => {
|
||||
try {
|
||||
let body = ''; for await (const part of request) body += part;
|
||||
const input = JSON.parse(body), payload = JSON.parse(input.messages.at(-1).content);
|
||||
calls.push(payload);
|
||||
assert.equal(input.stream, true);
|
||||
response.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
||||
const chunk = delta => ({ id: 'trial-fixture', model: 'trial-fixture', choices: [{ index: 0, delta, finish_reason: null }] });
|
||||
response.write(`data: ${JSON.stringify(chunk({ role: 'assistant', content: '先观察问题,再用清晰的例子展开。' }))}\n\n`);
|
||||
await new Promise(resolve => setTimeout(resolve, 900));
|
||||
response.write(`data: ${JSON.stringify(chunk({ content: '最后总结观点,邀请听众回应。' }))}\n\n`);
|
||||
response.write(`data: ${JSON.stringify({ id: 'trial-fixture', model: 'trial-fixture', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 30, completion_tokens: 20, total_tokens: 50 } })}\n\n`);
|
||||
response.end('data: [DONE]\n\n');
|
||||
} catch (error) {
|
||||
response.writeHead(500); response.end(JSON.stringify({ error: 'fixture failure' }));
|
||||
}
|
||||
});
|
||||
|
||||
async function request(route, method = 'GET', body, expected = 200) {
|
||||
const result = await fetch(base + route, { method, headers: { 'Content-Type': 'application/json', ...(auth ? { Authorization: `Bearer ${auth.token}`, 'X-Tenant-ID': '10059' } : {}) }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(30000) });
|
||||
assert.equal(result.status, expected, `${method} ${route} HTTP ${result.status}`);
|
||||
return result.json();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const credentials = JSON.parse(fs.readFileSync(path.join(directory, 'super-admin-login.json'), 'utf8'));
|
||||
auth = await request('/auth/login', 'POST', { email: credentials.email, password: credentials.password });
|
||||
await new Promise(resolve => server.listen(0, '0.0.0.0', resolve));
|
||||
try {
|
||||
model = (await request('/models', 'POST', { name: `trial-fixture-${Date.now()}`, type: 'KnowledgeQA', source: 'remote', parameters: { base_url: `http://host.docker.internal:${server.address().port}/v1`, api_key: 'local-fixture-only', provider: 'generic' } }, 201)).data;
|
||||
human = (await request('/digital-humans', 'POST', { name: `版本试写验收-${Date.now()}`, model_id: model.id, persona: { tone: 'ACTIVE_VERSION_ONE' } }, 201)).data;
|
||||
human = (await request(`/digital-humans/${human.id}/review`, 'POST', { version: 1, approve: true, note: 'fixture' })).data;
|
||||
human = (await request(`/digital-humans/${human.id}`, 'PUT', { name: human.name, model_id: model.id, revision: human.revision, persona: { tone: 'SELECTED_DRAFT_VERSION_TWO' } })).data;
|
||||
const before = JSON.stringify(human);
|
||||
const trial = await fetch(`${base}/digital-humans/${human.id}/trial`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${auth.token}`, 'X-Tenant-ID': '10059' }, body: JSON.stringify({ version: 2, theme: '教学表达', advice: '先举例,再总结' }), signal: AbortSignal.timeout(30000) });
|
||||
check('stream_endpoint_ready', trial.status === 200 && trial.headers.get('content-type').includes('text/event-stream'));
|
||||
const events = (await trial.text()).split('\n\n').filter(event => event.startsWith('event:trial') || event.startsWith('event: trial')).map(event => JSON.parse(event.split('\n').find(line => line.startsWith('data:')).slice(5)));
|
||||
check('progress_and_result_complete', events.some(event => event.type === 'progress') && events.some(event => event.type === 'delta') && events.at(-1).type === 'complete' && events.at(-1).progress === 100);
|
||||
check('selected_draft_reaches_configured_model', calls.length === 1 && calls[0].selected_version === 2 && calls[0].expression_profile.tone === 'SELECTED_DRAFT_VERSION_TWO' && calls[0].writing_advice === '先举例,再总结');
|
||||
const after = (await request(`/digital-humans/${human.id}`)).data;
|
||||
check('trial_does_not_publish_or_edit_profile', JSON.stringify(after) === before && after.active_version === 1 && after.bindings[0].applied_version === 1);
|
||||
await request(`/digital-humans/${human.id}/trial`, 'POST', { version: 999, theme: 'missing' }, 404);
|
||||
check('missing_version_does_not_call_model', calls.length === 1);
|
||||
|
||||
browser = await chromium.launch({ headless: true, channel: 'chrome' });
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
|
||||
await context.addInitScript(a => {
|
||||
const values = { weknora_user: JSON.stringify({ ...a.user, tenant_id: String(a.user.tenant_id) }), weknora_tenant: JSON.stringify({ ...a.active_tenant, id: '10059' }), weknora_token: a.token, weknora_refresh_token: a.refresh_token, weknora_memberships: JSON.stringify(a.memberships), weknora_selected_tenant_id: '10059', 'weknora:new-user-guide-done:v1': '1' };
|
||||
for (const [key, value] of Object.entries(values)) localStorage.setItem(key, value);
|
||||
}, auth);
|
||||
const page = await context.newPage(); page.setDefaultTimeout(20000);
|
||||
page.on('pageerror', error => browserErrors.push(error.message));
|
||||
await page.goto('http://127.0.0.1:15173/platform/digital-humans');
|
||||
await page.locator('.human-row').filter({ hasText: human.name }).getByRole('button', { name: '管理', exact: true }).click();
|
||||
await page.getByText('版本审核', { exact: true }).click();
|
||||
const panel = page.locator('.trial-panel');
|
||||
await panel.getByPlaceholder('填写主题和目标受众').fill('教学试写验收');
|
||||
await panel.getByRole('button', { name: '开始试写', exact: true }).click();
|
||||
await panel.locator('.trial-progress').waitFor();
|
||||
await page.screenshot({ path: path.join(directory, 'digital-trial-progress-desktop.png'), animations: 'disabled' });
|
||||
await panel.getByText('试写完成', { exact: true }).waitFor();
|
||||
check('browser_selected_version_result', (await panel.locator('.trial-result').innerText()).includes('v2 试写结果') && (await panel.locator('.trial-result').innerText()).includes('最后总结观点'));
|
||||
await page.screenshot({ path: path.join(directory, 'digital-trial-desktop.png'), animations: 'disabled' });
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.screenshot({ path: path.join(directory, 'digital-trial-mobile.png'), animations: 'disabled' });
|
||||
check('mobile_no_horizontal_overflow', await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2));
|
||||
const usage = JSON.parse(sql(`SELECT COALESCE(json_agg(u),'[]') FROM workbench_usage u WHERE tenant_id=10059 AND model_id=${literal(model.id)}`));
|
||||
check('trial_metered_to_caller', usage.length === 2 && usage.every(row => row.owner_id === auth.user.id && row.status === 'succeeded' && row.points > 0 && row.action.includes('试写')));
|
||||
check('no_browser_errors', browserErrors.length === 0);
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
if (human) {
|
||||
await request(`/digital-humans/${human.id}`, 'DELETE').catch(() => {});
|
||||
sql(`BEGIN; DELETE FROM digital_human_jobs WHERE digital_human_id=${literal(human.id)}; DELETE FROM digital_human_bindings WHERE digital_human_id=${literal(human.id)}; DELETE FROM digital_human_versions WHERE digital_human_id=${literal(human.id)}; DELETE FROM digital_humans WHERE id=${literal(human.id)}; DELETE FROM custom_agents WHERE id=${literal(human.primary_agent_id)}; COMMIT;`);
|
||||
}
|
||||
if (model) {
|
||||
// Refund only this model fixture's metered calls; concurrent balances are preserved.
|
||||
sql(`BEGIN; UPDATE workbench_balances SET balance=balance+(SELECT COALESCE(sum(points),0) FROM workbench_usage WHERE tenant_id=10059 AND owner_id=${literal(auth.user.id)} AND model_id=${literal(model.id)}) WHERE tenant_id=10059 AND user_id=${literal(auth.user.id)}; DELETE FROM workbench_transactions WHERE tenant_id=10059 AND user_id=${literal(auth.user.id)} AND metadata->>'call_id' IN (SELECT call_id FROM workbench_usage WHERE model_id=${literal(model.id)}); DELETE FROM workbench_balance_holds WHERE id IN (SELECT call_id FROM workbench_usage WHERE model_id=${literal(model.id)}); DELETE FROM workbench_usage WHERE model_id=${literal(model.id)}; DELETE FROM models WHERE id=${literal(model.id)}; COMMIT;`);
|
||||
}
|
||||
await new Promise(resolve => server.close(resolve));
|
||||
const report = { time: new Date().toISOString(), checks, browser_errors: browserErrors, model_calls: calls.length, model: 'local deterministic stream fixture; no paid generation' };
|
||||
fs.writeFileSync(path.join(directory, 'digital-trial-live-20260917.json'), JSON.stringify(report, null, 2));
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
}
|
||||
}
|
||||
main().catch(error => { console.error(error.message); process.exitCode = 1; });
|
||||
Reference in New Issue
Block a user