mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
test(mobile): consolidate bridge roundtrip fixture
This commit is contained in:
@@ -1,19 +1,8 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
|
||||
it('round trips typed account reads, selection, and snapshots through the production bridge', async () => {
|
||||
let broker: MobileWebCapabilityBroker
|
||||
let hostListener: ((event: unknown) => void) | null = null
|
||||
const hostUnsubscribe = vi.fn()
|
||||
const sendRequest = vi.fn(async (method: string) => {
|
||||
@@ -31,46 +20,16 @@ it('round trips typed account reads, selection, and snapshots through the produc
|
||||
} as unknown as RpcClient
|
||||
const requestIds = ['A', 'B', 'C', 'D', 'E']
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
context: CONTEXT,
|
||||
const { client, pageMessages, shellMessages } = createMobileWebBridgeRoundtripFixture({
|
||||
grants: [accountGrant('snapshot'), accountGrant('select'), accountGrant('subscribe')],
|
||||
rpcClient,
|
||||
createRequestId: () => requestIds[requestIndex++]!.repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
},
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
navigationAuthority: {
|
||||
route: vi.fn(),
|
||||
reconnect: vi.fn(),
|
||||
removeHost: vi.fn(),
|
||||
consumeRecentUserGesture: () => true
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(1)
|
||||
}
|
||||
})
|
||||
|
||||
await expect(client.account.snapshot()).resolves.toMatchObject({
|
||||
@@ -79,6 +38,17 @@ it('round trips typed account reads, selection, and snapshots through the produc
|
||||
activeAccountId: 'claude-1'
|
||||
}
|
||||
})
|
||||
expect(pageMessages[0]).toMatchObject({
|
||||
type: 'request',
|
||||
requestId: 'A'.repeat(22),
|
||||
capability: 'account',
|
||||
operation: 'snapshot'
|
||||
})
|
||||
expect(shellMessages[0]).toMatchObject({
|
||||
type: 'response',
|
||||
requestId: 'A'.repeat(22),
|
||||
status: 'success'
|
||||
})
|
||||
await expect(
|
||||
client.account.select({ provider: 'codex', accountId: 'codex-1' })
|
||||
).resolves.toBeNull()
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
|
||||
it('round trips opaque agent history and gesture-gates resume through the production bridge', async () => {
|
||||
let broker: MobileWebCapabilityBroker
|
||||
const sendRequest = vi.fn(async (method: string) => {
|
||||
if (method === 'status.get') {
|
||||
return {
|
||||
@@ -35,49 +24,20 @@ it('round trips opaque agent history and gesture-gates resume through the produc
|
||||
const rpcClient = { sendRequest } as unknown as RpcClient
|
||||
const requestIds = ['A', 'B', 'C']
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
context: CONTEXT,
|
||||
const { broker, client } = createMobileWebBridgeRoundtripFixture({
|
||||
grants: [
|
||||
agentHistoryGrant('snapshot'),
|
||||
agentHistoryGrant('preview'),
|
||||
agentHistoryGrant('resume')
|
||||
],
|
||||
rpcClient,
|
||||
createRequestId: () => requestIds[requestIndex++]!.repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
},
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
navigationAuthority: {
|
||||
route: vi.fn(),
|
||||
reconnect: vi.fn(),
|
||||
removeHost: vi.fn(),
|
||||
consumeRecentUserGesture: () => false
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(5)
|
||||
})
|
||||
const route = await broker.resolveNavigationRoute('host-workspace')
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { onTestFinished } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage,
|
||||
type MobileWebBridgeMessageContext,
|
||||
type MobileWebBridgePageMessage,
|
||||
type MobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import type { MobileWebNativeCapabilityAuthority } from './mobile-web-native-capability-authority'
|
||||
import type { MobileWebNavigationAuthority } from './mobile-web-navigation-operations'
|
||||
|
||||
type InitMessage = Extract<MobileWebBridgeShellMessage, { type: 'init' }>
|
||||
|
||||
export const MOBILE_WEB_BRIDGE_ROUNDTRIP_CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
|
||||
export function createMobileWebBridgeRoundtripFixture(options: {
|
||||
grants: InitMessage['grants']
|
||||
rpcClient?: RpcClient | null
|
||||
context?: MobileWebBridgeMessageContext
|
||||
createRequestId?: () => string
|
||||
nativeAuthority?: Partial<MobileWebNativeCapabilityAuthority>
|
||||
navigationAuthority?: MobileWebNavigationAuthority
|
||||
isActive?: () => boolean
|
||||
isConnected?: () => boolean
|
||||
terminalClientId?: string
|
||||
randomBytes?: (length: number) => Uint8Array
|
||||
}) {
|
||||
const context = options.context ?? MOBILE_WEB_BRIDGE_ROUNDTRIP_CONTEXT
|
||||
const pageMessages: MobileWebBridgePageMessage[] = []
|
||||
const shellMessages: MobileWebBridgeShellMessage[] = []
|
||||
let broker: MobileWebCapabilityBroker
|
||||
const client = new MobileWebBridgeClient({
|
||||
context,
|
||||
grants: options.grants,
|
||||
createRequestId: options.createRequestId,
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), context)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
pageMessages.push(parsed.value)
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context,
|
||||
getClient: () => options.rpcClient ?? null,
|
||||
isConnected: options.isConnected ?? (() => options.rpcClient != null),
|
||||
isActive: options.isActive ?? (() => true),
|
||||
nativeAuthority: { ...defaultNativeAuthority(), ...options.nativeAuthority },
|
||||
navigationAuthority: options.navigationAuthority,
|
||||
terminalClientId: options.terminalClientId ?? 'roundtrip-device',
|
||||
randomBytes: options.randomBytes ?? ((length) => new Uint8Array(length).fill(1)),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), context)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
shellMessages.push(parsed.value)
|
||||
client.receive(parsed.value)
|
||||
}
|
||||
})
|
||||
const dispose = () => {
|
||||
client.dispose()
|
||||
broker.dispose()
|
||||
}
|
||||
onTestFinished(dispose)
|
||||
return { broker, client, dispose, pageMessages, shellMessages }
|
||||
}
|
||||
|
||||
function defaultNativeAuthority(): MobileWebNativeCapabilityAuthority {
|
||||
return {
|
||||
hapticFeedback: () => {},
|
||||
clipboardAvailability: async () => ({ hasText: false, hasImage: false }),
|
||||
clipboardWrite: async () => ({ confirmation: 'in-app' }),
|
||||
openExternal: async () => {},
|
||||
terminalPreferences: async () => ({
|
||||
textScale: 1,
|
||||
autocompleteEnabled: true,
|
||||
linkOpenMode: 'phone-browser'
|
||||
}),
|
||||
terminalTextScaleUpdate: async () => {}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
MOBILE_WEB_BRIDGE_PROTOCOL_VERSION,
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import {
|
||||
MOBILE_WEB_PRODUCTION_GRANTS,
|
||||
MobileWebCapabilityBroker
|
||||
} from './mobile-web-capability-broker'
|
||||
import { MOBILE_WEB_PRODUCTION_GRANTS } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
@@ -149,7 +145,6 @@ describe('mobile web bridge round trip', () => {
|
||||
.mockResolvedValueOnce({ ok: true, result: { browserPageId: 'browser-2' } })
|
||||
.mockResolvedValueOnce({ ok: true, result: { closed: true } })
|
||||
.mockResolvedValueOnce({ ok: true, result: { opened: true } })
|
||||
let broker: MobileWebCapabilityBroker
|
||||
let hostSubscriptionListener: ((event: unknown) => void) | undefined
|
||||
const hostUnsubscribe = vi.fn()
|
||||
const subscribe = vi
|
||||
@@ -160,41 +155,13 @@ describe('mobile web bridge round trip', () => {
|
||||
})
|
||||
const requestIds = ['R', 'Q', 'T', 'U', 'V', 'W', 'X', 'Y', 'A', 'B']
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
const rpcClient = { sendRequest, subscribe } as unknown as RpcClient
|
||||
const { client } = createMobileWebBridgeRoundtripFixture({
|
||||
context: CONTEXT,
|
||||
grants: [...MOBILE_WEB_PRODUCTION_GRANTS],
|
||||
rpcClient,
|
||||
createRequestId: () => (requestIds[requestIndex++] ?? 'Z').repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
const rpcClient = { sendRequest, subscribe } as unknown as RpcClient
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(1),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
}
|
||||
terminalClientId: 'device-token'
|
||||
})
|
||||
|
||||
const firstWorkspacePage = await client.workspaceSnapshot({ limit: 1 })
|
||||
@@ -422,8 +389,5 @@ describe('mobile web bridge round trip', () => {
|
||||
expect(liveErrors).toEqual([])
|
||||
subscription.unsubscribe()
|
||||
expect(hostUnsubscribe).toHaveBeenCalledOnce()
|
||||
|
||||
client.dispose()
|
||||
broker.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import { MOBILE_WEB_PRODUCTION_NATIVE_GRANTS } from './mobile-web-production-native-grants'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
|
||||
describe('mobile web native capability round trip', () => {
|
||||
it('keeps device effects in the shell behind typed grants and gestures', async () => {
|
||||
const hapticFeedback = vi.fn()
|
||||
@@ -30,26 +20,11 @@ describe('mobile web native capability round trip', () => {
|
||||
const terminalCustomKeysUpdate = vi.fn().mockResolvedValue(undefined)
|
||||
const terminalTextScaleUpdate = vi.fn().mockResolvedValue(undefined)
|
||||
const consumeRecentUserGesture = vi.fn(() => true)
|
||||
let broker: MobileWebCapabilityBroker
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
context: CONTEXT,
|
||||
const { client } = createMobileWebBridgeRoundtripFixture({
|
||||
grants: [...MOBILE_WEB_PRODUCTION_NATIVE_GRANTS],
|
||||
createRequestId: () => String.fromCharCode(65 + requestIndex++).repeat(22),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => null,
|
||||
isConnected: () => false,
|
||||
isActive: () => true,
|
||||
nativeAuthority: {
|
||||
hapticFeedback,
|
||||
clipboardWrite,
|
||||
@@ -64,15 +39,6 @@ describe('mobile web native capability round trip', () => {
|
||||
reconnect: vi.fn(),
|
||||
removeHost: vi.fn(),
|
||||
consumeRecentUserGesture
|
||||
},
|
||||
terminalClientId: 'native-only-device',
|
||||
randomBytes: (length) => new Uint8Array(length),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -108,7 +74,5 @@ describe('mobile web native capability round trip', () => {
|
||||
{ id: 'custom-2', label: 'Test', bytes: 'pnpm test\r', enter: false }
|
||||
])
|
||||
expect(consumeRecentUserGesture).toHaveBeenCalledTimes(4)
|
||||
client.dispose()
|
||||
broker.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,58 +1,23 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import { MOBILE_WEB_PRODUCTION_NAVIGATION_GRANTS } from './mobile-web-production-navigation-grants'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
|
||||
describe('mobile web navigation round trip', () => {
|
||||
it('carries named shell intent without host identity', async () => {
|
||||
const route = vi.fn()
|
||||
const reconnect = vi.fn()
|
||||
const removeHost = vi.fn()
|
||||
const consumeRecentUserGesture = vi.fn(() => true)
|
||||
let broker: MobileWebCapabilityBroker
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
context: CONTEXT,
|
||||
const { client } = createMobileWebBridgeRoundtripFixture({
|
||||
grants: [...MOBILE_WEB_PRODUCTION_NAVIGATION_GRANTS],
|
||||
createRequestId: () => String.fromCharCode(82 + requestIndex++).repeat(22),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => null,
|
||||
isConnected: () => false,
|
||||
isActive: () => true,
|
||||
nativeAuthority: nativeAuthority(),
|
||||
navigationAuthority: {
|
||||
route,
|
||||
reconnect,
|
||||
removeHost,
|
||||
consumeRecentUserGesture
|
||||
},
|
||||
terminalClientId: 'native-only-device',
|
||||
randomBytes: (length) => new Uint8Array(length),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -68,17 +33,5 @@ describe('mobile web navigation round trip', () => {
|
||||
expect(reconnect).toHaveBeenCalledWith()
|
||||
expect(removeHost).toHaveBeenCalledWith()
|
||||
expect(consumeRecentUserGesture).toHaveBeenCalledTimes(3)
|
||||
client.dispose()
|
||||
broker.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
function nativeAuthority() {
|
||||
return {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import { webHostTaskItemFileOperations } from '../tasks/web-host-task-item-file-operations'
|
||||
import { webHostTaskItemMutationOperations } from '../tasks/web-host-task-item-mutation-operations'
|
||||
import { webHostTaskItemReviewOperations } from '../tasks/web-host-task-item-review-operations'
|
||||
@@ -12,7 +7,7 @@ import { webHostTaskProviderWriteOperations } from '../tasks/web-host-task-provi
|
||||
import { webHostTaskListOperations } from '../tasks/web-host-task-list-operations'
|
||||
import { webHostTaskReadOperations } from '../tasks/web-host-task-read-operations'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import { taskRoundtripHostResponse } from './mobile-web-task-roundtrip-host-fixtures'
|
||||
|
||||
const CONTEXT = {
|
||||
@@ -21,11 +16,10 @@ const CONTEXT = {
|
||||
}
|
||||
|
||||
it('revalidates opaque hosted task targets before provider writes', async () => {
|
||||
let broker: MobileWebCapabilityBroker
|
||||
const sendRequest = vi.fn(async (method: string) => taskRoundtripHostResponse(method))
|
||||
const rpcClient = { sendRequest } as unknown as RpcClient
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
const { broker, client } = createMobileWebBridgeRoundtripFixture({
|
||||
context: CONTEXT,
|
||||
grants: [
|
||||
taskGrant('repositories'),
|
||||
@@ -56,36 +50,9 @@ it('revalidates opaque hosted task targets before provider writes', async () =>
|
||||
taskGrant('createProviderIssue'),
|
||||
taskGrant('updateIssueSource')
|
||||
],
|
||||
rpcClient,
|
||||
createRequestId: () =>
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'[requestIndex++]!.repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
},
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(4)
|
||||
})
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import { webHostTaskProjectFileOperations } from '../tasks/web-host-task-project-file-operations'
|
||||
import { webHostTaskProjectMutationOperations } from '../tasks/web-host-task-project-mutation-operations'
|
||||
import { webHostTaskProjectReadOperations } from '../tasks/web-host-task-project-read-operations'
|
||||
import { webHostTaskReadOperations } from '../tasks/web-host-task-read-operations'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import { taskRoundtripHostResponse } from './mobile-web-task-roundtrip-host-fixtures'
|
||||
|
||||
const CONTEXT = {
|
||||
@@ -18,11 +13,10 @@ const CONTEXT = {
|
||||
}
|
||||
|
||||
it('revalidates opaque GitHub Project mutation targets before every write', async () => {
|
||||
let broker: MobileWebCapabilityBroker
|
||||
const sendRequest = vi.fn(async (method: string) => taskRoundtripHostResponse(method))
|
||||
const rpcClient = { sendRequest } as unknown as RpcClient
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
const { broker, client } = createMobileWebBridgeRoundtripFixture({
|
||||
context: CONTEXT,
|
||||
grants: [
|
||||
taskGrant('repositories'),
|
||||
@@ -45,35 +39,8 @@ it('revalidates opaque GitHub Project mutation targets before every write', asyn
|
||||
taskGrant('loadProjectFileContents'),
|
||||
taskGrant('addProjectInlineComment')
|
||||
],
|
||||
rpcClient,
|
||||
createRequestId: () => String.fromCharCode(65 + requestIndex++).repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
},
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(3)
|
||||
})
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import { webHostTaskPreferenceOperations } from '../tasks/web-host-task-preference-operations'
|
||||
import { webHostTaskDetailOperations } from '../tasks/web-host-task-detail-operations'
|
||||
import { webHostTaskProjectReadOperations } from '../tasks/web-host-task-project-read-operations'
|
||||
import { webHostTaskListOperations } from '../tasks/web-host-task-list-operations'
|
||||
import { webHostTaskReadOperations } from '../tasks/web-host-task-read-operations'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import {
|
||||
TASK_ROUNDTRIP_HOST_REPO_ID,
|
||||
taskRoundtripHostResponse
|
||||
@@ -21,11 +16,10 @@ const CONTEXT = {
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
it('round trips bounded task bootstrap reads through opaque repository authority', async () => {
|
||||
let broker: MobileWebCapabilityBroker
|
||||
const sendRequest = vi.fn(async (method: string) => taskRoundtripHostResponse(method))
|
||||
const rpcClient = { sendRequest } as unknown as RpcClient
|
||||
let requestIndex = 0
|
||||
const client = new MobileWebBridgeClient({
|
||||
const { broker, client } = createMobileWebBridgeRoundtripFixture({
|
||||
context: CONTEXT,
|
||||
grants: [
|
||||
taskGrant('bootstrap'),
|
||||
@@ -53,37 +47,9 @@ it('round trips bounded task bootstrap reads through opaque repository authority
|
||||
taskGrant('projectItemAssignableUsers'),
|
||||
taskGrant('projectIssueTypes')
|
||||
],
|
||||
rpcClient,
|
||||
createRequestId: () => String.fromCharCode(65 + requestIndex++).repeat(22),
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => rpcClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
postMessage: (message) => {
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
client.receive(parsed.value)
|
||||
},
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
terminalClientId: 'device-token',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(1)
|
||||
terminalClientId: 'device-token'
|
||||
})
|
||||
const operations = webHostTaskReadOperations(client)
|
||||
const lists = webHostTaskListOperations(client)
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseMobileWebBridgePageMessage,
|
||||
parseMobileWebBridgeShellMessage,
|
||||
type MobileWebBridgeShellMessage
|
||||
} from '../../../src/shared/mobile-web/bridge-contract'
|
||||
import { MobileWebBridgeClient } from '../../../src/mobile-web/src/mobile-web-bridge-client'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { MOBILE_WORKTREE_CREATE_IDEMPOTENCY_CAPABILITY } from '../tasks/worktree-create-capability'
|
||||
import { MobileWebCapabilityBroker } from './mobile-web-capability-broker'
|
||||
import { createMobileWebBridgeRoundtripFixture } from './mobile-web-bridge-roundtrip-fixture'
|
||||
import { MOBILE_WEB_PRODUCTION_WORKSPACE_CREATION_GRANTS } from './mobile-web-production-workspace-creation-grants'
|
||||
|
||||
const CONTEXT = {
|
||||
shellSessionId: 'S'.repeat(43),
|
||||
buildId: 'a'.repeat(64)
|
||||
}
|
||||
|
||||
describe('mobile web workspace creation round trip', () => {
|
||||
it('carries page requests through schemas and resolves host authority only in native', async () => {
|
||||
const shellMessages: MobileWebBridgeShellMessage[] = []
|
||||
const consumeRecentUserGesture = vi.fn(() => true)
|
||||
const sendRequest = vi.fn(async (method: string) => {
|
||||
if (method === 'repo.list') {
|
||||
@@ -47,48 +35,17 @@ describe('mobile web workspace creation round trip', () => {
|
||||
throw new Error(`Unexpected method ${method}`)
|
||||
})
|
||||
const hostClient = { sendRequest } as unknown as RpcClient
|
||||
let broker: MobileWebCapabilityBroker
|
||||
let requestIndex = 0
|
||||
const pageClient = new MobileWebBridgeClient({
|
||||
context: CONTEXT,
|
||||
const { client: pageClient, shellMessages } = createMobileWebBridgeRoundtripFixture({
|
||||
grants: [...MOBILE_WEB_PRODUCTION_WORKSPACE_CREATION_GRANTS],
|
||||
rpcClient: hostClient,
|
||||
createRequestId: () => String.fromCharCode(65 + requestIndex++).repeat(22),
|
||||
postMessage(message) {
|
||||
const parsed = parseMobileWebBridgePageMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
return false
|
||||
}
|
||||
void broker.handle(parsed.value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
broker = new MobileWebCapabilityBroker({
|
||||
context: CONTEXT,
|
||||
getClient: () => hostClient,
|
||||
isConnected: () => true,
|
||||
isActive: () => true,
|
||||
nativeAuthority: {
|
||||
hapticFeedback: vi.fn(),
|
||||
clipboardWrite: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
terminalPreferences: vi.fn(),
|
||||
terminalTextScaleUpdate: vi.fn()
|
||||
},
|
||||
terminalClientId: 'native-device-secret',
|
||||
randomBytes: (length) => new Uint8Array(length).fill(5),
|
||||
navigationAuthority: {
|
||||
route: vi.fn(),
|
||||
reconnect: vi.fn(),
|
||||
removeHost: vi.fn(),
|
||||
consumeRecentUserGesture
|
||||
},
|
||||
postMessage(message) {
|
||||
shellMessages.push(message)
|
||||
const parsed = parseMobileWebBridgeShellMessage(JSON.stringify(message), CONTEXT)
|
||||
if (!parsed.ok) {
|
||||
throw new Error(parsed.error)
|
||||
}
|
||||
pageClient.receive(parsed.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -132,8 +89,5 @@ describe('mobile web workspace creation round trip', () => {
|
||||
expect(createParams).not.toHaveProperty('startupCommand')
|
||||
expect(sendRequest).not.toHaveBeenCalledWith('settings.get')
|
||||
expect(JSON.stringify(shellMessages)).not.toMatch(/repo-secret|worktree-secret|ssh-private-id/)
|
||||
|
||||
pageClient.dispose()
|
||||
broker.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user