mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
test(liveshare-protocol): gate the package at per-file coverage
The protocol package leaves the repository-wide coverage exemption and meets the same per-file 100% gate as the other packages. The new specs cover the identity mock, the wire helpers and every additive-field parser branch, both public entry points, and the carrier's forwarded-route paths; the key parser drops an unreachable guard so its remaining branches are exercised.
This commit is contained in:
1 parent
abb9d0c50f
commit
535c40d4b7
6 files changed
+440
-10
No files matched your search
@@ -27,12 +27,8 @@ export function identityKeyOf(identity: IdentityTriple): string {
|
||||
export function identityFromKey(key: string): IdentityTriple | undefined {
|
||||
const parts = key.split(':')
|
||||
if (parts.length !== 3) return undefined
|
||||
const [providerPart, tenantPart, subjectPart] = parts
|
||||
if (providerPart === undefined || tenantPart === undefined || subjectPart === undefined) return undefined
|
||||
try {
|
||||
const providerId = decodeURIComponent(providerPart)
|
||||
const tenantId = decodeURIComponent(tenantPart)
|
||||
const subjectId = decodeURIComponent(subjectPart)
|
||||
const [providerId = '', tenantId = '', subjectId = ''] = parts.map(part => decodeURIComponent(part))
|
||||
if (providerId === '' || tenantId === '' || subjectId === '') return undefined
|
||||
return { providerId, tenantId, subjectId }
|
||||
} catch {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/** The package's public entry points re-export the one shared contract. */
|
||||
|
||||
import { expect, it } from 'vitest'
|
||||
import * as identity from '@deepseek-ai/dsh-experimental-liveshare-protocol/identity'
|
||||
import * as protocol from '@deepseek-ai/dsh-experimental-liveshare-protocol/protocol'
|
||||
import * as root from '@deepseek-ai/dsh-experimental-liveshare-protocol'
|
||||
|
||||
it('re-exports the protocol and identity contracts from the package root', () => {
|
||||
expect(root.LIVESHARE_PROTOCOL_VERSION).toBe(protocol.LIVESHARE_PROTOCOL_VERSION)
|
||||
expect(root.parseShareAccessState).toBe(protocol.parseShareAccessState)
|
||||
expect(root.identityKeyOf).toBe(identity.identityKeyOf)
|
||||
expect(root.MockIdentityProvider).toBe(identity.MockIdentityProvider)
|
||||
expect(root.viewerCarrierScript).toBeTypeOf('function')
|
||||
})
|
||||
|
||||
it('re-exports the identity contract from its own entry', () => {
|
||||
expect(identity.identityKeyOf({ providerId: 'p', tenantId: 't', subjectId: 's' })).toBe('p:t:s')
|
||||
expect(identity.identityFromKey('p:t:s')).toEqual({ providerId: 'p', tenantId: 't', subjectId: 's' })
|
||||
expect(identity.identityFromKey('p:t')).toBeUndefined()
|
||||
expect(identity.identityFromKey('p::s')).toBeUndefined()
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
/** The deterministic identity provider tests and loopback demos stand on. */
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
MockIdentityProvider,
|
||||
type MockIdentityPersona,
|
||||
} from '@deepseek-ai/dsh-experimental-liveshare-protocol/identity'
|
||||
|
||||
/** The personas one provider is built from; the array stays writable so a test can empty it. */
|
||||
function personas(): MockIdentityPersona[] {
|
||||
return [
|
||||
{ id: 'alice', tenantId: 'tenant-a', displayName: 'Alice', avatarUrl: 'https://avatar.example/alice.png' },
|
||||
{ id: 'bob' },
|
||||
]
|
||||
}
|
||||
|
||||
describe('mock identity provider', () => {
|
||||
it('refuses a provider that names no id or carries no persona', () => {
|
||||
expect(() => new MockIdentityProvider('', personas())).toThrow('mock identity provider id must not be empty')
|
||||
expect(() => new MockIdentityProvider('mock', [])).toThrow('mock identity provider requires at least one persona')
|
||||
expect(new MockIdentityProvider('mock', personas()).scopes).toEqual([])
|
||||
})
|
||||
|
||||
it('redirects to the callback with the selected persona code and the caller state', () => {
|
||||
const provider = new MockIdentityProvider('mock', personas())
|
||||
const url = provider.authorizationUrl({
|
||||
redirectUri: 'https://relay.example/__liveshare/auth/callback?existing=1',
|
||||
state: 'state-7',
|
||||
scopes: [],
|
||||
loginHint: 'bob',
|
||||
})
|
||||
expect(url.origin + url.pathname).toBe('https://relay.example/__liveshare/auth/callback')
|
||||
expect(Object.fromEntries(url.searchParams)).toEqual({ existing: '1', code: 'bob', state: 'state-7' })
|
||||
expect(provider.authorizationUrl({
|
||||
redirectUri: 'https://relay.example/callback', state: 'state-8', scopes: [],
|
||||
}).searchParams.get('code')).toBe('alice')
|
||||
})
|
||||
|
||||
it('refuses a redirect when no persona is selectable', () => {
|
||||
const list = personas()
|
||||
const provider = new MockIdentityProvider('mock', list)
|
||||
list.length = 0
|
||||
expect(() => provider.authorizationUrl({ redirectUri: 'https://relay.example/callback', state: 's', scopes: [] }))
|
||||
.toThrow('mock identity provider has no selectable persona')
|
||||
})
|
||||
|
||||
it('resolves a persona into a normalized actor, keeping what the provider issued', async () => {
|
||||
const provider = new MockIdentityProvider('mock', personas())
|
||||
await expect(provider.exchange({ code: 'alice', redirectUri: 'https://relay.example/callback' })).resolves.toEqual({
|
||||
providerId: 'mock',
|
||||
tenantId: 'tenant-a',
|
||||
subjectId: 'alice',
|
||||
displayName: 'Alice',
|
||||
avatarUrl: 'https://avatar.example/alice.png',
|
||||
})
|
||||
// A persona with no tenant, no display name, and no avatar receives the documented defaults.
|
||||
await expect(provider.exchange({ code: 'bob', redirectUri: 'https://relay.example/callback' })).resolves.toEqual({
|
||||
providerId: 'mock', tenantId: 'mock', subjectId: 'bob', displayName: 'bob',
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects an unknown callback code and an empty persona id', async () => {
|
||||
const provider = new MockIdentityProvider('mock', personas())
|
||||
await expect(provider.exchange({ code: 'nobody', redirectUri: 'https://relay.example/callback' })).rejects.toThrow('mock identity callback code is unknown')
|
||||
const empty = new MockIdentityProvider('mock', [{ id: '' }])
|
||||
await expect(empty.exchange({ code: '', redirectUri: 'https://relay.example/callback' })).rejects.toThrow('mock identity persona id must not be empty')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,302 @@
|
||||
/** Every wire helper and additive-field parser branch, exercised without a recorded frame. */
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
parseRelayFrame,
|
||||
parseShareAccessState,
|
||||
parseTunnelWorkerCommand,
|
||||
parseTunnelWorkerEvent,
|
||||
rawText,
|
||||
rejectedCommandShareId,
|
||||
sendFrame,
|
||||
unsupportedFrameRefusal,
|
||||
viewerRequestFields,
|
||||
} from '@deepseek-ai/dsh-experimental-liveshare-protocol/protocol'
|
||||
|
||||
/** One registration token; the parsers accept only this UUID-v4 shape. */
|
||||
const REGISTRATION = '11111111-1111-4111-8111-111111111111'
|
||||
/** One request token the IPC parsers require to be non-empty. */
|
||||
const TOKEN = 'request-token'
|
||||
|
||||
/**
|
||||
* A share spec the worker accepts as written.
|
||||
* @returns Every required field, with no optional field present.
|
||||
*/
|
||||
function spec(): Record<string, unknown> {
|
||||
return {
|
||||
shareId: 'share-one',
|
||||
sessionId: 'session-one',
|
||||
sessionTitle: 'Title',
|
||||
accessMode: 'selected',
|
||||
relay: 'wss://relay.example',
|
||||
gatewayToken: 'token-one',
|
||||
ownerDisplayName: 'Owner',
|
||||
allowedSubjectIds: ['viewer'],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap one share spec as the add-share command the host sends its worker.
|
||||
* @param share - Share spec to carry.
|
||||
* @returns The IPC command.
|
||||
*/
|
||||
function addShare(share: Record<string, unknown>): Record<string, unknown> {
|
||||
return { type: 'add-share', registrationToken: REGISTRATION, share }
|
||||
}
|
||||
|
||||
/**
|
||||
* A committed authority snapshot the parser accepts.
|
||||
* @returns Every required field, with the optional additions left out.
|
||||
*/
|
||||
function state(): Record<string, unknown> {
|
||||
return {
|
||||
type: 'share-access-state',
|
||||
version: 3,
|
||||
accessMode: 'selected',
|
||||
allowedSubjectIds: ['viewer'],
|
||||
sessionMode: 'view',
|
||||
}
|
||||
}
|
||||
|
||||
describe('wire helpers', () => {
|
||||
it('summarizes one opaque viewer request for logging', () => {
|
||||
expect(viewerRequestFields({ op: 'call', id: 7 })).toEqual({ op: 'call', streamId: '7' })
|
||||
expect(viewerRequestFields({ op: 4, id: 'seven' })).toEqual({ op: 'unknown', streamId: '-' })
|
||||
expect(viewerRequestFields({})).toEqual({ op: 'unknown', streamId: '-' })
|
||||
for (const body of [null, 'text', 3, undefined]) {
|
||||
expect(viewerRequestFields(body)).toEqual({ op: 'unknown', streamId: '-' })
|
||||
}
|
||||
})
|
||||
|
||||
it('sends one frame only on an open socket', () => {
|
||||
const sent: string[] = []
|
||||
sendFrame({ readyState: 1, send: (data: string) => { sent.push(data) } }, { type: 'share-rpc-cancel', id: 1 })
|
||||
sendFrame({ readyState: 3, send: (data: string) => { sent.push(data) } }, { type: 'share-rpc-cancel', id: 1 })
|
||||
expect(sent).toHaveLength(1)
|
||||
expect(JSON.parse(sent[0] ?? '')).toEqual({ type: 'share-rpc-cancel', id: 1 })
|
||||
})
|
||||
|
||||
it('decodes every representation ws hands the gateways', () => {
|
||||
expect(rawText(Buffer.from('a'))).toBe('a')
|
||||
expect(rawText(new TextEncoder().encode('bc').buffer)).toBe('bc')
|
||||
expect(rawText([Buffer.from('de'), Buffer.from('f')])).toBe('def')
|
||||
})
|
||||
|
||||
it('reads the share a rejected command names', () => {
|
||||
expect(rejectedCommandShareId({ shareId: 'direct' })).toBe('direct')
|
||||
expect(rejectedCommandShareId({ share: { shareId: 'nested' } })).toBe('nested')
|
||||
expect(rejectedCommandShareId({ share: { shareId: '' } })).toBeUndefined()
|
||||
expect(rejectedCommandShareId({ share: 'not-an-object' })).toBeUndefined()
|
||||
expect(rejectedCommandShareId({})).toBeUndefined()
|
||||
expect(rejectedCommandShareId(null)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Relay frame parsing', () => {
|
||||
it('parses the frames a gateway may receive', () => {
|
||||
expect(parseRelayFrame(JSON.stringify(state()))).toMatchObject({ type: 'share-access-state', version: 3 })
|
||||
expect(parseRelayFrame(JSON.stringify({
|
||||
type: 'share-rpc', id: 4, body: { op: 'call' }, actor: { providerId: 'p', tenantId: 't', subjectId: 's' },
|
||||
}))).toEqual({
|
||||
type: 'share-rpc', id: 4, body: { op: 'call' },
|
||||
actor: { providerId: 'p', tenantId: 't', subjectId: 's' },
|
||||
})
|
||||
expect(parseRelayFrame(JSON.stringify({
|
||||
type: 'share-rpc', id: 4, body: null,
|
||||
actor: { providerId: 'p', tenantId: 't', subjectId: 's', credential: 'signed' },
|
||||
}))).toMatchObject({ actor: { credential: 'signed' } })
|
||||
expect(parseRelayFrame(JSON.stringify({ type: 'share-rpc-cancel', id: 5 }))).toEqual({ type: 'share-rpc-cancel', id: 5 })
|
||||
})
|
||||
|
||||
it('drops frames that carry no serveable request', () => {
|
||||
expect(parseRelayFrame('not json')).toBeUndefined()
|
||||
expect(parseRelayFrame('null')).toBeUndefined()
|
||||
expect(parseRelayFrame('[]')).toBeUndefined()
|
||||
expect(parseRelayFrame(JSON.stringify({ type: 'share-rpc', body: {} }))).toBeUndefined()
|
||||
expect(parseRelayFrame(JSON.stringify({ type: 'share-rpc', id: 1, actor: { providerId: 'p', tenantId: 't', subjectId: 's' } })))
|
||||
.toBeUndefined()
|
||||
const valid = { type: 'share-rpc', id: 1, body: {} }
|
||||
for (const actor of [
|
||||
undefined,
|
||||
'actor',
|
||||
{ providerId: '', tenantId: 't', subjectId: 's' },
|
||||
{ providerId: 'p', tenantId: 't', subjectId: '' },
|
||||
{ providerId: 'p', tenantId: 't', subjectId: 's', credential: 3 },
|
||||
]) {
|
||||
expect(parseRelayFrame(JSON.stringify({ ...valid, actor }))).toBeUndefined()
|
||||
}
|
||||
expect(parseRelayFrame(JSON.stringify({ type: 'share-rpc-res', id: 1 }))).toBeUndefined()
|
||||
expect(parseRelayFrame(JSON.stringify({ type: 'future', id: 1 }))).toBeUndefined()
|
||||
})
|
||||
|
||||
it('answers only a request it could not serve', () => {
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'share-rpc-cancel', id: 1 }))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify(state()))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal('not json')).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'share-rpc', body: {} }))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'share-rpc', id: 1 }))).toEqual({
|
||||
type: 'share-rpc-res', id: 1, status: 400, body: { error: 'invalid-request' },
|
||||
})
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'share-rpc-res', id: 1 }))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'share-rpc-cancel', id: 1 }))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 'future-frame', id: 2 }))).toEqual({
|
||||
type: 'share-rpc-res', id: 2, status: 501, body: { error: 'unsupported-frame' },
|
||||
})
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: '', id: 3 }))).toBeUndefined()
|
||||
expect(unsupportedFrameRefusal(JSON.stringify({ type: 4, id: 5 }))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('tunnel worker commands', () => {
|
||||
it('parses the commands a worker serves', () => {
|
||||
expect(parseTunnelWorkerCommand({ type: 'shutdown' })).toEqual({ type: 'shutdown' })
|
||||
expect(parseTunnelWorkerCommand({ type: 'remove-share', shareId: 'share-one' })).toEqual({
|
||||
type: 'remove-share', shareId: 'share-one',
|
||||
})
|
||||
expect(parseTunnelWorkerCommand({ type: 'remove-share', shareId: '' })).toBeUndefined()
|
||||
expect(parseTunnelWorkerCommand(addShare(spec()))).toMatchObject({
|
||||
type: 'add-share',
|
||||
share: { shareId: 'share-one', connection: { mode: 'relay' } },
|
||||
})
|
||||
expect(parseTunnelWorkerCommand(addShare({ ...spec(), connection: { mode: 'direct' } }))).toMatchObject({
|
||||
share: { connection: { mode: 'direct' } },
|
||||
})
|
||||
expect(parseTunnelWorkerCommand(addShare({ ...spec(), connection: { mode: 'carrier' } }))).toBeUndefined()
|
||||
expect(parseTunnelWorkerCommand(addShare({ ...spec(), connection: 'relay' }))).toBeUndefined()
|
||||
expect(parseTunnelWorkerCommand({ type: 'add-share', registrationToken: 'not-a-token', share: spec() })).toBeUndefined()
|
||||
expect(parseTunnelWorkerCommand({
|
||||
type: 'share-rpc-response', registrationToken: REGISTRATION, token: TOKEN, id: 1,
|
||||
shareId: 'share-one', status: 200, body: { ok: true },
|
||||
})).toMatchObject({ type: 'share-rpc-response', status: 200, body: { ok: true } })
|
||||
expect(parseTunnelWorkerCommand({
|
||||
type: 'share-rpc-response', registrationToken: REGISTRATION, token: TOKEN, id: 1,
|
||||
shareId: 'share-one', status: 200,
|
||||
})).not.toHaveProperty('body')
|
||||
for (const status of [99, 600, '200', 2_147_483_648]) {
|
||||
expect(parseTunnelWorkerCommand({
|
||||
type: 'share-rpc-response', registrationToken: REGISTRATION, token: TOKEN, id: 1,
|
||||
shareId: 'share-one', status,
|
||||
})).toBeUndefined()
|
||||
}
|
||||
expect(parseTunnelWorkerCommand({ type: 'future-command' })).toBeUndefined()
|
||||
expect(parseTunnelWorkerCommand(null)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('refuses every malformed field of a share spec', () => {
|
||||
const invalid: ReadonlyArray<Record<string, unknown>> = [
|
||||
{},
|
||||
{ ...spec(), shareId: '' },
|
||||
{ ...spec(), shareId: 4 },
|
||||
{ ...spec(), sessionId: 4 },
|
||||
{ ...spec(), sessionTitle: 4 },
|
||||
{ ...spec(), accessMode: 'approval' },
|
||||
{ ...spec(), relay: 4 },
|
||||
{ ...spec(), gatewayToken: 4 },
|
||||
{ ...spec(), ownerDisplayName: 4 },
|
||||
{ ...spec(), ownerProviderId: 4 },
|
||||
{ ...spec(), ownerTenantId: 4 },
|
||||
{ ...spec(), dshVersion: 4 },
|
||||
{ ...spec(), ownerInstanceId: 4 },
|
||||
{ ...spec(), ownerCredential: 4 },
|
||||
{ ...spec(), expiresAt: 4 },
|
||||
{ ...spec(), expiresAt: 'banana' },
|
||||
{ ...spec(), allowsFork: 'yes' },
|
||||
{ ...spec(), sessionMode: 'fork-readonly' },
|
||||
{ ...spec(), allowsFileUpload: 'yes' },
|
||||
{ ...spec(), allowedSubjectIds: 'viewer' },
|
||||
{ ...spec(), allowedSubjectIds: ['viewer', ''] },
|
||||
{ ...spec(), allowedSubjectIds: ['viewer', 4] },
|
||||
]
|
||||
for (const share of invalid) expect(parseTunnelWorkerCommand(addShare(share)), JSON.stringify(share)).toBeUndefined()
|
||||
for (const share of [null, 'share-one', []] as const) {
|
||||
expect(parseTunnelWorkerCommand(addShare(share as unknown as Record<string, unknown>))).toBeUndefined()
|
||||
}
|
||||
|
||||
const allOptional = {
|
||||
...spec(),
|
||||
ownerProviderId: 'p', ownerTenantId: 't', dshVersion: '1.0.0', ownerInstanceId: 'i',
|
||||
ownerCredential: 'credential', expiresAt: '2026-10-03T00:00:00.000Z',
|
||||
allowsFork: true, sessionMode: 'write', allowsFileUpload: false,
|
||||
}
|
||||
expect(parseTunnelWorkerCommand(addShare(allOptional))).toBeDefined()
|
||||
expect(parseTunnelWorkerCommand(addShare({ ...spec(), accessMode: 'open' }))).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('tunnel worker events', () => {
|
||||
it('parses the lifecycle events a host receives', () => {
|
||||
expect(parseTunnelWorkerEvent({ type: 'ready' })).toEqual({ type: 'ready' })
|
||||
for (const type of ['share-connected', 'share-closed'] as const) {
|
||||
expect(parseTunnelWorkerEvent({ type, registrationToken: REGISTRATION, shareId: 'share-one' }))
|
||||
.toEqual({ type, registrationToken: REGISTRATION, shareId: 'share-one' })
|
||||
expect(parseTunnelWorkerEvent({ type, registrationToken: 'bad', shareId: 'share-one' })).toBeUndefined()
|
||||
expect(parseTunnelWorkerEvent({ type, registrationToken: REGISTRATION, shareId: '' })).toBeUndefined()
|
||||
}
|
||||
expect(parseTunnelWorkerEvent({
|
||||
...state(), registrationToken: REGISTRATION, shareId: 'share-one',
|
||||
})).toMatchObject({ type: 'share-access-state', registrationToken: REGISTRATION, shareId: 'share-one' })
|
||||
expect(parseTunnelWorkerEvent({
|
||||
...state(), registrationToken: 'bad', shareId: 'share-one',
|
||||
})).toBeUndefined()
|
||||
expect(parseTunnelWorkerEvent({
|
||||
type: 'share-error', registrationToken: REGISTRATION, shareId: 'share-one', message: 'nope',
|
||||
})).toEqual({ type: 'share-error', registrationToken: REGISTRATION, shareId: 'share-one', message: 'nope' })
|
||||
expect(parseTunnelWorkerEvent({
|
||||
type: 'share-error', registrationToken: REGISTRATION, shareId: 'share-one',
|
||||
})).toBeUndefined()
|
||||
expect(parseTunnelWorkerEvent({
|
||||
type: 'share-rpc-cancel', registrationToken: REGISTRATION, token: TOKEN, id: 2, shareId: 'share-one',
|
||||
})).toEqual({ type: 'share-rpc-cancel', registrationToken: REGISTRATION, token: TOKEN, id: 2, shareId: 'share-one' })
|
||||
const actor = { providerId: 'p', tenantId: 't', subjectId: 's' }
|
||||
expect(parseTunnelWorkerEvent({
|
||||
type: 'share-rpc-request', registrationToken: REGISTRATION, token: TOKEN, id: 3,
|
||||
shareId: 'share-one', body: { op: 'call' }, actor,
|
||||
})).toMatchObject({ type: 'share-rpc-request', id: 3, actor })
|
||||
for (const malformed of [
|
||||
{ type: 'share-rpc-request', registrationToken: REGISTRATION, token: TOKEN, id: 3, shareId: 'share-one', actor },
|
||||
{ type: 'share-rpc-request', registrationToken: REGISTRATION, token: TOKEN, id: 3, shareId: 'share-one', body: {}, actor: 'actor' },
|
||||
{ type: 'share-rpc-request', registrationToken: REGISTRATION, token: '', id: 3, shareId: 'share-one', body: {}, actor },
|
||||
{ type: 'share-rpc-request', registrationToken: REGISTRATION, token: TOKEN, id: 0, shareId: 'share-one', body: {}, actor },
|
||||
{ type: 'future-event' },
|
||||
]) {
|
||||
expect(parseTunnelWorkerEvent(malformed)).toBeUndefined()
|
||||
}
|
||||
expect(parseTunnelWorkerEvent(null)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('committed authority parsing', () => {
|
||||
it('owns the snapshot fields a reader follows', () => {
|
||||
expect(parseShareAccessState(state())).toEqual({
|
||||
type: 'share-access-state', version: 3, accessMode: 'selected',
|
||||
allowedSubjectIds: ['viewer'], sessionMode: 'view', allowsFileUpload: false,
|
||||
})
|
||||
expect(parseShareAccessState({
|
||||
...state(), version: 0, accessMode: 'open', sessionMode: 'write', allowsFork: true,
|
||||
allowsFileUpload: true, allowedSubjectIds: [], expiresAt: '2026-10-03T00:00:00.000Z',
|
||||
})).toEqual({
|
||||
type: 'share-access-state', version: 0, accessMode: 'open', allowedSubjectIds: [],
|
||||
sessionMode: 'write', allowsFork: true, allowsFileUpload: true,
|
||||
expiresAt: '2026-10-03T00:00:00.000Z',
|
||||
})
|
||||
})
|
||||
|
||||
it('refuses a snapshot field this build cannot trust', () => {
|
||||
const invalid: ReadonlyArray<unknown> = [
|
||||
null,
|
||||
{ ...state(), type: 'share-rpc' },
|
||||
{ ...state(), version: -1 },
|
||||
{ ...state(), version: 1.5 },
|
||||
{ ...state(), accessMode: 'approval' },
|
||||
{ ...state(), allowedSubjectIds: 'viewer' },
|
||||
{ ...state(), allowedSubjectIds: [''] },
|
||||
{ ...state(), allowedSubjectIds: [4] },
|
||||
{ ...state(), sessionMode: 'fork-readonly' },
|
||||
{ ...state(), allowsFork: 'yes' },
|
||||
{ ...state(), allowsFileUpload: 'yes' },
|
||||
{ ...state(), expiresAt: 4 },
|
||||
{ ...state(), expiresAt: 'not-a-date' },
|
||||
]
|
||||
for (const value of invalid) expect(parseShareAccessState(value), JSON.stringify(value)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -588,4 +588,52 @@ describe('viewer carrier forwarded page routes', () => {
|
||||
.toBe('direct:open-in-app/open')
|
||||
expect(direct).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('carries a forwarded request body and its abort signal to the owner', async () => {
|
||||
const { posted } = carrier(() => json({ name: 'owner-host' }))
|
||||
const controller = new AbortController()
|
||||
const response = await globalThis.fetch('/api/present.host', {
|
||||
body: '{"app":"finder"}',
|
||||
signal: controller.signal,
|
||||
})
|
||||
expect(await response.json()).toEqual({ name: 'owner-host' })
|
||||
expect(posted[0]).toMatchObject({ op: 'call', path: '/api/present.host', body: { app: 'finder' } })
|
||||
})
|
||||
|
||||
it('forwards a GET whose init states no body or signal', async () => {
|
||||
const { posted } = carrier(() => json({ name: 'owner-host' }))
|
||||
expect(await (await globalThis.fetch('/api/present.host', { method: 'GET' })).json())
|
||||
.toEqual({ name: 'owner-host' })
|
||||
expect(posted[0]).toMatchObject({ op: 'call', path: '/api/present.host', body: {} })
|
||||
})
|
||||
|
||||
it('treats a null page signal as no signal', async () => {
|
||||
const { posted } = carrier(() => json({ name: 'owner-host' }))
|
||||
// A page handler may pass `signal: null`; the carrier states it as no signal rather than as one.
|
||||
expect(await (await globalThis.fetch('/api/present.host', { signal: null })).json())
|
||||
.toEqual({ name: 'owner-host' })
|
||||
expect(posted[0]).toMatchObject({ op: 'call', path: '/api/present.host' })
|
||||
})
|
||||
|
||||
it('answers a forwarded route whose owner reply carries no body', async () => {
|
||||
const { posted } = carrier(() => new Response('', { status: 200 }))
|
||||
const response = await globalThis.fetch('/api/present.host')
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.text()).toBe('null')
|
||||
expect(posted).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('reads the target and method from a Request object', async () => {
|
||||
const { posted } = carrier(() => json({ apps: ['finder'] }))
|
||||
const response = await globalThis.fetch(new Request('https://relay.example/open-in-app/apps'))
|
||||
expect(await response.json()).toEqual({ apps: ['finder'] })
|
||||
expect(posted[0]).toMatchObject({ op: 'call', path: '/open-in-app/apps' })
|
||||
})
|
||||
|
||||
it('leaves a target the page base cannot resolve to the page fetch', async () => {
|
||||
const direct = directFetch()
|
||||
// `new URL` rejects this target, so it cannot name a forwarded route.
|
||||
expect(await (await globalThis.fetch('http://[invalid')).text()).toBe('direct:http://[invalid')
|
||||
expect(direct).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -385,11 +385,6 @@ export default defineConfig({
|
||||
// by decision: its correctness signal is its uninstrumented suite and
|
||||
// the packer's end-to-end image spec.
|
||||
'packages/experimental/webworker-runtime/src/**/*.ts',
|
||||
// The protocol layer's codec/refusal branches are covered by its own
|
||||
// package suite, but the repo-wide per-file gate is deferred until this
|
||||
// package ships in-repo with the Relay; tracked in the LiveShare stack
|
||||
// pull requests (protocol layer) rather than another package's lane.
|
||||
'packages/experimental/liveshare-protocol/src/**/*.ts',
|
||||
// Projection/command round: executor lifecycle branches and the
|
||||
// registry's drive tails need the same maturing lanes. TODO(gui):
|
||||
// cover and remove with the client test lane above.
|
||||
|
||||
Reference in new issue
Block a user