瀏覽代碼

test: 收口远程终端V2最终验收

main
leiyun 3 週之前
父節點
當前提交
2c67faee71
共有 2 個文件被更改,包括 38 次插入13 次删除
  1. +6
    -0
      tests/remote-terminal-v2-lifecycle.spec.ts
  2. +32
    -13
      tests/remote-terminal-v2-ui.spec.ts

+ 6
- 0
tests/remote-terminal-v2-lifecycle.spec.ts 查看文件

@@ -493,6 +493,7 @@ async function directRuntimeSessionFence(
{ message: '旧 owner 会话必须被服务端拒绝' },
ownerToken,
)
socket.send(JSON.stringify({ type: 'ping' }))
const oldOwnerWebSocketCloseCode = await socketClosed

const contenderSession = await post(sessionPath, contenderInstance, {})
@@ -1171,6 +1172,7 @@ test.describe('远程终端 V2 真实双浏览器生命周期', () => {
}
}
primaryRuntime.page.on('response', restartResponseObserver)
const releasesBeforeRestart = primaryRuntime.transport.releaseRequests()
const heartbeatAfterRestart = primaryRuntime.page.waitForResponse((response) => response.request().method() === 'POST'
&& new URL(response.url()).pathname === '/open/v2/terminals/heartbeat'
&& response.status() === 200, { timeout: 45_000 })
@@ -1184,6 +1186,10 @@ test.describe('远程终端 V2 真实双浏览器生命周期', () => {
const restart = await parseCommandResponse(restartResponse, '下发 RESTART 命令')
commandIds.restart = restart.id
await waitCommand(api, headers, restart.id, 'SUCCEEDED', 60_000)
await expect.poll(primaryRuntime.transport.releaseRequests, {
message: 'RESTART 刷新前必须立即释放旧 runtime lease,不能等待租约自然过期',
timeout: 10_000,
}).toBeGreaterThan(releasesBeforeRestart)
responseStatus(await heartbeatAfterRestart, 200, 'RESTART 后新页面运行实例重新取得租约')
await expect.poll(() => primaryRuntime!.terminalFollowupRequests().end, {
message: 'RESTART 必须调用受管会话 end',


+ 32
- 13
tests/remote-terminal-v2-ui.spec.ts 查看文件

@@ -422,6 +422,7 @@ test.describe('远程终端 V2 受控页面回归', () => {
let pullCount = 0
let sessionCreates = 0
let terminalConfigRequests = 0
let terminalConfigFailures = 0
let activationRequests = 0
let activationRequestValid = true
let legacyPublicConfigRequests = 0
@@ -587,6 +588,10 @@ test.describe('远程终端 V2 受控页面回归', () => {
if (pathname === `/open/v2/terminals/agents/${publishedAgent.slug}/config` && request.method() === 'GET') {
terminalConfigRequests += 1
terminalConfigHeadersValid = terminalConfigHeadersValid && configHeadersValid
if (terminalConfigFailures === 0) {
terminalConfigFailures += 1
return fulfill(route, {}, 503, { 'Cache-Control': 'no-store' })
}
return fulfill(route, {
terminal: { id: 'rt-reliability', displayMode: 'WIDGET' },
agent: {
@@ -756,7 +761,8 @@ test.describe('远程终端 V2 受控页面回归', () => {
await expect(widget.locator('.embed-widget')).toHaveClass(/is-expanded/, { timeout: 20_000 })

expect(sessionCreates, 'WAKE 页面业务动作必须只执行一次').toBe(1)
expect(terminalConfigRequests, '受管终端必须通过设备凭据读取一次智能体配置').toBe(1)
expect(terminalConfigFailures, '受管配置首次临时失败必须被测试覆盖').toBe(1)
expect(terminalConfigRequests, '受管终端配置首次 503 后必须自动重试成功').toBe(2)
expect(terminalConfigHeadersValid, '终端配置请求必须携带设备头和页面 Origin').toBe(true)
expect(terminalSessionHeadersValid, '终端会话请求必须携带设备头和页面 Origin').toBe(true)
expect(legacyPublicConfigRequests, '带 terminal 参数时不得调用旧公开配置接口').toBe(0)
@@ -805,7 +811,11 @@ test.describe('远程终端 V2 受控页面回归', () => {
expect(sessionCreates, 'STANDBY 页面不得签发 scoped session').toBe(sessionsBeforeStandby)
expect(terminalChatRequests, 'STANDBY 页面不得发送 scoped chat').toBe(chatsBeforeStandby)
expect(runtimeTransport.heartbeatRequests(), '浏览器非 leader 不得竞争 heartbeat').toBe(heartbeatBeforeStandby)
expect(runtimeTransport.pullRequests(), '浏览器非 leader 不得启动 pull').toBe(pullsBeforeStandby)
expect(
[...new Set(runtimeTransport.endpointInstances().pull)],
'浏览器非 leader 不得以第二个 runtime instance 启动 pull',
).toEqual([leaderRuntimeInstanceId])
expect(runtimeTransport.pullRequests(), '主 leader 在校验期间应继续正常长轮询').toBeGreaterThanOrEqual(pullsBeforeStandby)
standbyInteractionBlocked = true
await page.locator('#terminal-widget-standby').evaluate((element) => element.remove())

@@ -832,6 +842,16 @@ test.describe('远程终端 V2 受控页面回归', () => {
const configRequestsBeforeMismatch = terminalConfigRequests
const heartbeatRequestsBeforeMismatch = runtimeTransport.heartbeatRequests()
const activationRequestsBeforeMismatch = activationRequests
await page.addInitScript(({ appOrigin, hostOrigin }) => {
if (window.location.origin !== appOrigin || !document.referrer.startsWith(hostOrigin)) return
window.localStorage.setItem('ai-person:remote-terminal-runtime:v2:RTC-RELIABILITY-01', JSON.stringify({
terminalId: 'RTC-RELIABILITY-01',
terminalCode: 'RTC-RELIABILITY-01',
secret: 'controlled-device-secret',
deviceId: 'controlled-device-id',
pageOrigin: appOrigin,
}))
}, { appOrigin: controlledAppOrigin, hostOrigin: mismatchHostOrigin })
await page.goto(new URL(mismatchHostPath, mismatchHostOrigin).toString())
const mismatchWidget = page.frameLocator('#terminal-widget-mismatch')
await expect(mismatchWidget.locator('.embed-widget')).toBeVisible({ timeout: 20_000 })
@@ -851,13 +871,17 @@ test.describe('远程终端 V2 受控页面回归', () => {
Boolean(window.localStorage.getItem('ai-person:remote-terminal-runtime:v2:RTC-RELIABILITY-01'))
))
expect(originMismatchCredentialPreserved, '来源不匹配只拒绝本次连接,不得删除合法宿主激活的设备凭据').toBe(true)

const heartbeatSuccessesBeforeTrustedReturn = runtimeTransport.heartbeatSuccesses()
// 此处只验证 Origin fail-closed 与凭据保留;受控路由不模拟真实时间,
// 手动推进到服务端租约已到期,正常卸载/release 由真实生命周期用例覆盖。
leaderRuntimeInstanceId = ''

const heartbeatAfterTrustedReturn = page.waitForResponse((response) => (
response.request().method() === 'POST'
&& new URL(response.url()).pathname === '/open/v2/terminals/heartbeat'
&& response.status() === 200
), { timeout: 20_000 })
await page.goto(hostPath)
await expect.poll(runtimeTransport.heartbeatSuccesses, {
message: '返回合法宿主后必须复用原设备凭据重新取得 runtime lease',
timeout: 20_000,
}).toBeGreaterThan(heartbeatSuccessesBeforeTrustedReturn)
await heartbeatAfterTrustedReturn
await expect.poll(() => terminalConfigRequests, {
message: '返回合法宿主后必须重新读取 scoped config',
timeout: 20_000,
@@ -869,17 +893,12 @@ test.describe('远程终端 V2 受控页面回归', () => {
expect(activationRequests, '来源切换过程不得重复兑换一次性激活码').toBe(activationRequestsBeforeMismatch)

const releasesBeforeLeavingHost = runtimeTransport.releaseRequests()
const releaseResponsesBeforeLeavingHost = releaseResponses
ordinaryPublicPhase = true
await page.goto(`/live/${publishedAgent.slug}`)
await expect.poll(runtimeTransport.releaseRequests, {
message: '离开受管 WIDGET 时必须 best-effort 释放服务端运行租约',
timeout: 15_000,
}).toBeGreaterThan(releasesBeforeLeavingHost)
await expect.poll(() => releaseResponses, {
message: '受控 release mock 必须实际处理卸载请求',
timeout: 15_000,
}).toBeGreaterThan(releaseResponsesBeforeLeavingHost)
expect(runtimeTransport.releaseBodyEmpty(), 'release 必须是无 body 的 keepalive POST').toBe(true)
expect(runtimeTransport.headersValid(), 'release 必须复用同页运行实例和三项设备头且无旧头').toBe(true)
await expect(page.locator('.live-agent-page')).toBeVisible()


Loading…
取消
儲存