mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
test: consolidate refusal cases and repair boundary coverage (#33098)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -97,6 +97,8 @@ describe('trim pipeline', () => {
|
||||
const image = solidSquareWithInset(100, 60)
|
||||
const cropped = cropImage(image, findOpaqueBounds(image))
|
||||
expect(cropped.width).toBe(60)
|
||||
expect(cropped.height).toBe(60)
|
||||
expect(cropped.data).toEqual(solidSquareWithInset(60, 60).data)
|
||||
const squared = squareWithMargin(cropped, 0.04)
|
||||
// Glyph (60) plus ~4% margin per side keeps it near-full-bleed.
|
||||
expect(glyphFillFraction(squared)).toBeGreaterThanOrEqual(0.9)
|
||||
@@ -108,6 +110,12 @@ describe('trim pipeline', () => {
|
||||
const filled = squareWithMargin(cropImage(image, findOpaqueBounds(image)))
|
||||
const small = resizeImage(filled, 32, 32)
|
||||
expect(small.width).toBe(32)
|
||||
const edge = {
|
||||
width: 2,
|
||||
height: 1,
|
||||
data: Buffer.from([200, 100, 50, 255, 0, 0, 0, 0])
|
||||
}
|
||||
expect(resizeImage(edge, 1, 1).data).toEqual(Buffer.from([200, 100, 50, 128]))
|
||||
expect(glyphFillFraction(small)).toBeGreaterThanOrEqual(0.9)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,15 +9,23 @@ import {
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { verifyPackageCliBin } from './verify-cli-bin.mjs'
|
||||
|
||||
const roots = []
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
/** Builds a temporary Orca-style project fixture with a compiled CLI entrypoint. */
|
||||
function makeProjectWithCli(
|
||||
content,
|
||||
{ mode = 0o755, rootPackageType, writeOutPackageJson = true } = {}
|
||||
) {
|
||||
const projectDir = mkdtempSync(path.join(tmpdir(), 'orca-cli-bin-'))
|
||||
roots.push(projectDir)
|
||||
const cliPath = path.join(projectDir, 'out', 'cli', 'index.js')
|
||||
const outPackageJsonPath = path.join(projectDir, 'out', 'package.json')
|
||||
mkdirSync(path.dirname(cliPath), { recursive: true })
|
||||
@@ -39,12 +47,16 @@ function makeProjectWithCli(
|
||||
describe('verifyPackageCliBin', () => {
|
||||
it('accepts a non-empty Node entrypoint and can run help through Node', () => {
|
||||
const { projectDir, cliPath } = makeProjectWithCli(
|
||||
'#!/usr/bin/env node\nif (process.argv.includes("--help")) process.exit(0)\n'
|
||||
'#!/usr/bin/env node\nrequire("node:fs").writeFileSync("help-receipt.json", JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd() }))\nif (!process.argv.includes("--help")) process.exit(9)\n'
|
||||
)
|
||||
|
||||
expect(verifyPackageCliBin({ projectDir, runHelp: true })).toMatchObject({
|
||||
binPath: cliPath
|
||||
})
|
||||
expect(JSON.parse(readFileSync(path.join(projectDir, 'help-receipt.json'), 'utf8'))).toEqual({
|
||||
args: ['--help'],
|
||||
cwd: projectDir
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects an empty package bin target', () => {
|
||||
|
||||
@@ -60,6 +60,10 @@ describe.skipIf(process.platform === 'win32')('static ELF and remote musl payloa
|
||||
|
||||
it.each([
|
||||
['missing program headers', 'Dynamic Section:\n'],
|
||||
[
|
||||
'version references without a dynamic segment',
|
||||
`${STATIC_HEADERS}Version References:\n required from libc.so.6:\n 0x0 0x00 02 GLIBC_2.17\n`
|
||||
],
|
||||
['dynamic segment', `${STATIC_HEADERS} DYNAMIC off 0x0000000000001000\n`],
|
||||
['interpreter segment', `${STATIC_HEADERS} INTERP off 0x0000000000001000\n`],
|
||||
['dependency without a dynamic segment', `${STATIC_HEADERS} NEEDED libc.so.6\n`]
|
||||
|
||||
@@ -126,6 +126,51 @@ describe.skipIf(process.platform === 'win32')('terminal host round trip', () =>
|
||||
}
|
||||
}
|
||||
|
||||
it('accepts a valid peer round trip and cleans up after observing the boot request', async () => {
|
||||
const messagesPath = join(root, 'messages.jsonl')
|
||||
const entry = fakeDaemon(`(socket) => {
|
||||
let buffer = ''
|
||||
socket.on('data', (chunk) => {
|
||||
buffer += chunk
|
||||
let newline
|
||||
while ((newline = buffer.indexOf('\\n')) !== -1) {
|
||||
const message = JSON.parse(buffer.slice(0, newline))
|
||||
buffer = buffer.slice(newline + 1)
|
||||
fs.appendFileSync(${JSON.stringify(messagesPath)}, JSON.stringify(message) + '\\n')
|
||||
if (message.type === 'hello') {
|
||||
if (message.role === 'stream') globalThis.stream = socket
|
||||
socket.write(JSON.stringify({ ok: true }) + '\\n')
|
||||
} else if (message.type === 'createOrAttach') {
|
||||
globalThis.stream.write(JSON.stringify({ payload: { data: 'OK 42' } }) + '\\n')
|
||||
}
|
||||
}
|
||||
})
|
||||
}`)
|
||||
const result = await roundTrip(entry, 1_500)
|
||||
expect(result.error).toBe(null)
|
||||
expect(result.daemonAlive).toBe(false)
|
||||
expect(result.scratchDirs).toEqual([])
|
||||
const messages = readFileSync(messagesPath, 'utf8').trim().split('\n').map(JSON.parse)
|
||||
expect(messages.slice(0, 2).map(({ type, role, token }) => ({ type, role, token }))).toEqual([
|
||||
{ type: 'hello', role: 'control', token: 'tok' },
|
||||
{ type: 'hello', role: 'stream', token: 'tok' }
|
||||
])
|
||||
expect(messages[0].clientId).toBe(messages[1].clientId)
|
||||
expect(messages[0].clientId).toMatch(/^[0-9a-f-]{36}$/)
|
||||
expect(messages).toHaveLength(3)
|
||||
expect(messages[2]).toMatchObject({
|
||||
id: 'boot-1',
|
||||
type: 'createOrAttach',
|
||||
payload: {
|
||||
sessionId: 'terminal-host-boot',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
command: 'echo "OK $((6*7))" && exit'
|
||||
}
|
||||
})
|
||||
expect(messages[2].payload.cwd.startsWith(join(root, 'oth-'))).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects by the deadline and cleans up when the daemon never answers the hello', async () => {
|
||||
const result = await roundTrip(fakeDaemon('() => {}'), 1_500)
|
||||
expect(result.error?.message).toMatch(/timed out waiting for the control hello reply/)
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('scheduleMobileClaudeAnswer', () => {
|
||||
it('writes each question body then its Enter, paced per step, one Enter per line', () => {
|
||||
const events: string[] = []
|
||||
const timers = scheduleMobileClaudeAnswer(
|
||||
['', 'b', 'c'], // blank middle answer must still get its own body + Enter
|
||||
['', 'b', 'c'], // A blank answer still gets its own body + Enter.
|
||||
(line) => events.push(`body:${line}`),
|
||||
() => events.push('enter')
|
||||
)
|
||||
@@ -29,7 +29,9 @@ describe('scheduleMobileClaudeAnswer', () => {
|
||||
|
||||
vi.advanceTimersByTime(0)
|
||||
expect(events).toEqual(['body:'])
|
||||
vi.advanceTimersByTime(MOBILE_NATIVE_CHAT_SUBMIT_DELAY_MS)
|
||||
vi.advanceTimersByTime(MOBILE_NATIVE_CHAT_SUBMIT_DELAY_MS - 1)
|
||||
expect(events).toEqual(['body:'])
|
||||
vi.advanceTimersByTime(1)
|
||||
expect(events).toEqual(['body:', 'enter'])
|
||||
vi.advanceTimersByTime(MOBILE_NATIVE_CHAT_ADVANCE_BUFFER_MS)
|
||||
expect(events).toEqual(['body:', 'enter', 'body:b'])
|
||||
|
||||
@@ -42,13 +42,21 @@ describe('isNoiseMessage', () => {
|
||||
expect(isNoiseMessage(msg('user', '<bash-brothers> is my band'))).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps assistant and tool turns', () => {
|
||||
it('keeps assistant, tool, and ordinary system turns', () => {
|
||||
expect(isNoiseMessage(msg('assistant', '<system-reminder> in prose'))).toBe(false)
|
||||
expect(isNoiseMessage(msg('tool', '<system-reminder> in prose'))).toBe(false)
|
||||
expect(isNoiseMessage(msg('system', 'Conversation interrupted'))).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps a user turn that carries tool results', () => {
|
||||
expect(isNoiseMessage(msg('user', '', [{ type: 'tool-result', output: 'ok' }]))).toBe(false)
|
||||
it('keeps tool-bearing user turns even when their text looks like harness noise', () => {
|
||||
for (const block of [
|
||||
{ type: 'tool-result', output: 'ok' },
|
||||
{ type: 'tool-call', name: 'Bash', input: { command: 'ls' } }
|
||||
] satisfies NativeChatMessage['blocks']) {
|
||||
expect(
|
||||
isNoiseMessage(msg('user', '', [{ type: 'text', text: '<task-notification>done' }, block]))
|
||||
).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('stripNoiseMessages removes only the noise', () => {
|
||||
|
||||
@@ -34,6 +34,14 @@ describe('mobile complete-pair stream windows', () => {
|
||||
})
|
||||
expect(result).toEqual({ kind: 'messages', messages: [...base, row('latest')] })
|
||||
expect(merger.indexById.get('opencode:oldest:reasoning')).toBe(0)
|
||||
const updated = { ...row('latest'), blocks: [{ type: 'text' as const, text: 'updated' }] }
|
||||
applyMobileNativeChatStreamFrame({
|
||||
merger,
|
||||
frame: { type: 'appended', messages: [updated] },
|
||||
limit: 40,
|
||||
replaceSnapshot: false
|
||||
})
|
||||
expect(merger.list).toEqual([...base, updated])
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('appendMobileNativeChatPending ordinals for repeated sends', () => {
|
||||
const MULTILINE = 'first line of the prompt\nsecond line of the prompt'
|
||||
|
||||
it('gives a repeated multi-line send the next ordinal', () => {
|
||||
const baseline = [userTurn('m1', 'unrelated')]
|
||||
const baseline = [userTurn('m1', MULTILINE)]
|
||||
const first = appendMobileNativeChatPending(
|
||||
{},
|
||||
KEY,
|
||||
@@ -63,7 +63,8 @@ describe('appendMobileNativeChatPending ordinals for repeated sends', () => {
|
||||
MULTILINE
|
||||
)
|
||||
|
||||
expect(both[KEY]?.map((item) => item.expectedOccurrence)).toEqual([1, 2])
|
||||
expect(sendOrigin(MULTILINE, baseline).baselineOccurrences).toBe(1)
|
||||
expect(both[KEY]?.map((item) => item.expectedOccurrence)).toEqual([2, 3])
|
||||
})
|
||||
|
||||
it('retires only the first echo when the first of two identical rows lands', () => {
|
||||
|
||||
@@ -26,6 +26,14 @@ describe('agent sleep confirmation', () => {
|
||||
confirmAgentHibernationCandidates(first.confirmationState, [firstCandidate]).candidates
|
||||
).toEqual([firstCandidate])
|
||||
|
||||
const absent = confirmAgentHibernationCandidates(first.confirmationState, [])
|
||||
expect(absent.confirmationState).toEqual({})
|
||||
const reappeared = confirmAgentHibernationCandidates(absent.confirmationState, [firstCandidate])
|
||||
expect(reappeared.candidates).toEqual([])
|
||||
expect(
|
||||
confirmAgentHibernationCandidates(reappeared.confirmationState, [firstCandidate]).candidates
|
||||
).toEqual([firstCandidate])
|
||||
|
||||
const changed = candidate({ signature: 'changed-signature' })
|
||||
expect(
|
||||
confirmAgentHibernationCandidates(first.confirmationState, [changed]).candidates
|
||||
|
||||
@@ -77,6 +77,9 @@ describe('hibernation PTY binding age', () => {
|
||||
it('expires an unseen binding once it is older than the idle window', () => {
|
||||
observe('pty-1', 1_000)
|
||||
recordHibernationBoundaryResolved(PANE, 1_000)
|
||||
observe(null, 1_000 + IDLE_MS)
|
||||
expect(getHibernationPtyBindingFirstSeenAtByPaneKey()[PANE]).toBe(1_000)
|
||||
expect(getHibernationBoundaryResolvedAtByPaneKey()[PANE]).toBe(1_000)
|
||||
observe(null, 1_000 + IDLE_MS + 1)
|
||||
expect(getHibernationPtyBindingFirstSeenAtByPaneKey()[PANE]).toBeUndefined()
|
||||
expect(getHibernationBoundaryResolvedAtByPaneKey()[PANE]).toBeUndefined()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
resetAgentHibernationCoordinatorForTests,
|
||||
startAgentHibernationCoordinator,
|
||||
stopAgentHibernationCoordinator
|
||||
} from './agent-hibernation-coordinator'
|
||||
@@ -17,21 +16,50 @@ describe('agent hibernation coordinator visibility wiring', () => {
|
||||
setVisibility('visible')
|
||||
resetStaleDocumentVisibilityForTesting()
|
||||
})
|
||||
afterEach(() => {
|
||||
resetAgentHibernationCoordinatorForTests()
|
||||
afterEach(async () => {
|
||||
const { resetAgentHibernationCoordinatorFixture } =
|
||||
await import('./agent-hibernation-coordinator-test-fixture')
|
||||
resetAgentHibernationCoordinatorFixture()
|
||||
resetStaleDocumentVisibilityForTesting()
|
||||
setVisibility('visible')
|
||||
vi.unstubAllGlobals()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('subscribes to the becoming-visible pass on start and unsubscribes on stop', () => {
|
||||
const add = vi.spyOn(document, 'addEventListener')
|
||||
const remove = vi.spyOn(document, 'removeEventListener')
|
||||
it('subscribes to the becoming-visible pass on start and unsubscribes on stop', async () => {
|
||||
// Keep happy-dom's event target when the state fixture stubs window.
|
||||
const browserWindow = window
|
||||
const { installEligibleState, NOW, LEAF } =
|
||||
await import('./agent-hibernation-coordinator-test-fixture')
|
||||
vi.stubGlobal('window', browserWindow)
|
||||
vi.useFakeTimers()
|
||||
const shutdown = installEligibleState(vi.fn().mockResolvedValue(undefined))
|
||||
|
||||
startAgentHibernationCoordinator({ intervalMs: 60_000, now: () => 0 })
|
||||
expect(add.mock.calls.some(([type]) => type === 'visibilitychange')).toBe(true)
|
||||
startAgentHibernationCoordinator({ intervalMs: 1_000, now: () => NOW })
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
expect(shutdown).not.toHaveBeenCalled()
|
||||
|
||||
setVisibility('hidden')
|
||||
await vi.advanceTimersByTimeAsync(3_000)
|
||||
expect(shutdown).not.toHaveBeenCalled()
|
||||
|
||||
setVisibility('visible')
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(shutdown).toHaveBeenCalledExactlyOnceWith('wt-bg', {
|
||||
paneKey: `tab-1:${LEAF}`,
|
||||
tabId: 'tab-1',
|
||||
leafId: LEAF,
|
||||
ptyId: 'pty-1'
|
||||
})
|
||||
|
||||
shutdown.mockClear()
|
||||
stopAgentHibernationCoordinator()
|
||||
expect(remove.mock.calls.some(([type]) => type === 'visibilitychange')).toBe(true)
|
||||
setVisibility('hidden')
|
||||
setVisibility('visible')
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
setVisibility('hidden')
|
||||
setVisibility('visible')
|
||||
await vi.advanceTimersByTimeAsync(3_000)
|
||||
expect(shutdown).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -147,8 +147,9 @@ describe('completed context clear projections', () => {
|
||||
|
||||
it('includes new sends but excludes older unknown sends even at the current fence', () => {
|
||||
const currentSend = { ...oldSend, clientMessageId: 'new-send', acceptedSequence: 6 }
|
||||
const boundarySend = { ...oldSend, clientMessageId: 'boundary-send', acceptedSequence: 5 }
|
||||
const rows = [user, clear, row(7, { kind: 'turn', turnId: 'new-turn', state: 'running' })]
|
||||
expect(agentSessionCurrentContextRows(rows, [oldSend, currentSend])).toEqual({
|
||||
expect(agentSessionCurrentContextRows(rows, [oldSend, boundarySend, currentSend])).toEqual({
|
||||
items: [rows[2]],
|
||||
submissions: [currentSend]
|
||||
})
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { MAX_CONTEXT_CATEGORIES, MAX_CONTEXT_MODEL_ID_CHARS } from './agent-session-context-usage'
|
||||
import {
|
||||
MAX_CONTEXT_CATEGORIES,
|
||||
MAX_CONTEXT_CATEGORY_NAME_CHARS,
|
||||
MAX_CONTEXT_MODEL_ID_CHARS
|
||||
} from './agent-session-context-usage'
|
||||
import {
|
||||
AgentSessionContextUsageSchema,
|
||||
isAdmissibleAgentSessionContextUsage
|
||||
@@ -32,8 +36,18 @@ describe('context usage admission', () => {
|
||||
capturedAt: 1
|
||||
}
|
||||
},
|
||||
{ used: report, future: { retained: true } }
|
||||
])('accepts canonical facts and leaves additive fields untouched (%j)', (value) => {
|
||||
{ used: report, future: { retained: true } },
|
||||
{
|
||||
used: {
|
||||
...report,
|
||||
model: 'x'.repeat(MAX_CONTEXT_MODEL_ID_CHARS),
|
||||
categories: Array.from({ length: MAX_CONTEXT_CATEGORIES }, () => ({
|
||||
name: 'x'.repeat(MAX_CONTEXT_CATEGORY_NAME_CHARS),
|
||||
tokens: 0
|
||||
}))
|
||||
}
|
||||
}
|
||||
])('accepts canonical facts and leaves inputs untouched (%j)', (value) => {
|
||||
const original = structuredClone(value)
|
||||
expect(isAdmissibleAgentSessionContextUsage(value)).toBe(true)
|
||||
expect(AgentSessionContextUsageSchema.safeParse(value).success).toBe(true)
|
||||
@@ -51,6 +65,12 @@ describe('context usage admission', () => {
|
||||
{
|
||||
used: { ...report, categories: Array(MAX_CONTEXT_CATEGORIES + 1).fill(report.categories[0]) }
|
||||
},
|
||||
{
|
||||
used: {
|
||||
...report,
|
||||
categories: [{ name: 'x'.repeat(MAX_CONTEXT_CATEGORY_NAME_CHARS + 1), tokens: 0 }]
|
||||
}
|
||||
},
|
||||
{ used: { ...report, categories: [{ name: 'messages', tokens: null }] } }
|
||||
])('rejects malformed facts and bounds just like parsing (%j)', (value) => {
|
||||
expect(isAdmissibleAgentSessionContextUsage(value)).toBe(false)
|
||||
|
||||
@@ -9,16 +9,6 @@ describe('definitive agent-session create refusals', () => {
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('agent_session_ownership_unknown')).toBe(false)
|
||||
})
|
||||
|
||||
it('leaves transport failures, timeouts and a missing code unknown', () => {
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('runtime_error')).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('remote_runtime_unavailable')).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('timeout')).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('runtime_timeout')).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal(undefined)).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal(null)).toBe(false)
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('')).toBe(false)
|
||||
})
|
||||
|
||||
it('counts a method an old host never registered as definitive', () => {
|
||||
expect(isDefinitiveAgentSessionCreateRefusal('method_not_found')).toBe(true)
|
||||
})
|
||||
@@ -41,6 +31,9 @@ describe('definitive agent-session create refusals', () => {
|
||||
|
||||
// Pins the RPC half (round-2 S5): transport and client-minted codes never allow a fallback.
|
||||
it.each([
|
||||
'runtime_error',
|
||||
'timeout',
|
||||
'',
|
||||
'runtime_unavailable',
|
||||
'remote_runtime_unavailable',
|
||||
'runtime_timeout',
|
||||
|
||||
Reference in New Issue
Block a user