mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: remove redundant browser and transport coverage (#33757)
* test: remove redundant browser and transport coverage * test: retire deleted suites from reliability manifest --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
projectHostSetupProjectionFromRepos,
|
||||
getProjectHostSetupsForProject,
|
||||
isGitHubBackedRepo,
|
||||
getProjectIdForProviderIdentity,
|
||||
isProjectRemoteIdentityPending
|
||||
} from './project-host-setup-projection'
|
||||
@@ -449,31 +448,6 @@ describe('project host setup projection', () => {
|
||||
).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('keeps same-named cross-host records separate when there is no shared repo identity', () => {
|
||||
const projection = projectHostSetupProjectionFromRepos([
|
||||
repo({
|
||||
id: 'local-sample-app',
|
||||
path: '/Users/alice/work/sample-app',
|
||||
displayName: 'sample-app'
|
||||
}),
|
||||
repo({
|
||||
id: 'ssh-sample-app',
|
||||
path: '/srv/unrelated/sample-app',
|
||||
displayName: 'sample-app',
|
||||
connectionId: 'staging server'
|
||||
}),
|
||||
repo({
|
||||
id: 'runtime-sample-app',
|
||||
path: '/workspace/sample-app',
|
||||
displayName: 'sample-app',
|
||||
executionHostId: 'runtime:preview'
|
||||
})
|
||||
])
|
||||
|
||||
// Display names cannot prove identity; merge only with normalized remote identity or explicit user linking.
|
||||
expect(projection.projects).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('does not collapse case-distinct remote paths for self-hosted git remotes', () => {
|
||||
const projection = projectHostSetupProjectionFromRepos([
|
||||
repo({
|
||||
@@ -537,47 +511,6 @@ describe('project host setup projection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('isGitHubBackedRepo', () => {
|
||||
it('is true when an explicit upstream owner/repo is present', () => {
|
||||
const target = repo({
|
||||
id: 'r',
|
||||
path: '/r',
|
||||
displayName: 'r',
|
||||
upstream: { owner: 'stablyai', repo: 'orca' }
|
||||
})
|
||||
expect(isGitHubBackedRepo(target)).toBe(true)
|
||||
})
|
||||
|
||||
it('is true when a GitHub-sourced avatar icon encodes the slug', () => {
|
||||
const target = repo({
|
||||
id: 'r',
|
||||
path: '/r',
|
||||
displayName: 'r',
|
||||
repoIcon: {
|
||||
type: 'image',
|
||||
src: 'https://github.com/stablyai.png?size=64',
|
||||
source: 'github',
|
||||
label: 'stablyai/orca'
|
||||
}
|
||||
})
|
||||
expect(isGitHubBackedRepo(target)).toBe(true)
|
||||
})
|
||||
|
||||
it('is false for a non-GitHub icon and no upstream (GitLab/folder)', () => {
|
||||
const target = repo({
|
||||
id: 'r',
|
||||
path: '/r',
|
||||
displayName: 'r',
|
||||
repoIcon: { type: 'lucide', name: 'gitlab' }
|
||||
})
|
||||
expect(isGitHubBackedRepo(target)).toBe(false)
|
||||
})
|
||||
|
||||
it('is false for a plain local repo with no provider signal', () => {
|
||||
expect(isGitHubBackedRepo(repo({ id: 'r', path: '/r', displayName: 'r' }))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getProjectIdForProviderIdentity', () => {
|
||||
it('uses the same normalized identity key as project projection', () => {
|
||||
expect(
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { expect, it } from 'vitest'
|
||||
import { DEFAULT_TERMINAL_THEME_DARK } from './terminal-theme-selection'
|
||||
import { TERMINAL_THEME_CATALOG } from './terminal-themes'
|
||||
import {
|
||||
ORCA_DEFAULT_COLOR_QUERY_REPLY_COLORS,
|
||||
normalizeColorQueryReplyColors,
|
||||
resolvePtyOwnerColorQueryColors
|
||||
} from './pty-owner-color-query-colors'
|
||||
|
||||
it("falls back to the colours of Orca's default dark terminal theme", () => {
|
||||
const theme = TERMINAL_THEME_CATALOG[DEFAULT_TERMINAL_THEME_DARK]
|
||||
expect(ORCA_DEFAULT_COLOR_QUERY_REPLY_COLORS).toEqual({
|
||||
foreground: theme?.foreground,
|
||||
background: theme?.background
|
||||
})
|
||||
})
|
||||
|
||||
it('prefers host colours, then spawn colours, then the default, skipping unusable pairs', () => {
|
||||
const host = { foreground: '#000000', background: '#111111' }
|
||||
const spawn = { foreground: '#222222', background: '#333333' }
|
||||
|
||||
@@ -37,16 +37,6 @@ describe('PTY owner colour replies for the terminal life', () => {
|
||||
expect(visible()).toBe('\x1b]11;#123456\x07\x1b]111\x07')
|
||||
})
|
||||
|
||||
it('answers with the viewer colours the host holds now, not the colours sent at spawn', () => {
|
||||
// The host's viewer may have changed theme since this pane was created.
|
||||
const spawnedLight = { foreground: '#2e3434', background: '#ffffff' }
|
||||
const { ingress, writes } = createHarness({ colors: spawnedLight, deadlineMs: 5_000 })
|
||||
|
||||
ingress.accept(QUERY)
|
||||
|
||||
expect(writes).toEqual([HOST_BACKGROUND_REPLY])
|
||||
})
|
||||
|
||||
it('answers a query torn right after its ESC once the Kitty window has closed', () => {
|
||||
const { ingress, writes, visible } = createHarness({
|
||||
colors: HOST,
|
||||
|
||||
@@ -46,14 +46,6 @@ describe('live query replies', () => {
|
||||
expect(needsCookedEchoSafeQueryReply(mode2031SequenceFor('light'))).toBe(true)
|
||||
})
|
||||
|
||||
it('writes an echo-risk reply in the calling turn', () => {
|
||||
const { ingress, writes } = harness()
|
||||
expect(ingress.answerLiveQueryReply(OSC_COLOR_REPLY)).toBe(true)
|
||||
// No timer advance: the whole point is that there is nothing to wait for.
|
||||
expect(writes).toEqual([OSC_COLOR_REPLY])
|
||||
ingress.drainAndClose()
|
||||
})
|
||||
|
||||
it('answers repeated identical replies without collapsing them', () => {
|
||||
const { ingress, writes } = harness()
|
||||
expect(ingress.answerLiveQueryReply(COLOR_SCHEME_REPLY)).toBe(true)
|
||||
|
||||
@@ -79,16 +79,6 @@ describe('PtyStartupIngress', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('suppresses the first echo immediately and keeps a later exact collision', () => {
|
||||
const { ingress, emissions } = createHarness({ projection: true })
|
||||
ingress.accept('\x1b]10;?\x07')
|
||||
const projected = ']10;rgb:2e2e/3434/3434\\'
|
||||
ingress.accept(projected)
|
||||
ingress.accept(projected)
|
||||
ingress.drainAndClose()
|
||||
expect(visible(emissions)).toBe(projected)
|
||||
})
|
||||
|
||||
it('matches each echo across every split without skipping an earlier FIFO candidate', () => {
|
||||
const foregroundEcho = ']10;rgb:2e2e/3434/3434\\'
|
||||
const backgroundEcho = ']11;rgb:ffff/ffff/ffff\\'
|
||||
@@ -751,32 +741,4 @@ describe('PtyStartupIngress', () => {
|
||||
expect(ingress.acceptedRawSequence).toBe(closedAt)
|
||||
expect(visible(emissions)).toBe(']10;rgb:2e2e/')
|
||||
})
|
||||
|
||||
it('suppresses the readline echo, which no reading of the ECHO bit could predict', () => {
|
||||
const { ingress, writes, emissions } = createHarness()
|
||||
ingress.accept('\x1b]10;?\x07')
|
||||
expect(writes).toEqual([FOREGROUND_REPLY])
|
||||
// Live bash PTYs echo via readline even with raw mode/ECHO-clear; kernel ECHO cannot predict software echoes.
|
||||
ingress.accept(POSIX_COOKED_ECHOES[1]?.(FOREGROUND_REPLY) ?? '')
|
||||
ingress.drainAndClose()
|
||||
expect(visible(emissions)).toBe('')
|
||||
})
|
||||
|
||||
it('suppresses the kernel caret echo', () => {
|
||||
const { ingress, writes, emissions } = createHarness()
|
||||
ingress.accept('\x1b]10;?\x07')
|
||||
expect(writes).toEqual([FOREGROUND_REPLY])
|
||||
// Always arm the default ECHOCTL projection; no probe verdict can safely retire it.
|
||||
ingress.accept(POSIX_COOKED_ECHOES[0]?.(FOREGROUND_REPLY) ?? '')
|
||||
ingress.drainAndClose()
|
||||
expect(visible(emissions)).toBe('')
|
||||
})
|
||||
|
||||
it('writes the reply in the accepting turn, with no probe and no wait', () => {
|
||||
const { ingress, writes } = createHarness()
|
||||
ingress.accept('\u001b]10;?\u0007')
|
||||
// No deferral: only output projections remove echo, while delayed writes reorder replies (#15559).
|
||||
expect(writes).toEqual(['\u001b]10;rgb:2e2e/3434/3434\u001b\\'])
|
||||
ingress.drainAndClose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,26 +40,6 @@ describe('replyEchoProjections on a POSIX pty', () => {
|
||||
expect(locateEcho(replyEchoProjections(reply, 'posix-pty'), reply).kind).toBe('complete')
|
||||
})
|
||||
|
||||
it('caret-encodes every control, not just ESC', () => {
|
||||
const [kernel] = replyEchoProjections(OSC11_BEL, 'posix-pty')
|
||||
expect(kernel?.needle).toBe('^[]11;rgb:2e2e/3434/3434^G')
|
||||
expect(kernel?.needle).not.toContain('\x07')
|
||||
})
|
||||
|
||||
// ECHOCTL leaves TAB/LF/CR literal and maps DEL→^? (vis(3)); pin future grammar behavior even though current replies lack them.
|
||||
it.each([
|
||||
{ name: 'TAB stays literal', input: '\t', encoded: '\t' },
|
||||
{ name: 'LF stays literal', input: '\n', encoded: '\n' },
|
||||
{ name: 'CR stays literal', input: '\r', encoded: '\r' },
|
||||
{ name: 'NUL carets to ^@', input: '\x00', encoded: '^@' },
|
||||
{ name: 'BEL carets to ^G', input: '\x07', encoded: '^G' },
|
||||
{ name: 'ESC carets to ^[', input: '\x1b', encoded: '^[' },
|
||||
{ name: 'DEL carets to ^?', input: '\x7f', encoded: '^?' }
|
||||
])('$name under ECHOCTL', ({ input, encoded }) => {
|
||||
const [kernel] = replyEchoProjections(`\x1b[?9${input}n`, 'posix-pty')
|
||||
expect(kernel?.needle).toBe(`^[[?9${encoded}n`)
|
||||
})
|
||||
|
||||
// Guard the private-DSR grammar locally: widening a caller must not arm DA1’s shared-prefix BEL needle.
|
||||
it.each(['\x1b[?1;2c', '\x1b[?0u', '\x1b[?2026;2$y', '\x1b[?12;5R'])(
|
||||
'projects no readline needle for %j, which is not a private DSR',
|
||||
@@ -82,13 +62,6 @@ describe('replyEchoProjections on a POSIX pty', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('projects readline for a private DSR, which carries no OSC introducer', () => {
|
||||
const needles = replyEchoProjections(DSR_997, 'posix-pty').map(
|
||||
(projection) => projection.needle
|
||||
)
|
||||
expect(needles).toContain('\x07997;1n')
|
||||
})
|
||||
|
||||
// Never hold ESC-led partials: a bare ESC could belong to a torn query and would escape unanswered when the hold expires.
|
||||
it('holds a partial only for needles that do not start with ESC', () => {
|
||||
for (const { reply } of LIVE_ECHOES) {
|
||||
|
||||
@@ -22,11 +22,6 @@ describe('resolvePublishedPaneAgentIdentity', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the launch record when an unambiguous title names a different agent', () => {
|
||||
// Unlike agreeing/absent title cases, this detects the title-first mutant: explicit Claude branding cannot override a Codex launch.
|
||||
expect(resolve({ launchAgent: 'codex', title: '✳ Claude Code' })).toBe('codex')
|
||||
})
|
||||
|
||||
it('prefers the live foreground process to the launch record', () => {
|
||||
expect(resolve({ launchAgent: 'claude', foregroundAgent: 'codex' })).toBe('codex')
|
||||
})
|
||||
|
||||
@@ -33,14 +33,6 @@ describe('buildPullRequestFieldsPrompt', () => {
|
||||
expect(prompt).toContain('Use conventional PR titles.')
|
||||
})
|
||||
|
||||
it('requires ELI5 problem and solution sections before implementation details', () => {
|
||||
const prompt = buildPullRequestFieldsPrompt(context, '')
|
||||
|
||||
expect(prompt).toContain('start with `## Problem`, then `## Solution`')
|
||||
expect(prompt).toContain('simple ELI5 language before details')
|
||||
expect(prompt).toContain('Reuse equivalent existing sections instead of duplicating them')
|
||||
})
|
||||
|
||||
it('includes GitHub issue details and complete or partial reference guidance', () => {
|
||||
const prompt = buildPullRequestFieldsPrompt(
|
||||
{
|
||||
@@ -110,19 +102,6 @@ describe('buildPullRequestFieldsPrompt', () => {
|
||||
expect(prompt).toContain('`Fixes AB#99` only for a complete fix')
|
||||
expect(prompt).toContain('use `AB#99`')
|
||||
})
|
||||
|
||||
it('tells the agent to preserve existing review templates', () => {
|
||||
const prompt = buildPullRequestFieldsPrompt(
|
||||
{
|
||||
...context,
|
||||
currentBody: '## Summary\n\n## Testing\n\n- [ ] Required checks'
|
||||
},
|
||||
''
|
||||
)
|
||||
|
||||
expect(prompt).toContain('Retain every heading, required section, and checklist')
|
||||
expect(prompt).toContain('Leave genuinely unknown template items as TODO or unchecked')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseGeneratedPullRequestFields', () => {
|
||||
@@ -140,28 +119,6 @@ describe('parseGeneratedPullRequestFields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('parses CRLF fenced JSON output without full-string fence matching', () => {
|
||||
const matchSpy = vi.spyOn(String.prototype, 'match')
|
||||
const replaceSpy = vi.spyOn(String.prototype, 'replace')
|
||||
const fields = parseGeneratedPullRequestFields(
|
||||
'```JSON\r\n{"base":"main","title":"fix: add details.","body":"Summary","draft":true}\r\n```',
|
||||
context
|
||||
)
|
||||
|
||||
expect(fields.title).toBe('fix: add details')
|
||||
const usedFenceMatch = matchSpy.mock.calls.some(
|
||||
([pattern]) =>
|
||||
pattern instanceof RegExp &&
|
||||
pattern.source.startsWith('^```') &&
|
||||
pattern.source.includes('[\\s\\S]')
|
||||
)
|
||||
const usedCrlfReplace = replaceSpy.mock.calls.some(
|
||||
([pattern]) => pattern instanceof RegExp && pattern.source === '\\r\\n' && pattern.global
|
||||
)
|
||||
expect(usedFenceMatch).toBe(false)
|
||||
expect(usedCrlfReplace).toBe(false)
|
||||
})
|
||||
|
||||
it('falls back for missing optional values', () => {
|
||||
const fields = parseGeneratedPullRequestFields('{"title":""}', context)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
buildGitLsFilesArgsForQuickOpen,
|
||||
buildHiddenDirExcludeGlobs,
|
||||
buildRgArgsForQuickOpen,
|
||||
HIDDEN_DIR_BLOCKLIST,
|
||||
normalizeQuickOpenRgLine,
|
||||
shouldExcludeQuickOpenRelPath,
|
||||
shouldIncludeQuickOpenPath
|
||||
@@ -38,14 +37,6 @@ describe('shouldIncludeQuickOpenPath', () => {
|
||||
expect(shouldIncludeQuickOpenPath('nested/.local/share/app/state.db')).toBe(false)
|
||||
expect(shouldIncludeQuickOpenPath('.local/bin/tool')).toBe(true)
|
||||
})
|
||||
|
||||
it('does NOT blocklist user-authored dirs like .config, .ssh, .github', () => {
|
||||
expect(HIDDEN_DIR_BLOCKLIST.has('.config')).toBe(false)
|
||||
expect(HIDDEN_DIR_BLOCKLIST.has('.ssh')).toBe(false)
|
||||
expect(HIDDEN_DIR_BLOCKLIST.has('.github')).toBe(false)
|
||||
expect(HIDDEN_DIR_BLOCKLIST.has('.devcontainer')).toBe(false)
|
||||
expect(HIDDEN_DIR_BLOCKLIST.has('.local')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildExcludePathPrefixes', () => {
|
||||
@@ -91,40 +82,9 @@ describe('buildExcludePathPrefixes', () => {
|
||||
it('strips trailing slashes', () => {
|
||||
expect(buildExcludePathPrefixes('/r', ['/r/a/', '/r/b///'])).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
it('keeps valid child prefixes whose segment starts with dotdot characters', () => {
|
||||
expect(buildExcludePathPrefixes('/home/u/repo', ['/home/u/repo/..fixtures'])).toEqual([
|
||||
'..fixtures'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldExcludeQuickOpenRelPath', () => {
|
||||
it('matches the original filter across boundary and Unicode path combinations', () => {
|
||||
const paths = [
|
||||
'',
|
||||
'/',
|
||||
'a',
|
||||
'a/',
|
||||
'a//',
|
||||
'ab',
|
||||
'a/b',
|
||||
'a\\b',
|
||||
'A/b',
|
||||
'界/😀',
|
||||
'界/😀x',
|
||||
'a[1]/x',
|
||||
'a./x'
|
||||
]
|
||||
for (const prefix of paths) {
|
||||
for (const relPath of paths) {
|
||||
const expected =
|
||||
relPath === prefix || (relPath.length > prefix.length && relPath.startsWith(`${prefix}/`))
|
||||
expect(shouldExcludeQuickOpenRelPath(relPath, [prefix])).toBe(expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves normalized Windows and UNC exclusion boundaries', () => {
|
||||
for (const [root, excluded] of [
|
||||
['C:\\Repo', 'C:\\Repo\\trees\\one'],
|
||||
@@ -327,14 +287,6 @@ describe('buildGitLsFilesArgsForQuickOpen', () => {
|
||||
expect(ignoredPass).not.toContain(':(glob)**/.env*')
|
||||
})
|
||||
|
||||
it('collapses untracked directories in both passes without generated pathspec churn', () => {
|
||||
const { primary, ignoredPass } = buildGitLsFilesArgsForQuickOpen()
|
||||
expect(primary).toContain('--directory')
|
||||
expect(ignoredPass).toContain('--directory')
|
||||
expect(ignoredPass).toContain('--no-empty-directory')
|
||||
expect([...primary, ...ignoredPass]).not.toContain(':(exclude,glob)**/node_modules/**')
|
||||
})
|
||||
|
||||
it('exclude prefixes prepend positive "." pathspec', () => {
|
||||
const { primary, ignoredPass } = buildGitLsFilesArgsForQuickOpen(['packages/app'])
|
||||
const dashDashIdx = primary.indexOf('--')
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
limitQuickOpenFilesBySerializedBytes,
|
||||
limitQuickOpenSearchReplyBySerializedBytes,
|
||||
serializedQuickOpenPathBytes
|
||||
limitQuickOpenSearchReplyBySerializedBytes
|
||||
} from './quick-open-transport-budget'
|
||||
|
||||
describe('Quick Open transport byte budget', () => {
|
||||
it('matches JSON.stringify byte accounting, including escaped paths', () => {
|
||||
const path = 'src/line\nquote".ts'
|
||||
expect(serializedQuickOpenPathBytes(path)).toBe(Buffer.byteLength(JSON.stringify(path), 'utf8'))
|
||||
})
|
||||
|
||||
it('returns the largest prefix that fits as a JSON array', () => {
|
||||
const files = ['src/a.ts', 'data/ignored\nfile.bin', 'src/b.ts']
|
||||
const budget = Buffer.byteLength(JSON.stringify(files.slice(0, 2)), 'utf8')
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { decodeBase64Prefix, exceedsRasterImagePreviewLimits } from './raster-image-base64-preview'
|
||||
import type * as RasterImageDimensionsModule from './raster-image-dimensions'
|
||||
import { readRasterImageDimensions } from './raster-image-dimensions'
|
||||
import {
|
||||
isKnownRasterImageMimeType,
|
||||
isRasterImagePreviewDimensions,
|
||||
RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES
|
||||
} from './raster-image-preview-limits'
|
||||
|
||||
/** Frozen full-payload decode/dimension baseline for comparison with the prefix probe. */
|
||||
function unprobedExceeds(content: string, mimeType: string | undefined): boolean {
|
||||
if (!isKnownRasterImageMimeType(mimeType)) {
|
||||
return false
|
||||
}
|
||||
const prefix = referenceDecodeBase64Prefix(content, RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES)
|
||||
if (!prefix) {
|
||||
return false
|
||||
}
|
||||
const dimensions = readRasterImageDimensions(prefix)
|
||||
return dimensions !== null && !isRasterImagePreviewDimensions(dimensions)
|
||||
}
|
||||
import { RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES } from './raster-image-preview-limits'
|
||||
|
||||
// Observe decoded bytes handed to dimension reading; output equivalence alone cannot detect wasted full-payload decoding.
|
||||
const { dimensionReadLengths } = vi.hoisted(() => ({ dimensionReadLengths: [] as number[] }))
|
||||
@@ -35,123 +17,6 @@ vi.mock('./raster-image-dimensions', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
// ── Reference decoder: the pre-change implementation, verbatim ──────────────────────────────────
|
||||
const BASE64_PADDING = -2
|
||||
const INVALID_BASE64 = -1
|
||||
|
||||
function base64Value(code: number): number {
|
||||
if (code >= 65 && code <= 90) {
|
||||
return code - 65
|
||||
}
|
||||
if (code >= 97 && code <= 122) {
|
||||
return code - 71
|
||||
}
|
||||
if (code >= 48 && code <= 57) {
|
||||
return code + 4
|
||||
}
|
||||
if (code === 43) {
|
||||
return 62
|
||||
}
|
||||
if (code === 47) {
|
||||
return 63
|
||||
}
|
||||
if (code === 61) {
|
||||
return BASE64_PADDING
|
||||
}
|
||||
return INVALID_BASE64
|
||||
}
|
||||
|
||||
function isWhitespace(code: number): boolean {
|
||||
return code === 9 || code === 10 || code === 12 || code === 13 || code === 32
|
||||
}
|
||||
|
||||
function referenceWriteQuartet(
|
||||
output: Uint8Array,
|
||||
offset: number,
|
||||
quartet: readonly number[]
|
||||
): { bytesWritten: number; padded: boolean } | null {
|
||||
const [a, b, c, d] = quartet
|
||||
if (a === undefined || b === undefined || a < 0 || b < 0) {
|
||||
return null
|
||||
}
|
||||
if (c === BASE64_PADDING) {
|
||||
if (d !== BASE64_PADDING) {
|
||||
return null
|
||||
}
|
||||
if (offset < output.length) {
|
||||
output[offset] = (a << 2) | (b >> 4)
|
||||
}
|
||||
return { bytesWritten: Math.min(1, output.length - offset), padded: true }
|
||||
}
|
||||
if (c === undefined || c < 0) {
|
||||
return null
|
||||
}
|
||||
if (offset < output.length) {
|
||||
output[offset] = (a << 2) | (b >> 4)
|
||||
}
|
||||
if (offset + 1 < output.length) {
|
||||
output[offset + 1] = ((b & 15) << 4) | (c >> 2)
|
||||
}
|
||||
if (d === BASE64_PADDING) {
|
||||
return { bytesWritten: Math.min(2, output.length - offset), padded: true }
|
||||
}
|
||||
if (d === undefined || d < 0) {
|
||||
return null
|
||||
}
|
||||
if (offset + 2 < output.length) {
|
||||
output[offset + 2] = ((c & 3) << 6) | d
|
||||
}
|
||||
return { bytesWritten: Math.min(3, output.length - offset), padded: false }
|
||||
}
|
||||
|
||||
function referenceDecodeBase64Prefix(content: string, maxBytes: number): Uint8Array | null {
|
||||
const capacity = Math.min(maxBytes, Math.ceil(content.length / 4) * 3)
|
||||
const output = new Uint8Array(capacity)
|
||||
const quartet: number[] = []
|
||||
let outputLength = 0
|
||||
let padded = false
|
||||
|
||||
for (let index = 0; index < content.length && outputLength < capacity; index += 1) {
|
||||
const code = content.charCodeAt(index)
|
||||
if (isWhitespace(code)) {
|
||||
continue
|
||||
}
|
||||
if (padded) {
|
||||
return null
|
||||
}
|
||||
const value = base64Value(code)
|
||||
if (value === INVALID_BASE64) {
|
||||
return null
|
||||
}
|
||||
quartet.push(value)
|
||||
if (quartet.length !== 4) {
|
||||
continue
|
||||
}
|
||||
const decoded = referenceWriteQuartet(output, outputLength, quartet)
|
||||
if (!decoded) {
|
||||
return null
|
||||
}
|
||||
outputLength += decoded.bytesWritten
|
||||
padded = decoded.padded
|
||||
quartet.length = 0
|
||||
}
|
||||
|
||||
if (!padded && outputLength < capacity && quartet.length > 0) {
|
||||
if (quartet.length === 1 || quartet.includes(BASE64_PADDING)) {
|
||||
return null
|
||||
}
|
||||
while (quartet.length < 4) {
|
||||
quartet.push(BASE64_PADDING)
|
||||
}
|
||||
const decoded = referenceWriteQuartet(output, outputLength, quartet)
|
||||
if (!decoded) {
|
||||
return null
|
||||
}
|
||||
outputLength += decoded.bytesWritten
|
||||
}
|
||||
return output.subarray(0, outputLength)
|
||||
}
|
||||
|
||||
function pngBytes(totalBytes: number, width: number, height: number): Buffer {
|
||||
const bytes = Buffer.alloc(Math.max(totalBytes, 24))
|
||||
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(bytes)
|
||||
@@ -191,89 +56,7 @@ function jpegBytes(
|
||||
return Buffer.concat(parts)
|
||||
}
|
||||
|
||||
function gifBytes(width: number, height: number): Buffer {
|
||||
const gif = Buffer.alloc(64)
|
||||
gif.write('GIF89a', 0, 'ascii')
|
||||
gif.writeUInt16LE(width, 6)
|
||||
gif.writeUInt16LE(height, 8)
|
||||
return gif
|
||||
}
|
||||
|
||||
function webpBytes(width: number, height: number): Buffer {
|
||||
const webp = Buffer.alloc(64)
|
||||
webp.write('RIFF', 0, 'ascii')
|
||||
webp.writeUInt32LE(50, 4)
|
||||
webp.write('WEBP', 8, 'ascii')
|
||||
webp.write('VP8X', 12, 'ascii')
|
||||
webp.writeUInt32LE(10, 16)
|
||||
webp.writeUIntLE(width - 1, 24, 3)
|
||||
webp.writeUIntLE(height - 1, 27, 3)
|
||||
return webp
|
||||
}
|
||||
|
||||
const DECODE_FIXTURES: { label: string; content: string }[] = [
|
||||
{ label: 'png', content: pngBytes(24, 512, 512).toString('base64') },
|
||||
{ label: 'png padded once', content: pngBytes(26, 512, 512).toString('base64') },
|
||||
{ label: 'png padded twice', content: pngBytes(25, 512, 512).toString('base64') },
|
||||
{ label: 'png 70 KiB', content: pngBytes(70_000, 512, 512).toString('base64') },
|
||||
{ label: 'jpeg', content: jpegBytes(0, 640, 480).toString('base64') },
|
||||
{ label: 'jpeg 70 KiB exif', content: jpegBytes(70_000, 4000, 3000).toString('base64') },
|
||||
{ label: 'gif', content: gifBytes(320, 240).toString('base64') },
|
||||
{ label: 'webp', content: webpBytes(800, 600).toString('base64') },
|
||||
{ label: 'empty', content: '' },
|
||||
{ label: 'one character', content: 'A' },
|
||||
{ label: 'two characters', content: 'AB' },
|
||||
{ label: 'three characters', content: 'ABC' },
|
||||
{ label: 'invalid character', content: 'AB*D' },
|
||||
{ label: 'invalid tail', content: `${pngBytes(24, 4, 4).toString('base64')}!!!` },
|
||||
{ label: 'padding mid-payload', content: 'AAAA=AAA' },
|
||||
{ label: 'lone padding in tail', content: 'AAAAAB=' },
|
||||
{ label: 'single padding', content: 'AAAAAA==' },
|
||||
{ label: 'double padding', content: 'AAAAAAA=' },
|
||||
{ label: 'stray padding after padding', content: 'AAAA====' },
|
||||
{
|
||||
label: 'line-wrapped png',
|
||||
content: pngBytes(70_000, 512, 512)
|
||||
.toString('base64')
|
||||
.replace(/(.{76})/g, '$1\r\n')
|
||||
},
|
||||
{ label: 'leading and trailing whitespace', content: `\n\t ${'AAAA'} \r\n` },
|
||||
{ label: 'truncated png header', content: pngBytes(24, 512, 512).toString('base64').slice(0, 18) }
|
||||
]
|
||||
|
||||
const DECODE_CAPS = [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
1024,
|
||||
RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES
|
||||
]
|
||||
|
||||
describe('decodeBase64Prefix', () => {
|
||||
it('decodes byte-for-byte identically to the reference implementation', () => {
|
||||
for (const { label, content } of DECODE_FIXTURES) {
|
||||
for (const maxBytes of DECODE_CAPS) {
|
||||
const expected = referenceDecodeBase64Prefix(content, maxBytes)
|
||||
const actual = decodeBase64Prefix(content, maxBytes)
|
||||
const detail = `${label} @ maxBytes=${maxBytes}`
|
||||
if (expected === null) {
|
||||
expect(actual, detail).toBeNull()
|
||||
continue
|
||||
}
|
||||
expect(actual, detail).not.toBeNull()
|
||||
expect(Array.from(actual!), detail).toEqual(Array.from(expected))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('stops at the byte cap instead of decoding the whole payload', () => {
|
||||
const content = pngBytes(70_000, 512, 512).toString('base64')
|
||||
expect(decodeBase64Prefix(content, 32)?.byteLength).toBe(32)
|
||||
@@ -311,30 +94,4 @@ describe('exceedsRasterImagePreviewLimits', () => {
|
||||
const content = `${pngBytes(70_000, 32_769, 1).toString('base64')}!!!`
|
||||
expect(exceedsRasterImagePreviewLimits(content, 'image/png')).toBe(false)
|
||||
})
|
||||
|
||||
it('agrees with the unprobed implementation on every fixture and mime type', () => {
|
||||
const mimeTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
undefined
|
||||
]
|
||||
const fixtures = [
|
||||
...DECODE_FIXTURES,
|
||||
{ label: 'over-limit png', content: pngBytes(24, 32_769, 1).toString('base64') },
|
||||
{ label: 'over-limit pixels png', content: pngBytes(24, 8192, 8192).toString('base64') },
|
||||
{ label: 'over-limit gif', content: gifBytes(65_535, 65_535).toString('base64') },
|
||||
{ label: 'over-limit jpeg', content: jpegBytes(70_000, 40_000, 40_000).toString('base64') },
|
||||
{ label: 'over-limit webp', content: webpBytes(40_000, 40_000).toString('base64') }
|
||||
]
|
||||
for (const { label, content } of fixtures) {
|
||||
for (const mimeType of mimeTypes) {
|
||||
expect(exceedsRasterImagePreviewLimits(content, mimeType), `${label} / ${mimeType}`).toBe(
|
||||
unprobedExceeds(content, mimeType)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,16 +15,6 @@ describe('react update depth attribution', () => {
|
||||
).toBe(UNRELIABLE_BOUNDARY_ATTRIBUTION)
|
||||
})
|
||||
|
||||
it('matches the legacy error-decoder invariant form', () => {
|
||||
expect(
|
||||
getReactErrorBoundaryAttribution(
|
||||
new Error(
|
||||
'Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message.'
|
||||
)
|
||||
)
|
||||
).toBe(UNRELIABLE_BOUNDARY_ATTRIBUTION)
|
||||
})
|
||||
|
||||
it('matches the development message', () => {
|
||||
expect(
|
||||
getReactErrorBoundaryAttribution(new Error('Maximum update depth exceeded. This can happen…'))
|
||||
|
||||
@@ -2,15 +2,12 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
findInstallerAssetName,
|
||||
getReleaseNotesUrlForVersion,
|
||||
getReleaseRepoForChannel,
|
||||
getVersionChannel,
|
||||
hasDedicatedReleaseRepo,
|
||||
hasInstallableArtifactForPlatform,
|
||||
isAdhocVersion,
|
||||
isChannelSupportedOnPlatform,
|
||||
isDailyVersion,
|
||||
isHourlyVersion,
|
||||
isReleaseChannel,
|
||||
parseAdhocVersionStamp,
|
||||
parseDailyVersionStamp,
|
||||
parseDevBuildStamp,
|
||||
@@ -33,24 +30,6 @@ describe('release channel', () => {
|
||||
expect(getVersionChannel('not-a-version')).toBeNull()
|
||||
})
|
||||
|
||||
// The main release Atom feed holds only 10 entries; daily/hourly tags would evict stable/RC updates.
|
||||
it('keeps dev builds out of the main release repo, and apart from each other', () => {
|
||||
expect(getReleaseRepoForChannel('hourly')).toBe('stablyai/orca-hourly')
|
||||
expect(getReleaseRepoForChannel('daily')).toBe('stablyai/orca-daily')
|
||||
// Keep unlanded adhoc branch builds away from users following main’s hourly channel.
|
||||
expect(getReleaseRepoForChannel('adhoc')).toBe('stablyai/orca-adhoc')
|
||||
expect(getReleaseRepoForChannel('stable')).toBe('stablyai/orca')
|
||||
expect(getReleaseRepoForChannel('rc')).toBe('stablyai/orca')
|
||||
})
|
||||
|
||||
it('marks exactly the dev channels as having their own repo', () => {
|
||||
expect(hasDedicatedReleaseRepo('hourly')).toBe(true)
|
||||
expect(hasDedicatedReleaseRepo('daily')).toBe(true)
|
||||
expect(hasDedicatedReleaseRepo('adhoc')).toBe(true)
|
||||
expect(hasDedicatedReleaseRepo('stable')).toBe(false)
|
||||
expect(hasDedicatedReleaseRepo('rc')).toBe(false)
|
||||
})
|
||||
|
||||
// Dev tags exist only in their dedicated repos; main-repo links 404.
|
||||
it('builds release-notes links against the repo that published the version', () => {
|
||||
expect(getReleaseNotesUrlForVersion('1.4.160-hourly.202607281400')).toBe(
|
||||
@@ -221,16 +200,6 @@ describe('release channel', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts only known channels', () => {
|
||||
expect(isReleaseChannel('hourly')).toBe(true)
|
||||
expect(isReleaseChannel('daily')).toBe(true)
|
||||
expect(isReleaseChannel('adhoc')).toBe(true)
|
||||
expect(isReleaseChannel('stable')).toBe(true)
|
||||
expect(isReleaseChannel('nightly')).toBe(false)
|
||||
expect(isReleaseChannel(null)).toBe(false)
|
||||
expect(isReleaseChannel(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
// Timestamp tails distinguish consecutive hourlies; keep picker ordering chronological.
|
||||
it('sorts consecutive hourly builds newest first', () => {
|
||||
const build = (version: string): ReleaseBuild => ({
|
||||
|
||||
@@ -18,12 +18,7 @@ import {
|
||||
import { sendRemoteRuntimeRequest, subscribeRemoteRuntimeRequest } from './remote-runtime-client'
|
||||
import { remoteRuntimeClientCapabilities } from './remote-runtime-client-capabilities'
|
||||
import { MAX_TIMER_DELAY_MS } from './timer-delay'
|
||||
import {
|
||||
BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY,
|
||||
BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY,
|
||||
BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY
|
||||
} from './protocol-version'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from './electron-remote-runtime-client-capabilities'
|
||||
import { BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY } from './protocol-version'
|
||||
|
||||
const servers: WebSocketServer[] = []
|
||||
|
||||
@@ -189,39 +184,6 @@ describe('subscribeRemoteRuntimeRequest', () => {
|
||||
await vi.waitFor(() => expect(releaseSocket).toHaveBeenCalledOnce())
|
||||
})
|
||||
|
||||
it('detaches subscription socket listeners after close', async () => {
|
||||
const offSpy = vi.spyOn(WebSocketClient.prototype, 'off')
|
||||
try {
|
||||
const server = await createSubscriptionServer()
|
||||
const onResponse = vi.fn()
|
||||
const onError = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
|
||||
const subscription = await subscribeRemoteRuntimeRequest(
|
||||
server.pairing,
|
||||
'terminal.subscribe',
|
||||
{ terminal: 't1' },
|
||||
1000,
|
||||
{
|
||||
onResponse,
|
||||
onError,
|
||||
onClose
|
||||
}
|
||||
)
|
||||
|
||||
await vi.waitFor(() => expect(onResponse).toHaveBeenCalled())
|
||||
subscription.close()
|
||||
await vi.waitFor(() => expect(onClose).toHaveBeenCalledOnce())
|
||||
|
||||
const removedEvents = offSpy.mock.calls.map(([event]) => event)
|
||||
expect(removedEvents).toEqual(expect.arrayContaining(['open', 'error', 'close', 'message']))
|
||||
expect(subscription.sendBinary(new Uint8Array([9]))).toBe(false)
|
||||
expect(onError).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
offSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('closes a half-open subscription socket via client liveness so callers can resubscribe', async () => {
|
||||
// Half-open dedicated streams get no close/pongs/data; client liveness must trigger onError/onClose so renderer resubscription runs (#7718).
|
||||
const server = await createSubscriptionServer({ disableAutoPong: true })
|
||||
@@ -288,52 +250,6 @@ describe('subscribeRemoteRuntimeRequest', () => {
|
||||
})
|
||||
|
||||
describe('sendRemoteRuntimeRequest', () => {
|
||||
it('keeps generic native authentication free of Electron placement support', async () => {
|
||||
let receivedAuth: Record<string, unknown> | null = null
|
||||
const server = await createOneShotServer({
|
||||
onAuth: (auth) => {
|
||||
receivedAuth = auth
|
||||
}
|
||||
})
|
||||
|
||||
await sendRemoteRuntimeRequest(
|
||||
server.pairing,
|
||||
'session.tabs.list',
|
||||
{ worktree: 'id:worktree-a' },
|
||||
1000
|
||||
)
|
||||
|
||||
expect(receivedAuth).toMatchObject({
|
||||
clientCapabilities: remoteRuntimeClientCapabilities()
|
||||
})
|
||||
})
|
||||
|
||||
it('advertises browser placement support only when the Electron caller opts in', async () => {
|
||||
let receivedAuth: Record<string, unknown> | null = null
|
||||
const server = await createOneShotServer({
|
||||
onAuth: (auth) => {
|
||||
receivedAuth = auth
|
||||
}
|
||||
})
|
||||
|
||||
await sendRemoteRuntimeRequest(
|
||||
server.pairing,
|
||||
'session.tabs.list',
|
||||
{ worktree: 'id:worktree-a' },
|
||||
1000,
|
||||
undefined,
|
||||
undefined,
|
||||
ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES
|
||||
)
|
||||
|
||||
expect(receivedAuth).toMatchObject({
|
||||
clientCapabilities: expect.arrayContaining([
|
||||
BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY,
|
||||
BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it.each([-1, 1.5, MAX_TIMER_DELAY_MS + 1, Number.MAX_SAFE_INTEGER + 1])(
|
||||
'rejects invalid timer delay %s before reading pairing data',
|
||||
async (timeoutMs) => {
|
||||
@@ -565,25 +481,6 @@ describe('sendRemoteRuntimeRequest', () => {
|
||||
expect(receivedRequest).not.toHaveProperty('orchestrationContractVersion')
|
||||
expect(receivedRequest).not.toHaveProperty('orchestrationRequestId')
|
||||
})
|
||||
|
||||
it('detaches one-shot socket listeners after a successful response', async () => {
|
||||
const offSpy = vi.spyOn(WebSocketClient.prototype, 'off')
|
||||
try {
|
||||
const server = await createOneShotServer()
|
||||
|
||||
await sendRemoteRuntimeRequest<{ satisfied: boolean }>(
|
||||
server.pairing,
|
||||
'terminal.wait',
|
||||
{ terminal: 't1', for: 'tui-idle', timeoutMs: 550 },
|
||||
300
|
||||
)
|
||||
|
||||
const removedEvents = offSpy.mock.calls.map(([event]) => event)
|
||||
expect(removedEvents).toEqual(expect.arrayContaining(['open', 'error', 'close', 'message']))
|
||||
} finally {
|
||||
offSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
async function createSubscriptionServer(
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
probeAllRemoteRuntimeSocketsNow,
|
||||
startRemoteRuntimeSocketLiveness
|
||||
} from './remote-runtime-socket-liveness'
|
||||
import { SharedControlReconnectScheduler } from './remote-runtime/control/remote-runtime-shared-control-reconnect'
|
||||
|
||||
// After sleep, probe every socket immediately; 8s misses only re-ping and the normal 25s keepalive decides death so relayed paths have time to recover (#9092).
|
||||
describe('remote runtime resume probe', () => {
|
||||
@@ -154,45 +153,3 @@ describe('remote runtime resume probe', () => {
|
||||
expect(onDead).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SharedControlReconnectScheduler.retryNow after an outage', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('restarts the backoff ladder so a failure right after wake retries quickly', () => {
|
||||
const scheduler = new SharedControlReconnectScheduler()
|
||||
const open = vi.fn()
|
||||
for (let attempt = 0; attempt < 6; attempt += 1) {
|
||||
scheduler.scheduleWithIdleBackoff(false, open)
|
||||
vi.runOnlyPendingTimers()
|
||||
}
|
||||
expect(scheduler.attemptCount).toBeGreaterThanOrEqual(6)
|
||||
|
||||
scheduler.scheduleWithIdleBackoff(false, open)
|
||||
expect(scheduler.retryNow()).toBe(true)
|
||||
expect(scheduler.attemptCount).toBe(0)
|
||||
})
|
||||
|
||||
it('restarts the ladder even when the wake lands while a reconnect is already opening', () => {
|
||||
const scheduler = new SharedControlReconnectScheduler()
|
||||
const open = vi.fn()
|
||||
for (let attempt = 0; attempt < 6; attempt += 1) {
|
||||
scheduler.scheduleWithIdleBackoff(false, open)
|
||||
vi.runOnlyPendingTimers()
|
||||
}
|
||||
// The last open() is in flight, so no timer is pending for retryNow to advance.
|
||||
expect(scheduler.isScheduled).toBe(false)
|
||||
expect(scheduler.retryNow()).toBe(false)
|
||||
expect(scheduler.attemptCount).toBe(0)
|
||||
})
|
||||
|
||||
it('does nothing when no reconnect is pending', () => {
|
||||
const scheduler = new SharedControlReconnectScheduler()
|
||||
expect(scheduler.retryNow()).toBe(false)
|
||||
expect(scheduler.attemptCount).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Buffer } from 'node:buffer'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { RemoteRuntimeClientError } from './remote-runtime-client-error'
|
||||
import {
|
||||
RECOVERABLE_CODES,
|
||||
RECOVERABLE_MESSAGE_FRAGMENTS,
|
||||
isRecoverableRemoteRuntimeConnectionError,
|
||||
isRuntimeRpcQueueOverloadError,
|
||||
@@ -411,12 +410,6 @@ function classifyByMessageOnly(pair: TransportErrorPair): boolean {
|
||||
}
|
||||
|
||||
describe('transport error code/message classification agreement', () => {
|
||||
it('enumerates every reachable coded producer', () => {
|
||||
// Use a floor so corpus additions pass; lower deliberately only for genuinely removed producers (#12667 originally enumerated 34).
|
||||
expect(CODED_TRANSPORT_ERRORS.length).toBeGreaterThanOrEqual(59)
|
||||
expect(CODED_TRANSPORT_ERRORS.every((pair) => typeof pair.code === 'string')).toBe(true)
|
||||
})
|
||||
|
||||
it('never classifies a coded error as fatal while its own message reads as recoverable', () => {
|
||||
const violations = CODED_TRANSPORT_ERRORS.filter(
|
||||
(pair) => !isRecoverableRemoteRuntimeConnectionError(pair) && classifyByMessageOnly(pair)
|
||||
@@ -450,33 +443,6 @@ describe('transport error code/message classification agreement', () => {
|
||||
expect(misclassified).toEqual([])
|
||||
})
|
||||
|
||||
it('backs every recoverable message fragment with a producer in the corpus', () => {
|
||||
const allMessages = [...CODED_TRANSPORT_ERRORS, ...CODELESS_TRANSPORT_ERRORS].map((pair) =>
|
||||
pair.message.toLowerCase()
|
||||
)
|
||||
const unbacked = RECOVERABLE_MESSAGE_FRAGMENTS.filter(
|
||||
(fragment) => !allMessages.some((message) => message.includes(fragment))
|
||||
).map(
|
||||
(fragment) =>
|
||||
`recoverable fragment "${fragment}" matches no producer in this corpus — add the producer that emits it, or drop the fragment.`
|
||||
)
|
||||
expect(unbacked).toEqual([])
|
||||
})
|
||||
|
||||
it('backs every recoverable code with a producer in the corpus', () => {
|
||||
// 'reconnecting' is vocabulary borrowed from the SSH/runtime status states; no error
|
||||
// producer raises it. Kept recoverable defensively.
|
||||
const codesWithoutProducer = new Set(['reconnecting'])
|
||||
const corpusCodes = new Set(CODED_TRANSPORT_ERRORS.map((pair) => pair.code))
|
||||
const unbacked = [...RECOVERABLE_CODES]
|
||||
.filter((code) => !corpusCodes.has(code) && !codesWithoutProducer.has(code))
|
||||
.map(
|
||||
(code) =>
|
||||
`recoverable code "${code}" matches no producer in this corpus — add the producer that raises it so its message is checked, or declare it producer-less here.`
|
||||
)
|
||||
expect(unbacked).toEqual([])
|
||||
})
|
||||
|
||||
it('classifies remote_runtime_busy recoverable with or without its code (STA-3479)', () => {
|
||||
for (const pair of BUSY_TRANSPORT_ERRORS) {
|
||||
expect(isRecoverableRemoteRuntimeConnectionError(pair)).toBe(true)
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { getRemoteRuntimeRequestAdmissionEvidence } from '../requests/remote-runtime-prepared-request-admission'
|
||||
import { remoteRuntimeClientCapabilities } from '../../remote-runtime-client-capabilities'
|
||||
import { RemoteRuntimeSharedControlConnection } from './remote-runtime-shared-control-connection'
|
||||
import * as sharedControlProtocol from './remote-runtime-shared-control-protocol'
|
||||
import { isRuntimeSubscriptionReplayResponse } from '../../runtime-subscription-replay'
|
||||
import {
|
||||
closeSharedControlTestServers,
|
||||
@@ -81,10 +80,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('does not expose a binary sender on the shared control protocol surface', () => {
|
||||
expect('sendSharedControlEncryptedBinary' in sharedControlProtocol).toBe(false)
|
||||
})
|
||||
|
||||
it('releases a pending request when the socket send throws', async () => {
|
||||
const connection = new RemoteRuntimeSharedControlConnection(
|
||||
pairedRuntimeConnectionFromOffer({
|
||||
@@ -122,36 +117,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('replaces a stuck pre-ready socket when a one-shot probe proves reachability', () => {
|
||||
const connection = new RemoteRuntimeSharedControlConnection(
|
||||
pairedRuntimeConnectionFromOffer({
|
||||
v: 2,
|
||||
endpoint: 'ws://127.0.0.1:1',
|
||||
deviceToken: 'token',
|
||||
publicKeyB64: Buffer.from(new Uint8Array(32).fill(1)).toString('base64')
|
||||
})
|
||||
)
|
||||
const close = vi.fn()
|
||||
const cleanup = vi.fn()
|
||||
const open = vi.fn()
|
||||
const unsafe = connection as unknown as {
|
||||
state: string
|
||||
ws: { readyState: number; close: () => void } | null
|
||||
socketCleanup: (() => void) | null
|
||||
open: () => void
|
||||
}
|
||||
unsafe.state = 'awaiting_ready'
|
||||
unsafe.ws = { readyState: 0, close }
|
||||
unsafe.socketCleanup = cleanup
|
||||
unsafe.open = open
|
||||
|
||||
connection.reconnectNow()
|
||||
|
||||
expect(cleanup).toHaveBeenCalledOnce()
|
||||
expect(close).toHaveBeenCalledOnce()
|
||||
expect(open).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps a waiting request alive when a reachability probe replaces its pre-ready socket', async () => {
|
||||
const server = await createServer({ suppressReadyFrameCount: 1 })
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
@@ -257,29 +222,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('keeps many logical subscriptions on one authenticated WebSocket', async () => {
|
||||
const server = await createServer()
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
const subscriptions = await Promise.all(
|
||||
Array.from({ length: 35 }, (_value, index) =>
|
||||
connection.subscribe('runtime.clientEvents.subscribe', { index }, 1000, {
|
||||
onResponse: vi.fn(),
|
||||
onError: vi.fn()
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
await vi.waitFor(() => expect(server.requests).toHaveLength(35))
|
||||
|
||||
expect(server.connectionCount()).toBe(1)
|
||||
expect(
|
||||
server.requests.every((request) => request.method === 'runtime.clientEvents.subscribe')
|
||||
).toBe(true)
|
||||
|
||||
subscriptions.forEach((subscription) => subscription.close())
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('reconnects and replays passive subscriptions without closing them', async () => {
|
||||
const server = await createServer({ closeAfterFirstStreamingResponse: true })
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
@@ -310,74 +252,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('keeps passive subscriptions alive after reaching the capped reconnect delay', async () => {
|
||||
const server = await createServer()
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
const onClose = vi.fn()
|
||||
|
||||
const unsafe = connection as unknown as {
|
||||
reconnect: {
|
||||
attempt: number
|
||||
scheduleWithDefaultBackoff: (intentionallyClosed: boolean, open: () => void) => void
|
||||
}
|
||||
subscriptions: Map<string, unknown>
|
||||
}
|
||||
unsafe.reconnect.attempt = 7
|
||||
unsafe.subscriptions.set('sub-1', {
|
||||
requestId: 'sub-1',
|
||||
method: 'runtime.clientEvents.subscribe',
|
||||
params: null,
|
||||
callbacks: { onResponse: vi.fn(), onError: vi.fn(), onClose },
|
||||
sent: false,
|
||||
closed: false,
|
||||
closeAfterReady: false,
|
||||
remoteSubscriptionId: null
|
||||
})
|
||||
|
||||
unsafe.reconnect.scheduleWithDefaultBackoff(false, () => {})
|
||||
|
||||
expect(onClose).not.toHaveBeenCalled()
|
||||
expect(connection.getDiagnostics()).toMatchObject({
|
||||
state: 'reconnecting',
|
||||
reconnectAttempt: 8,
|
||||
subscriptionCount: 1
|
||||
})
|
||||
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('resets reconnect attempts after a stable authenticated ready period', async () => {
|
||||
const server = await createServer()
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing, {
|
||||
reconnectStableResetMs: 50
|
||||
})
|
||||
|
||||
await expect(connection.request('worktree.ps', undefined, 1000)).resolves.toMatchObject({
|
||||
ok: true
|
||||
})
|
||||
;(connection as unknown as { reconnect: { attempt: number } }).reconnect.attempt = 3
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(connection.getDiagnostics()).toMatchObject({ reconnectAttempt: 0 })
|
||||
)
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('removes ready waiters when a one-shot request times out during handshake', async () => {
|
||||
const server = await createServer({ suppressReadyFrame: true })
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
const unsafe = connection as unknown as {
|
||||
readyWaiters: unknown[]
|
||||
pendingRequests: Map<string, unknown>
|
||||
}
|
||||
|
||||
await expect(connection.request('worktree.ps', undefined, 25)).rejects.toThrow('Timed out')
|
||||
|
||||
await vi.waitFor(() => expect(unsafe.readyWaiters).toHaveLength(0))
|
||||
expect(unsafe.pendingRequests.size).toBe(0)
|
||||
connection.close()
|
||||
})
|
||||
|
||||
it('cleans up an id-scoped subscription closed before its ready response', async () => {
|
||||
const server = await createServer({ delaySubscriptionReady: true })
|
||||
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
|
||||
|
||||
-9
@@ -74,15 +74,6 @@ describe('shared control keepalive timeout refresh semantics', () => {
|
||||
await expect(promise).resolves.toMatchObject({ ok: true })
|
||||
})
|
||||
|
||||
it('fires the deadline for a short RPC when no keepalives arrive', async () => {
|
||||
const { pendingRequests, promise } = startRequest()
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1001)
|
||||
|
||||
await expect(promise).rejects.toThrow()
|
||||
expect(pendingRequests.size).toBe(0)
|
||||
})
|
||||
|
||||
it('aborts one pending request without disturbing its peer', async () => {
|
||||
const pendingRequests = new Map<string, SharedControlPendingRequest<unknown>>()
|
||||
const controller = new AbortController()
|
||||
|
||||
-25
@@ -92,31 +92,6 @@ describe('shared-control standing intent', () => {
|
||||
scheduler.clear()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ manual: false, paused: false, subscriptions: 0, scheduled: true },
|
||||
{ manual: false, paused: false, subscriptions: 1, scheduled: true },
|
||||
{ manual: false, paused: true, subscriptions: 0, scheduled: false },
|
||||
{ manual: false, paused: true, subscriptions: 1, scheduled: true },
|
||||
{ manual: true, paused: false, subscriptions: 0, scheduled: false },
|
||||
{ manual: true, paused: false, subscriptions: 1, scheduled: false },
|
||||
{ manual: true, paused: true, subscriptions: 0, scheduled: false },
|
||||
{ manual: true, paused: true, subscriptions: 1, scheduled: false }
|
||||
])(
|
||||
'applies the intent/capability truth table: $manual/$paused/$subscriptions',
|
||||
({ manual, paused, subscriptions, scheduled }) => {
|
||||
vi.useFakeTimers()
|
||||
const connection = testConnection({ manual, paused })
|
||||
if (subscriptions > 0) {
|
||||
addLogicalSubscription(connection)
|
||||
}
|
||||
|
||||
closeCurrentGeneration(connection)
|
||||
|
||||
expect(connection.getDiagnostics().state === 'reconnecting').toBe(scheduled)
|
||||
connection.close()
|
||||
}
|
||||
)
|
||||
|
||||
it('advances an armed idle retry without recreating cleared work', () => {
|
||||
vi.useFakeTimers()
|
||||
const connection = testConnection()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
RECOVERABLE_CODES,
|
||||
RECOVERABLE_MESSAGE_FRAGMENTS,
|
||||
isRecoverableRemoteRuntimeConnectionError,
|
||||
isRuntimeRpcQueueOverloadError,
|
||||
toRemoteRuntimeClientErrorLike
|
||||
@@ -64,33 +62,6 @@ describe('remote runtime client error classification', () => {
|
||||
expect(isRecoverableRemoteRuntimeConnectionError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it('pins the recoverable codes and message fragments', () => {
|
||||
expect([...RECOVERABLE_CODES].toSorted()).toEqual([
|
||||
'reconnecting',
|
||||
'remote_runtime_busy',
|
||||
'remote_runtime_unavailable',
|
||||
'runtime_rpc_queue_overloaded',
|
||||
'runtime_timeout',
|
||||
'runtime_unavailable',
|
||||
'timeout'
|
||||
])
|
||||
expect(RECOVERABLE_MESSAGE_FRAGMENTS).toEqual([
|
||||
'could not connect to the remote orca runtime',
|
||||
'remote orca runtime closed the connection',
|
||||
'remote orca runtime connection closed',
|
||||
'remote orca runtime is not connected',
|
||||
'remote runtime call queue is full',
|
||||
'remote runtime connection closed',
|
||||
'remote runtime connection wait limit reached',
|
||||
'remote runtime request limit reached',
|
||||
'remote runtime subscription limit reached',
|
||||
'remote runtime subscription memory limit reached',
|
||||
'remote runtime subscription closed before it started',
|
||||
'remote terminal stream is not connected',
|
||||
'timed out waiting for the remote orca runtime'
|
||||
])
|
||||
})
|
||||
|
||||
it.each([
|
||||
'runtime_manually_disconnected',
|
||||
'ssh_user_disconnected',
|
||||
@@ -99,17 +70,4 @@ describe('remote runtime client error classification', () => {
|
||||
])('does not treat client code %s as recoverable', (code) => {
|
||||
expect(isRecoverableRemoteRuntimeConnectionError({ code, message: 'refused' })).toBe(false)
|
||||
})
|
||||
|
||||
it.each(RECOVERABLE_MESSAGE_FRAGMENTS)(
|
||||
'recovers a code-stripped message containing %s',
|
||||
(fragment) => {
|
||||
expect(
|
||||
isRecoverableRemoteRuntimeConnectionError(
|
||||
toRemoteRuntimeClientErrorLike(
|
||||
new Error(`Error invoking remote method 'x': Error: ${fragment}.`)
|
||||
)
|
||||
)
|
||||
).toBe(true)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -136,27 +136,6 @@ describe('createUpdaterQuitAbortRelay', () => {
|
||||
expect(aborted).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('resets a prepared restart on a linux package-install recovery status', () => {
|
||||
const eventTarget = new EventTarget()
|
||||
const aborted = vi.fn()
|
||||
eventTarget.addEventListener('update-restart-aborted', aborted)
|
||||
const relay = createUpdaterQuitAbortRelay(eventTarget, 'update-restart-aborted')
|
||||
relay.markPrepared()
|
||||
|
||||
relay.handleStatus({
|
||||
state: 'error',
|
||||
message: 'No authentication agent found.',
|
||||
recovery: {
|
||||
kind: 'linux-package-install',
|
||||
packageType: 'deb',
|
||||
reason: 'authentication-agent-unavailable',
|
||||
version: '1.0.61'
|
||||
}
|
||||
} satisfies UpdateStatus)
|
||||
|
||||
expect(aborted).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('ignores updater errors when no update restart was prepared', () => {
|
||||
const eventTarget = new EventTarget()
|
||||
const aborted = vi.fn()
|
||||
|
||||
@@ -2,18 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from './repo-display-labels'
|
||||
|
||||
describe('getRepoDisplayLabelsByPath', () => {
|
||||
it('keeps non-colliding repository names basename-only', () => {
|
||||
const labels = getRepoDisplayLabelsByPath([
|
||||
{ path: '/workspace/platform/web', displayName: 'web' },
|
||||
{ path: '/workspace/platform/worker', displayName: 'worker' }
|
||||
])
|
||||
|
||||
expect(labels.get(getRepoDisplayLabelKey({ path: '/workspace/platform/web' }))).toBe('web')
|
||||
expect(labels.get(getRepoDisplayLabelKey({ path: '/workspace/platform/worker' }))).toBe(
|
||||
'worker'
|
||||
)
|
||||
})
|
||||
|
||||
it('adds the minimal real parent suffix only for colliding basenames', () => {
|
||||
const labels = getRepoDisplayLabelsByPath([
|
||||
{ path: '/workspace/platform/web', displayName: 'web' },
|
||||
@@ -80,15 +68,6 @@ describe('getRepoDisplayLabelsByPath', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps identical project paths short across local and paired execution hosts', () => {
|
||||
const local = { path: '/home/user/projects/app', displayName: 'app' }
|
||||
const paired = { ...local, executionHostId: 'runtime:home' as const }
|
||||
const labels = getRepoDisplayLabelsByPath([local, paired])
|
||||
expect(labels.get(getRepoDisplayLabelKey(local))).toBe('app')
|
||||
expect(labels.get(getRepoDisplayLabelKey(paired))).toBe('app')
|
||||
expect(labels.size).toBe(2)
|
||||
})
|
||||
|
||||
it('disambiguates collisions only within their owning host, even when paths overlap', () => {
|
||||
const local = { path: '/workspace/payments/api', displayName: 'api' }
|
||||
const sibling = { path: '/workspace/billing/api', displayName: 'api' }
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { describe, expect, expectTypeOf, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toRuntimeExecutionHostId, toSshExecutionHostId } from './execution-host'
|
||||
import {
|
||||
getRepoHostIdentityForParts,
|
||||
getRepoRegistrationHostIds,
|
||||
type HostScopedRepoKey
|
||||
} from './repo-host-identity'
|
||||
import { getRepoHostIdentityForParts, getRepoRegistrationHostIds } from './repo-host-identity'
|
||||
|
||||
// Why: these keys index persisted and cross-module repo state, so the bytes are frozen (D25).
|
||||
describe('repo host identity golden bytes', () => {
|
||||
@@ -21,17 +17,6 @@ describe('repo host identity golden bytes', () => {
|
||||
] as const)('composes %s on %s', (repoId, hostId, expected) => {
|
||||
expect(getRepoHostIdentityForParts(repoId, hostId)).toBe(expected)
|
||||
})
|
||||
|
||||
it('brands the key so a plain string cannot pose as one', () => {
|
||||
const key = getRepoHostIdentityForParts('repo-1', 'local')
|
||||
expectTypeOf(key).toEqualTypeOf<HostScopedRepoKey>()
|
||||
expectTypeOf<HostScopedRepoKey>().toExtend<string>()
|
||||
expectTypeOf<string>().not.toExtend<HostScopedRepoKey>()
|
||||
const raw: string = key
|
||||
// @ts-expect-error a raw string is not a host-scoped key
|
||||
const forged: HostScopedRepoKey = 'local\0repo-1'
|
||||
expect(raw).toBe(forged)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRepoRegistrationHostIds', () => {
|
||||
|
||||
@@ -554,20 +554,6 @@ describe('RepoRefMaintenance single-flight and backoff', () => {
|
||||
expect(spans).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('records a failure when the pack left the backlog in place', async () => {
|
||||
const refs = await refsDirectoryWith(THRESHOLD + 2)
|
||||
const { maintenance, spans } = createHarness({
|
||||
packRefs: async () => {
|
||||
throw new Error('permission denied')
|
||||
}
|
||||
})
|
||||
|
||||
maintenance.arm(target('local::/denied/.git', refs, () => Promise.reject(new Error('denied'))))
|
||||
await elapseQuietPeriod(maintenance)
|
||||
|
||||
expect(attributesOf(spans[0])['repo.maintenance_outcome']).toBe('failed')
|
||||
})
|
||||
|
||||
it('records a failure instead of throwing, and backs off', async () => {
|
||||
const refs = await refsDirectoryWith(THRESHOLD + 2)
|
||||
const { maintenance, spans, packRefs } = createHarness({
|
||||
|
||||
@@ -22,18 +22,6 @@ describe('getRepoRoutingConnectionId', () => {
|
||||
expect(getRepoRoutingConnectionId(repo)).toBe(expected)
|
||||
})
|
||||
|
||||
it('never answers local for a row whose raw connectionId is set', () => {
|
||||
for (const executionHostId of [
|
||||
undefined,
|
||||
'local',
|
||||
'runtime:env',
|
||||
'ssh:gpu',
|
||||
'ssh:other'
|
||||
] as const) {
|
||||
expect(getRepoRoutingConnectionId({ executionHostId, connectionId: 'gpu' })).not.toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
it('lets executionHostId win on a contradictory SSH row, as getRepoExecutionHostId does', () => {
|
||||
expect(getRepoRoutingConnectionId({ executionHostId: 'ssh:b', connectionId: 'a' })).toBe('b')
|
||||
})
|
||||
|
||||
@@ -9,12 +9,10 @@ import {
|
||||
} from './agent-detection'
|
||||
import { isDecorativeAgentTitleFrameChange } from './agent-decorative-title-signature'
|
||||
import { stripLeadingAgentTitleDecoration } from './agent-title-decoration'
|
||||
import { resolveExplicitTerminalTitleAgentType } from './terminal-title-agent-type'
|
||||
|
||||
// Titles captured from real `claude` binaries running the same one-line prompt.
|
||||
// 2.1.228 swapped the busy spinner from braille to quarter circles, which read as
|
||||
// "no agent" and made the tracker report a confirmed exit mid-turn (#13889).
|
||||
const BUSY_2_1_227 = ['⠂ Claude Code', '⠐ Claude Code', '⠂ Say hi in one word'] as const
|
||||
const BUSY_2_1_228 = ['◐ Claude Code', '◑ Claude Code', '◑ Say hi in one word'] as const
|
||||
|
||||
const CAPTURED_2_1_228_TURN = [
|
||||
@@ -58,30 +56,12 @@ describe('Claude Code quarter-circle busy titles (#13889)', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('matches 2.1.227 status and identity frame for frame', () => {
|
||||
BUSY_2_1_228.forEach((title, index) => {
|
||||
const braille = BUSY_2_1_227[index]
|
||||
expect(detectAgentStatusFromTitle(title)).toBe(detectAgentStatusFromTitle(braille))
|
||||
expect(getAgentLabel(title)).toBe(getAgentLabel(braille))
|
||||
expect(resolveExplicitTerminalTitleAgentType(title)).toBe(
|
||||
resolveExplicitTerminalTitleAgentType(braille)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('never confirms an agent exit across a real 2.1.228 turn', () => {
|
||||
const events = trackTurn(CAPTURED_2_1_228_TURN)
|
||||
expect(events).not.toContain('exited')
|
||||
expect(events).toEqual(['working', 'idle'])
|
||||
})
|
||||
|
||||
it('tracks the 2.1.228 turn exactly like the 2.1.227 turn', () => {
|
||||
const brailleTurn = CAPTURED_2_1_228_TURN.map((title) =>
|
||||
title.replace('◐', '⠂').replace('◑', '⠐')
|
||||
)
|
||||
expect(trackTurn(CAPTURED_2_1_228_TURN)).toEqual(trackTurn(brailleTurn))
|
||||
})
|
||||
|
||||
it('strips the spinner from stale exit titles and displayed labels', () => {
|
||||
expect(clearWorkingIndicators('◐ Say hi in one word')).toBe('Say hi in one word')
|
||||
expect(stripLeadingAgentTitleDecoration('◐ Claude Code')).toBe('Claude Code')
|
||||
|
||||
@@ -51,22 +51,12 @@ describe('sharesWorktreeLineageBoundary', () => {
|
||||
...overrides
|
||||
})
|
||||
|
||||
it('accepts the same host', () => {
|
||||
expect(sharesWorktreeLineageBoundary(boundary(), boundary())).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['child host', boundary({ hostId: undefined }), boundary()],
|
||||
['parent host', boundary(), boundary({ hostId: undefined })]
|
||||
])('treats an undefined %s as compatible', (_label, child, parent) => {
|
||||
expect(sharesWorktreeLineageBoundary(child, parent)).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects a different known host', () => {
|
||||
expect(sharesWorktreeLineageBoundary(boundary(), boundary({ hostId: 'ssh:remote' }))).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('projectResolvedWorktreeLineage', () => {
|
||||
|
||||
Reference in New Issue
Block a user