mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-09 09:57:05 +08:00
153 lines
11 KiB
JavaScript
153 lines
11 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawn, execFile } from 'node:child_process';
|
|
import { mkdir, mkdtemp, readFile, writeFile, realpath } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { promisify } from 'node:util';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { chromium, expect } from '@playwright/test';
|
|
import { expertDraftUrl } from '../packages/plugins/experts/dist/domain/navigation.js';
|
|
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
const artifacts = join(root, '.artifacts/office-native');
|
|
const home = await realpath(await mkdtemp(join(tmpdir(), 'workdsh-office-native-')));
|
|
await mkdir(artifacts, { recursive: true });
|
|
const workspace = join(home, 'workspace');
|
|
await mkdir(workspace);
|
|
const skillRoot = join(home, 'agents/skills/expert-authoring-test');
|
|
await mkdir(skillRoot, { recursive: true });
|
|
await writeFile(join(skillRoot, 'SKILL.md'), '---\nname: expert-authoring-test\ndescription: Safe expert authoring acceptance fixture\n---\nReturn a deterministic acceptance fixture.\n');
|
|
await mkdir(join(home, 'storages'));
|
|
const workspaceId = randomUUID();
|
|
const now = new Date().toISOString();
|
|
await writeFile(join(home, 'storages/workspace.json'), JSON.stringify({
|
|
unit: { name: 'workspace', version: 2 },
|
|
global: { initialized: true, workspaceIds: [workspaceId], archivedSessionIds: [] },
|
|
tables: { workspaces: { [workspaceId]: { path: workspace, title: 'Expert test', sessionIds: [], createdAt: now, updatedAt: now } } },
|
|
}));
|
|
const env = { ...process.env, DSH_HOME: home, DSH_AGENTS_HOME: join(home, 'agents'), PATH: `${join(root, 'node_modules/.bin')}:${dirname(process.execPath)}:${process.env.PATH}` };
|
|
const dsh = join(root, 'node_modules/@deepseek-ai/dsh/lib/bin.js');
|
|
const pnpm = join(root, 'node_modules/pnpm/bin/pnpm.cjs');
|
|
const exec = promisify(execFile);
|
|
const command = async (bin, args, cwd = home) => (await exec(process.execPath, [bin, ...args], { cwd, env, timeout: 60_000, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
const cli = (...args) => command(dsh, args);
|
|
let server, browser, log = '';
|
|
const browserErrors = [];
|
|
const checks = [];
|
|
const pass = text => { checks.push(text); console.log(`PASS: ${text}`); };
|
|
async function start() {
|
|
log = '';
|
|
server = spawn(process.execPath, [dsh, '--profile', 'experts', '--host', '127.0.0.1', '--port', '0', '--no-open'], { cwd: home, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
server.stdout.on('data', value => { log += value; });
|
|
server.stderr.on('data', value => { log += value; });
|
|
const deadline = Date.now() + 25_000;
|
|
while (!/http:\/\/127\.0\.0\.1:\d+\/\?token=[\w-]+/.test(log)) {
|
|
if (server.exitCode !== null || Date.now() > deadline) throw new Error(`Host startup failed: ${log.replace(/token=[^\s]+/g, 'token=[redacted]').slice(-2500)}`);
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
const loginUrl = log.match(/http:\/\/127\.0\.0\.1:\d+\/\?token=[\w-]+/)[0];
|
|
let response;
|
|
while (!response) {
|
|
try { response = await fetch(loginUrl, { redirect: 'manual', signal: AbortSignal.timeout(2000) }); }
|
|
catch {
|
|
if (server.exitCode !== null || Date.now() > deadline) throw new Error(`Host unavailable: ${log.replace(/token=[^\s]+/g, 'token=[redacted]').slice(-4500)}`);
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
}
|
|
const cookie = response.headers.getSetCookie().map(value => value.split(';')[0]).join('; ');
|
|
assert.ok(cookie);
|
|
return { address: new URL(loginUrl).origin, cookie };
|
|
}
|
|
async function stop() {
|
|
if (!server || server.exitCode !== null || server.signalCode !== null) return;
|
|
const stopped = new Promise(resolve => server.once('close', resolve));
|
|
server.kill('SIGTERM');
|
|
const timer = setTimeout(() => server.kill('SIGKILL'), 3000);
|
|
await stopped; clearTimeout(timer);
|
|
}
|
|
async function api(host, endpoint, payload = {}) {
|
|
const response = await fetch(`${host.address}/api/workdsh-experts`, { method: 'POST', headers: { cookie: host.cookie, 'content-type': 'application/json' }, body: JSON.stringify({ endpoint, payload }), signal: AbortSignal.timeout(15_000) });
|
|
assert.equal(response.status, 200);
|
|
const result = await response.json();
|
|
assert.equal(result.ok, true, JSON.stringify(result));
|
|
return result.value;
|
|
}
|
|
try {
|
|
const tarballs = [];
|
|
for (const directory of ['packages/providers/identity-local', 'packages/plugins/audit', 'packages/plugins/access', 'packages/plugins/skills', 'packages/plugins/experts', 'packages/plugins/office', 'packages/bundle']) {
|
|
const manifest = JSON.parse(await readFile(join(root, directory, 'package.json'), 'utf8'));
|
|
await command(pnpm, ['--filter', manifest.name, 'pack', '--pack-destination', artifacts], root);
|
|
tarballs.push(join(artifacts, `${manifest.name}-${manifest.version}.tgz`));
|
|
}
|
|
const fixture = join(home, 'probe-client'); await mkdir(fixture);
|
|
await writeFile(join(fixture, 'package.json'), JSON.stringify({ name: 'workdsh-office-native-probe', version: '0.0.0', type: 'module', exports: { '.': './index.js', './client': './client.js' }, dsh: { bundle: { patch: './patch.yml' }, client: { platform: 'web', inject: ['@deepseek-ai/dsh-client-ui-sidebar-right', '@deepseek-ai/dsh-client-ui-sidebar-documentpreview'] } } }));
|
|
await writeFile(join(fixture, 'patch.yml'), '- insert:\n - id: office-native-probe\n name: workdsh-office-native-probe\n');
|
|
await writeFile(join(fixture, 'index.js'), 'export function apply() {}');
|
|
await writeFile(join(fixture, 'client.js'), `window.__ModuleLoader__.load({id:'workdsh-office-native-probe',factory:function(){return {inject:['sidebarRight','documentPreviews'],apply:function(ctx){ctx.effect(function(){window.officeNativeProbe={files:function(sid){ctx.sidebarRight.openTabIn(sid,'files')},registered:function(){return ctx.documentPreviews.getSnapshot().map(d=>d.id)}};return function(){delete window.officeNativeProbe}})}}}});`);
|
|
await command(pnpm, ['pack', '--pack-destination', artifacts], fixture); tarballs.push(join(artifacts, 'workdsh-office-native-probe-0.0.0.tgz'));
|
|
for (const kind of ['docx','pptx','xlsx']) await writeFile(join(workspace, 'input.'+kind), await readFile(join(root, '.artifacts/office-integration/input.'+kind)));
|
|
await cli('--profile', 'experts', '--from-default-profile', 'web', '--dump-config');
|
|
// Exercise the same fresh-profile path used by users. Prefer the local pnpm
|
|
// store, but allow missing transitive metadata to be fetched: a clean machine
|
|
// cannot satisfy a first install with --offline. protobufjs is the sole
|
|
// transitive package in this stack that declares an install script, so keep
|
|
// pnpm's build policy explicit and narrowly scoped.
|
|
await cli('plugin', '--profile', 'experts', 'add', ...tarballs, '--prefer-offline', '--allow-build=protobufjs');
|
|
pass('Seven product Profile layers plus isolated diagnostic installed outside checkout');
|
|
let host = await start();
|
|
const listed = await api(host, 'list');
|
|
assert.ok(listed.items.length >= 3);
|
|
pass('Packaged expert Host and real local identity/access/audit serve defaults');
|
|
const expertId = listed.items[0].id;
|
|
const plan = await api(host, 'prepare-execution', { expertId });
|
|
assert.equal(plan.missing.length, 0, JSON.stringify(plan.missing));
|
|
const creation = await api(host, 'create-execution', { executionPlanId: plan.executionPlanId, operationId: 'packaged-create' });
|
|
assert.ok(creation.sessionId);
|
|
await api(host, 'verify-binding', { sessionId: creation.sessionId });
|
|
pass('Real native Session created and fixed binding verified');
|
|
browser = await chromium.launch({ headless: true });
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
|
page.on('pageerror', error => browserErrors.push(error.message));
|
|
page.on('console', message => { if (message.type() === 'error') browserErrors.push(message.text()); });
|
|
await page.context().addCookies(host.cookie.split('; ').map(pair => { const at = pair.indexOf('='); return { name: pair.slice(0, at), value: pair.slice(at + 1), url: host.address }; }));
|
|
await page.goto(host.address);
|
|
for (const name of ['Continue', 'Configure later']) await page.getByRole('button', { name, exact: true }).click({ timeout: 4000 }).catch(() => {});
|
|
await page.getByRole('button', { name: '专家 · 技能 · 连接器', exact: true }).click();
|
|
await page.getByRole('button', { name: '专家', exact: true }).click();
|
|
await expect(page.getByTestId('workdsh-experts')).toBeVisible();
|
|
await expect(page.getByText(listed.items[0].name, { exact: true }).first()).toBeVisible();
|
|
await page.waitForFunction(() => window.officeNativeProbe);
|
|
assert.ok((await page.evaluate(() => window.officeNativeProbe.registered())).includes('workdsh-office'));
|
|
await page.getByRole('button', { name: `查看专家 ${listed.items[0].name}`, exact: true }).click();
|
|
const summonedResponse = page.waitForResponse(response => response.url().endsWith('/api/workdsh-experts') && response.request().postDataJSON()?.endpoint === 'create-execution');
|
|
await page.getByTitle('用此示例召唤专家(仅填入草稿,不会自动发送)', { exact: true }).first().click();
|
|
const summoned = (await (await summonedResponse).json()).value;
|
|
await expect(page.locator('[contenteditable="true"]').first()).toBeVisible();
|
|
await page.evaluate(sid => window.officeNativeProbe.files(sid), summoned.sessionId);
|
|
await page.screenshot({path:join(artifacts,'files.png')});
|
|
for (const kind of ['docx','pptx','xlsx']) {
|
|
await page.evaluate(sid => window.officeNativeProbe.files(sid), summoned.sessionId);
|
|
await page.getByText('input.'+kind, {exact:true}).first().click();
|
|
if (kind === 'docx') {
|
|
await page.getByRole('region', {name: 'DOCX文档编辑', exact: true}).waitFor({timeout: 30000});
|
|
await page.getByRole('button', {name: '下载 Word', exact: true}).waitFor({timeout: 30000});
|
|
} else if (kind === 'pptx') {
|
|
const editor = page.getByRole('region', {name: 'PPTX 编辑', exact: true});
|
|
await editor.waitFor({timeout: 30000});
|
|
await editor.getByRole('tab', {name: '开始', exact: true}).waitFor({timeout: 30000});
|
|
} else {
|
|
const child = page.frameLocator('iframe[title="Office 文档编辑"]').last();
|
|
await child.locator('#status').filter({hasText: 'Excel 支持'}).waitFor({timeout:30000});
|
|
}
|
|
await page.screenshot({path:join(artifacts,'native-'+kind+'.png')});
|
|
pass('Official resource read and native Office Tab: '+kind);
|
|
}
|
|
await writeFile(join(artifacts,'result.json'),JSON.stringify({checks,browserErrors},null,2));
|
|
} catch(error) {
|
|
await writeFile(join(artifacts,'failure.txt'),String(error));
|
|
const page=browser?.contexts()[0]?.pages()[0];
|
|
if(page) {await page.screenshot({path:join(artifacts,'failure.png')}); await writeFile(join(artifacts,'failure-dom.txt'),await page.locator('body').innerText());}
|
|
throw error;
|
|
} finally {await browser?.close();await stop();}
|