import { expect, test } from '@playwright/test'; const site = 'https://cadworld-sim.robotquan.com'; const approvedParent = 'https://cadworld.robotquan.com'; test.skip(process.env.CADWORLD_PRODUCTION_TEST !== '1', '真实站点非付费检查须显式启用'); test('生产嵌入响应头:仅允许自身与指定同事域名,API 仍拒绝父页面来源', async ({ request }) => { const response = await request.get(site); expect(response.status()).toBe(200); const headers = response.headers(); expect(headers['x-frame-options']).toBeUndefined(); expect(headers['content-security-policy']).toContain(`frame-ancestors 'self' ${approvedParent};`); expect(headers['content-security-policy']).toContain("object-src 'none'"); expect(headers['x-content-type-options']).toBe('nosniff'); expect(headers['strict-transport-security']).toContain('max-age=31536000'); const forbidden = await request.post(`${site}/api/decision/v1/session`, { headers: { Origin: approvedParent, 'Sec-Fetch-Site': 'same-site' }, data: {}, }); expect(forbidden.status()).toBe(403); expect((await forbidden.json()).error).toBe('origin_forbidden'); expect(forbidden.headers()['access-control-allow-origin']).toBeUndefined(); }); test('生产嵌入:允许来源真实加载应用、Strict Cookie 会话和 CSRF 正常,无推理', async ({ page, context, }, info) => { let inference = 0; await page.route('**/api/decision/v1/**', async (route) => { if (/\/(command|plan|decide|test)$/.test(new URL(route.request().url()).pathname)) { inference++; await route.abort(); } else await route.continue(); }); // Only the parent HTML is synthetic. The iframe, assets and session API are live. const parentUrl = `${approvedParent}/__cadworld_embed_acceptance__`; await page.route(parentUrl, (route) => route.fulfill({ contentType: 'text/html', body: ``, }), ); await page.goto(parentUrl); const frame = page.frameLocator('iframe'); await frame.getByRole('tab', { name: '控制台', exact: true }).click(); await frame.getByRole('button', { name: '机器人语言控制', exact: true }).click(); await expect(frame.getByRole('textbox', { name: '机器人指令' })).toBeEditable(); const embedded = page.frames().find((f) => f.url() === `${site}/`); expect(embedded).toBeTruthy(); const sessionResult = await embedded!.evaluate(async () => { const prefix = '/api/decision/v1'; const bootstrap = await fetch(prefix + '/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}', }); const session = await bootstrap.json(); const status = await fetch(prefix + '/status'); const csrfGuard = await fetch(prefix + '/cancel', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}', }); const destroyed = await fetch(prefix + '/session', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': session.csrfToken }, body: '{}', }); const expired = await fetch(prefix + '/status'); return { bootstrap: bootstrap.status, ready: session.ready, status: status.status, csrfGuard: csrfGuard.status, destroyed: destroyed.status, expired: expired.status, }; }); expect(sessionResult).toEqual({ bootstrap: 200, ready: true, status: 200, csrfGuard: 403, destroyed: 200, expired: 401, }); expect( (await context.cookies(site)).filter((c) => c.name.includes('session')).map((c) => c.name), ).toHaveLength(0); expect(inference).toBe(0); await page.screenshot({ path: info.outputPath('approved-iframe.png') }); }); test('生产嵌入:未授权同站子域名仍被 CSP 拒绝', async ({ page }) => { const violations: string[] = []; page.on('console', (message) => { if (message.type() === 'error') violations.push(message.text()); }); const parentUrl = 'https://unapproved.robotquan.com/__cadworld_embed_acceptance__'; await page.route(parentUrl, (route) => route.fulfill({ contentType: 'text/html', body: ``, }), ); await page.goto(parentUrl); await expect.poll(() => violations.some((text) => text.includes('frame-ancestors'))).toBe(true); await expect( page.frameLocator('iframe').getByRole('tab', { name: '控制台', exact: true }), ).toHaveCount(0); });