mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
118 lines
8.5 KiB
TypeScript
118 lines
8.5 KiB
TypeScript
import { randomUUID, createHash } from 'node:crypto';
|
|
import { mkdir, readFile, writeFile, rm } from 'node:fs/promises';
|
|
import { join, resolve } from 'node:path';
|
|
import { clusterTest as test, expect } from '@/playwright/suite';
|
|
import { createToolsDevSuite, e2eWorkspaceRoot } from '@/tools-dev/runtime';
|
|
import { warmPlaywrightDaemonRuntime, warmPlaywrightWebRuntime } from '@/playwright/runtime-lifecycle';
|
|
import { createOwnWalletKeys, loadApiWalletFixture, readConsoleWallets, runApiWalletAction, walletEvidence } from '@/playwright/api-wallet-fixture';
|
|
import { T } from '@/timeouts';
|
|
|
|
const directory = process.env.OD_API001_FIXTURE_DIR;
|
|
test.use({ viewport: { width: 1280, height: 800 }, video: 'on' });
|
|
test('[P1] API-001 own API Key and real Open Design task share one wallet and retain actors', async ({ page, browser }, info) => {
|
|
test.skip(!directory, 'Requires an owned isolated AMR API/Link/Web fixture');
|
|
test.setTimeout(T.xlong * 6);
|
|
if (!directory) throw new Error('API001 owned fixture directory required');
|
|
const f = await loadApiWalletFixture(directory);
|
|
const evidence = resolve(directory, '../evidence');
|
|
await mkdir(evidence, { recursive: true });
|
|
const runtime = JSON.parse(await readFile(resolve(directory, 'runtime.private.json'), 'utf8')) as { velaBin: string; velaSourceSha: string; velaSha256: string; opencodeBin: string; opencodeSha256: string };
|
|
expect(runtime.velaSourceSha).toBe(f.tuple.amr);
|
|
expect(createHash('sha256').update(await readFile(runtime.velaBin)).digest('hex')).toBe(runtime.velaSha256);
|
|
expect(createHash('sha256').update(await readFile(runtime.opencodeBin)).digest('hex')).toBe(runtime.opencodeSha256);
|
|
// Recovery may continue only when the owned fixture proves API $2 is the
|
|
// sole positive call and the native client never reached the provider.
|
|
if (process.env.OD_API001_RESUME_CLIENT === '1') runApiWalletAction(f, directory, 'resume-client');
|
|
else {
|
|
await createOwnWalletKeys(browser, f, directory, evidence, info);
|
|
runApiWalletAction(f, directory, 'consume-api');
|
|
}
|
|
const namespace = `api001-${randomUUID().slice(0, 8)}`;
|
|
const root = join(e2eWorkspaceRoot(), '.tmp', 'e2e', namespace);
|
|
const scratch = join(root, 'scratch');
|
|
const suite = createToolsDevSuite({ root, namespace, ownerPid: process.pid, dataDir: join(scratch, 'data'), toolsDevRoot: join(scratch, 'tools-dev'), codexHomeDir: join(scratch, 'codex-home') });
|
|
const amrHome = join(scratch, 'amr-home');
|
|
await mkdir(amrHome, { recursive: true, mode: 0o700 });
|
|
await mkdir(suite.dataDir, { recursive: true });
|
|
const owner = f.actors.owner;
|
|
await writeFile(join(amrHome, 'config.json'), JSON.stringify({ profiles: { local: { controlKey: owner.controlKey, runtimeKey: owner.runtimeKey, apiUrl: f.cfg.urls.api, linkUrl: f.cfg.urls.link, user: { id: owner.userId, email: owner.email, name: 'API001 Owner' } } } }), { mode: 0o600 });
|
|
const config = { privacyDecisionAt: 1, telemetry: { metrics: false, content: false, artifactManifest: false }, mode: 'daemon', agentId: 'amr', model: f.model, onboardingCompleted: true, agentModels: { amr: { model: f.model } }, agentCliEnv: { amr: { OPEN_DESIGN_AMR_PROFILE: 'local', VELA_BIN: runtime.velaBin, VELA_OPENCODE_BIN: runtime.opencodeBin } } };
|
|
await writeFile(join(suite.dataDir, 'app-config.json'), JSON.stringify(config));
|
|
const env = { AMR_HOME: amrHome, VELA_BIN: runtime.velaBin, VELA_OPENCODE_BIN: runtime.opencodeBin, VELA_PROFILE: 'local', OPEN_DESIGN_AMR_PROFILE: 'local', VELA_API_URL: f.cfg.urls.api, VELA_LINK_URL: f.cfg.urls.link, VELA_CONTROL_KEY: owner.controlKey, OD_WORKSPACE_CONTEXT_SOURCE: 'vela', OD_VELA_WEB_URL: f.cfg.urls.web, OD_COLLAB_TRANSPORT: 'off', OD_RESOURCE_TRANSPORT: 'off', OD_TEAM_PROJECTS_TRANSPORT: 'off', OPENCODE_DISABLE_DEFAULT_PLUGINS: '1', OPENCODE_DISABLE_EXTERNAL_SKILLS: '1' };
|
|
let started = false;
|
|
let succeeded = false;
|
|
const posts: Array<{ workspaceId: string | undefined; status: number; runId: string }> = [];
|
|
try {
|
|
await suite.startWeb(env); started = true;
|
|
await warmPlaywrightWebRuntime(suite.url.web('/'));
|
|
await warmPlaywrightDaemonRuntime(suite.url.daemon('/api/health'));
|
|
await page.addInitScript(({ config, workspaceId, workspaceMemberId }) => {
|
|
localStorage.setItem('open-design:config', JSON.stringify(config));
|
|
localStorage.setItem('open-design:locale', 'en');
|
|
localStorage.setItem('open-design:locale-source', 'manual');
|
|
sessionStorage.setItem('od.workspaceSelection.v1', JSON.stringify({ workspaceId, workspaceMemberId }));
|
|
}, { config, workspaceId: f.workspaceId, workspaceMemberId: f.ownerMemberId });
|
|
page.on('response', async (response) => {
|
|
if (response.request().method() !== 'POST' || new URL(response.url()).pathname !== '/api/runs') return;
|
|
const body = await response.json() as { runId: string };
|
|
posts.push({ workspaceId: response.request().headers()['x-od-workspace-id'], status: response.status(), runId: body.runId });
|
|
});
|
|
await page.goto(suite.url.web('/'), { waitUntil: 'domcontentloaded' });
|
|
const input = page.getByTestId('home-hero-input');
|
|
await expect(input).toBeVisible({ timeout: T.xlong });
|
|
const runs: Array<{ runId: string; projectId: string; terminal: string }> = [];
|
|
for (const turn of [1, 2, 3]) {
|
|
// Each native session makes a paid title ($0.01) and main ($0.99)
|
|
// response at unchanged $4/$20 prices. Both are accounted in the audit.
|
|
if (turn > 1) {
|
|
await page.goto(suite.url.web('/'), { waitUntil: 'domcontentloaded' });
|
|
await expect(input).toBeVisible({ timeout: T.long });
|
|
}
|
|
const challenge = `QA:${JSON.stringify({ id: `api001-gui-${randomUUID()}`, tokens: 1, inputTokens: 145000, outputTokens: 20500 })} Reply exactly OK.`;
|
|
await input.fill(challenge);
|
|
const accepted = page.waitForResponse((response) => response.request().method() === 'POST' && new URL(response.url()).pathname === '/api/runs', { timeout: T.long });
|
|
await page.getByTestId('home-hero-submit').click();
|
|
const response = await accepted;
|
|
expect(response.status()).toBe(202);
|
|
const body = await response.json() as { runId: string };
|
|
expect(body.runId).toBeTruthy();
|
|
let terminal = '';
|
|
let projectId = '';
|
|
await expect.poll(async () => {
|
|
const r = await fetch(suite.url.web(`/api/runs/${body.runId}`));
|
|
expect(r.status).toBe(200);
|
|
const state = await r.json() as { status: string; projectId: string };
|
|
terminal = state.status;
|
|
projectId = state.projectId;
|
|
return ['succeeded', 'failed', 'canceled', 'error'].includes(terminal);
|
|
}, { timeout: T.xlong }).toBe(true);
|
|
expect(terminal).toBe('succeeded');
|
|
expect(projectId).toBeTruthy();
|
|
runs.push({ runId: body.runId, projectId, terminal });
|
|
await walletEvidence(page, info, evidence, `api001-od-manual-turn-${turn}`);
|
|
}
|
|
expect(new Set(runs.map((run) => run.projectId)).size).toBe(3);
|
|
await writeFile(resolve(directory, 'api001-od-runs.private.json'), JSON.stringify({ runs }, null, 2), { mode: 0o600 });
|
|
runApiWalletAction(f, directory, 'audit');
|
|
await page.goto(suite.url.web('/'), { waitUntil: 'domcontentloaded' });
|
|
await expect(input).toBeVisible({ timeout: T.long });
|
|
await page.getByTestId('entry-top-right-credits').click();
|
|
const panel = page.getByTestId('entry-top-right-credits-panel');
|
|
await expect(panel).toBeVisible();
|
|
await expect(panel.getByTestId('workspace-quota-skeleton')).toHaveCount(0, { timeout: T.long });
|
|
await expect(panel.getByTestId('entry-nav-credits-row')).toContainText('$5.00', { timeout: T.long });
|
|
await walletEvidence(page, info, evidence, 'api001-od-owner-wallet-five');
|
|
await readConsoleWallets(browser, f, evidence, info);
|
|
await expect.poll(() => posts.length).toBe(3);
|
|
expect(posts).toEqual(runs.map((run) => ({ workspaceId: f.workspaceId, status: 202, runId: run.runId })));
|
|
const result = { qa: 'client:API-001', status: 'passed', tuple: f.tuple, runs, manualRuns: posts.length, apiCredits: '20000', clientCredits: '30000', finalCredits: '50000', sourceBoundary: 'Actual API Key and actual OD GUI/Vela/Link; only provider and initial ten-dollar/projection Given controlled' };
|
|
await writeFile(resolve(evidence, 'api001-browser.safe.json'), JSON.stringify(result, null, 2));
|
|
await info.attach('api001-browser-result', { body: JSON.stringify(result), contentType: 'application/json' });
|
|
succeeded = true;
|
|
} finally {
|
|
await page.context().close();
|
|
if (started) await suite.stopWeb(env).catch(() => undefined);
|
|
if (succeeded) await rm(root, { recursive: true, force: true }).catch(() => undefined);
|
|
}
|
|
});
|