Files
orca/src/main/runtime/runtime-rpc-mobile-method-allowlist.test.ts
T
Jinjing bb77a2b159 Add coverage for skill lock release and fix WebRTC test flakiness (#16846)
* test: add coverage for skill lock release and simplify WebRTC test

- Add test for cleanupReleasedSkillInstallLock handling rmdir races
- Improve error handling to cover all documented directory removal error codes
- Simplify WebRTC egress test to use localhost addresses consistently

* test: use network interface address for WebRTC egress probe

- Discover the first non-internal IPv4 address instead of hardcoding
  localhost, allowing the test to work in CI and varied environments
- Update proxy rules to use loopback designation for clarity
- Bind UDP socket to all interfaces (0.0.0.0) to receive on the
  discovered address
2026-08-27 16:09:43 -07:00

801 lines
26 KiB
TypeScript

import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { OrcaRuntimeRpcServer } from './runtime-rpc'
import { remoteRpcContentBudget } from '../../shared/remote-rpc-content-budget'
import { DeviceRegistry } from './device-registry'
import { createMobileRpcSurfaceRuntime } from './runtime-rpc-mobile-method-allowlist-fixtures'
describe('OrcaRuntimeRpcServer', () => {
it('limits mobile-scoped WebSocket tokens to the mobile RPC surface', async () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
const { runtime, mocks, expectedCodexResetScope } = createMobileRpcSurfaceRuntime()
const server = new OrcaRuntimeRpcServer({ runtime, userDataPath, enableWebSocket: false })
server['deviceRegistry'] = new DeviceRegistry(userDataPath)
const mobile = server['deviceRegistry']!.addDevice('phone', 'mobile')
const replies: Record<string, unknown>[] = []
const dispatch = async (request: Record<string, unknown>): Promise<void> => {
await server['handleWebSocketMessage'](
JSON.stringify(request),
(response) => replies.push(JSON.parse(response) as Record<string, unknown>),
() => {}
)
}
await dispatch({
id: 'req_forbidden',
// files.delete is a real registered RPC intentionally kept off the
// mobile allowlist — mobile clients must never delete host files.
method: 'files.delete',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1' }
})
await dispatch({
id: 'req_allowed',
method: 'status.get',
deviceToken: mobile.token
})
await dispatch({
id: 'req_settings_get',
method: 'settings.get',
deviceToken: mobile.token
})
await dispatch({
id: 'req_settings_update',
method: 'settings.update',
deviceToken: mobile.token,
params: { defaultTaskSource: 'linear' }
})
await dispatch({
id: 'req_github_projects',
method: 'github.project.listAccessible',
deviceToken: mobile.token,
params: {}
})
await dispatch({
id: 'req_project_issue_types',
method: 'github.project.listIssueTypesBySlug',
deviceToken: mobile.token,
params: { owner: 'stablyai', repo: 'orca' }
})
await dispatch({
id: 'req_project_labels',
method: 'github.project.listLabelsBySlug',
deviceToken: mobile.token,
params: { owner: 'stablyai', repo: 'orca' }
})
await dispatch({
id: 'req_project_assignees',
method: 'github.project.listAssignableUsersBySlug',
deviceToken: mobile.token,
params: { owner: 'stablyai', repo: 'orca', seedLogins: ['alex'] }
})
await dispatch({
id: 'req_project_update_issue',
method: 'github.project.updateIssueBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
number: 123,
updates: { title: 'New title' }
}
})
await dispatch({
id: 'req_project_update_issue_type',
method: 'github.project.updateIssueTypeBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
number: 123,
issueTypeId: 'type-1'
}
})
await dispatch({
id: 'req_project_update_field',
method: 'github.project.updateItemField',
deviceToken: mobile.token,
params: {
projectId: 'project-1',
itemId: 'item-1',
fieldId: 'field-1',
value: { kind: 'text', text: 'Ready' }
}
})
await dispatch({
id: 'req_project_clear_field',
method: 'github.project.clearItemField',
deviceToken: mobile.token,
params: {
projectId: 'project-1',
itemId: 'item-1',
fieldId: 'field-1'
}
})
await dispatch({
id: 'req_project_update_pr',
method: 'github.project.updatePullRequestBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
number: 456,
updates: { state: 'closed' }
}
})
await dispatch({
id: 'req_project_add_comment',
method: 'github.project.addIssueCommentBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
number: 123,
body: 'done'
}
})
await dispatch({
id: 'req_project_update_comment',
method: 'github.project.updateIssueCommentBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
commentId: 101,
body: 'edited'
}
})
await dispatch({
id: 'req_project_delete_comment',
method: 'github.project.deleteIssueCommentBySlug',
deviceToken: mobile.token,
params: {
owner: 'stablyai',
repo: 'orca',
commentId: 101
}
})
await dispatch({
id: 'req_github_update_issue',
method: 'github.updateIssue',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
number: 123,
updates: { title: 'New title', addLabels: ['bug'] }
}
})
await dispatch({
id: 'req_github_labels',
method: 'github.listLabels',
deviceToken: mobile.token,
params: { repo: 'id:repo-1' }
})
await dispatch({
id: 'req_github_assignees',
method: 'github.listAssignableUsers',
deviceToken: mobile.token,
params: { repo: 'id:repo-1' }
})
await dispatch({
id: 'req_github_add_comment',
method: 'github.addIssueComment',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
number: 123,
body: 'done'
}
})
await dispatch({
id: 'req_github_add_review_comment',
method: 'github.addPRReviewComment',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
commitId: 'abc123',
path: 'src/app.ts',
line: 10,
body: 'please fix'
}
})
await dispatch({
id: 'req_github_reply_review_comment',
method: 'github.addPRReviewCommentReply',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
commentId: 99,
body: 'fixed',
threadId: 'thread-1',
path: 'src/app.ts',
line: 10
}
})
await dispatch({
id: 'req_github_pr_file_contents',
method: 'github.prFileContents',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
path: 'src/app.ts',
status: 'modified',
headSha: 'abc123',
baseSha: 'def456'
}
})
await dispatch({
id: 'req_github_rerun_checks',
method: 'github.rerunPRChecks',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
headSha: 'abc123',
failedOnly: true
}
})
await dispatch({
id: 'req_github_resolve_thread',
method: 'github.resolveReviewThread',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
threadId: 'thread-1',
resolve: true
}
})
await dispatch({
id: 'req_github_file_viewed',
method: 'github.setPRFileViewed',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
pullRequestId: 'PR_kw',
path: 'src/app.ts',
viewed: true
}
})
await dispatch({
id: 'req_github_request_reviewers',
method: 'github.requestPRReviewers',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
reviewers: ['alex']
}
})
await dispatch({
id: 'req_github_merge_pr',
method: 'github.mergePR',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
prNumber: 456,
method: 'squash'
}
})
await dispatch({
id: 'req_gitlab_add_issue_comment',
method: 'gitlab.addIssueComment',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
number: 123,
body: 'done'
}
})
await dispatch({
id: 'req_gitlab_add_mr_comment',
method: 'gitlab.addMRComment',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
iid: 456,
body: 'ship it'
}
})
await dispatch({
id: 'req_gitlab_resolve_mr_discussion',
method: 'gitlab.resolveMRDiscussion',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
iid: 456,
discussionId: 'discussion-1',
resolved: true
}
})
await dispatch({
id: 'req_gitlab_merge_mr',
method: 'gitlab.mergeMR',
deviceToken: mobile.token,
params: {
repo: 'id:repo-1',
iid: 456,
method: 'merge'
}
})
await dispatch({
id: 'req_linear_search',
method: 'linear.searchIssues',
deviceToken: mobile.token,
params: { query: 'auth', limit: 10, workspaceId: 'workspace-1' }
})
await dispatch({
id: 'req_linear_select_workspace',
method: 'linear.selectWorkspace',
deviceToken: mobile.token,
params: { workspaceId: 'workspace-1' }
})
await dispatch({
id: 'req_linear_team_labels',
method: 'linear.teamLabels',
deviceToken: mobile.token,
params: { teamId: 'team-1', workspaceId: 'workspace-1' }
})
await dispatch({
id: 'req_linear_team_members',
method: 'linear.teamMembers',
deviceToken: mobile.token,
params: { teamId: 'team-1', workspaceId: 'workspace-1' }
})
await dispatch({
id: 'req_linear_add_comment',
method: 'linear.addIssueComment',
deviceToken: mobile.token,
params: { issueId: 'issue-1', workspaceId: 'workspace-1', body: 'done' }
})
await dispatch({
id: 'req_git_status',
method: 'git.status',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1' }
})
await dispatch({
id: 'req_git_push',
method: 'git.push',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', publish: true }
})
await dispatch({
id: 'req_git_upstream',
method: 'git.upstreamStatus',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1' }
})
await dispatch({
id: 'req_git_rebase_from_base',
method: 'git.rebaseFromBase',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', baseRef: 'origin/main' }
})
await dispatch({
id: 'req_git_bulk_stage',
method: 'git.bulkStage',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', filePaths: ['a.ts', 'b.ts'] }
})
await dispatch({
id: 'req_git_abort_merge',
method: 'git.abortMerge',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1' }
})
await dispatch({
id: 'req_git_abort_rebase',
method: 'git.abortRebase',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1' }
})
await dispatch({
id: 'req_git_bulk_unstage',
method: 'git.bulkUnstage',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', filePaths: ['c.ts'] }
})
await dispatch({
id: 'req_select_claude',
method: 'accounts.selectClaude',
deviceToken: mobile.token,
params: { accountId: 'claude-account' }
})
await dispatch({
id: 'req_select_codex',
method: 'accounts.selectCodex',
deviceToken: mobile.token,
params: { accountId: null }
})
await dispatch({
id: 'req_consume_codex_reset',
method: 'accounts.consumeCodexResetCredit',
deviceToken: mobile.token,
params: {
idempotencyKey: '11111111-1111-4111-8111-111111111111',
expectedScope: expectedCodexResetScope
}
})
await dispatch({
id: 'req_remove_claude',
method: 'accounts.removeClaude',
deviceToken: mobile.token,
params: { accountId: 'claude-account' }
})
await dispatch({
id: 'req_terminal_read',
method: 'terminal.read',
deviceToken: mobile.token,
params: { terminal: 'term-1' }
})
await dispatch({
id: 'req_files_open_diff',
method: 'files.openDiff',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', relativePath: 'docs/readme.md', staged: true }
})
await dispatch({
id: 'req_git_diff',
method: 'git.diff',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', filePath: 'docs/readme.md', staged: false }
})
await dispatch({
id: 'req_browser_tab_create',
method: 'browser.tabCreate',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', url: 'about:blank' }
})
await dispatch({
id: 'req_browser_viewport',
method: 'browser.viewport',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', page: 'page-1', width: 390, height: 844 }
})
await dispatch({
id: 'req_browser_certificate_proceed',
method: 'browser.certificate.proceed',
deviceToken: mobile.token,
params: {
worktree: 'id:wt-1',
page: 'page-1',
challengeId: 'challenge-1'
}
})
await dispatch({
id: 'req_browser_dialog_accept',
method: 'browser.dialogAccept',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', page: 'page-1', text: 'ok' }
})
await dispatch({
id: 'req_browser_dialog_dismiss',
method: 'browser.dialogDismiss',
deviceToken: mobile.token,
params: { worktree: 'id:wt-1', page: 'page-1' }
})
expect(replies).toContainEqual(
expect.objectContaining({
id: 'req_forbidden',
ok: false,
error: expect.objectContaining({ code: 'forbidden' })
})
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_allowed', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_settings_get', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_settings_update', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_github_projects', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_issue_types', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_project_labels', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_assignees', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_update_issue', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_update_issue_type', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_update_field', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_clear_field', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_update_pr', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_add_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_update_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_project_delete_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_update_issue', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_github_labels', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_assignees', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_add_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_add_review_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_reply_review_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_pr_file_contents', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_rerun_checks', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_resolve_thread', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_file_viewed', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_github_request_reviewers', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_github_merge_pr', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_gitlab_add_issue_comment', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_gitlab_add_mr_comment', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_gitlab_merge_mr', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_linear_search', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_linear_select_workspace', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_linear_team_labels', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_linear_team_members', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_linear_add_comment', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_status', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_push', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_upstream', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_git_rebase_from_base', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_bulk_stage', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_abort_merge', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_git_abort_rebase', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_git_bulk_unstage', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_select_claude', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_select_codex', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_consume_codex_reset', ok: true })
)
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_terminal_read', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_files_open_diff', ok: true }))
expect(replies).toContainEqual(expect.objectContaining({ id: 'req_git_diff', ok: true }))
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_browser_tab_create', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_browser_viewport', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({
id: 'req_browser_certificate_proceed',
ok: false,
error: expect.objectContaining({ code: 'forbidden' })
})
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_browser_dialog_accept', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({ id: 'req_browser_dialog_dismiss', ok: true })
)
expect(replies).toContainEqual(
expect.objectContaining({
id: 'req_remove_claude',
ok: false,
error: expect.objectContaining({ code: 'forbidden' })
})
)
expect(mocks.selectClaudeAccount).toHaveBeenCalledWith('claude-account')
expect(mocks.selectCodexAccount).toHaveBeenCalledWith(null)
expect(mocks.consumeCodexRateLimitResetCredit).toHaveBeenCalledWith(
'11111111-1111-4111-8111-111111111111',
expectedCodexResetScope
)
expect(mocks.readTerminal).toHaveBeenCalledWith('term-1', { cursor: undefined })
expect(mocks.getRuntimeGitStatus).toHaveBeenCalledWith('id:wt-1')
expect(mocks.pushRuntimeGit).toHaveBeenCalledWith('id:wt-1', true, undefined, undefined)
expect(mocks.getRuntimeGitUpstreamStatus).toHaveBeenCalledWith('id:wt-1')
expect(mocks.bulkStageRuntimeGitPaths).toHaveBeenCalledWith('id:wt-1', ['a.ts', 'b.ts'])
expect(mocks.abortRuntimeGitMerge).toHaveBeenCalledWith('id:wt-1')
expect(mocks.abortRuntimeGitRebase).toHaveBeenCalledWith('id:wt-1')
expect(mocks.bulkUnstageRuntimeGitPaths).toHaveBeenCalledWith('id:wt-1', ['c.ts'])
expect(mocks.openMobileDiff).toHaveBeenCalledWith('id:wt-1', 'docs/readme.md', true)
// A mobile WebSocket client is transport-capped; a local caller gets undefined here.
expect(mocks.getRuntimeGitDiff).toHaveBeenCalledWith(
'id:wt-1',
'docs/readme.md',
false,
undefined,
remoteRpcContentBudget('req_git_diff')
)
expect(mocks.browserTabCreate).toHaveBeenCalledWith(
{ worktree: 'id:wt-1', url: 'about:blank' },
{ pairedDeviceId: mobile.deviceId, clientKind: 'mobile' }
)
expect(mocks.browserSetViewport).toHaveBeenCalledWith({
worktree: 'id:wt-1',
page: 'page-1',
width: 390,
height: 844
})
expect(mocks.browserDialogAccept).toHaveBeenCalledWith({
worktree: 'id:wt-1',
page: 'page-1',
text: 'ok'
})
expect(mocks.browserDialogDismiss).toHaveBeenCalledWith({
worktree: 'id:wt-1',
page: 'page-1'
})
expect(mocks.listGitHubIssueTypesBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca'
})
expect(mocks.listGitHubLabelsBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca'
})
expect(mocks.listGitHubAssignableUsersBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
seedLogins: ['alex']
})
expect(mocks.updateGitHubIssueBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
number: 123,
updates: { title: 'New title' }
})
expect(mocks.updateGitHubIssueTypeBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
number: 123,
issueTypeId: 'type-1'
})
expect(mocks.updateGitHubPullRequestBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
number: 456,
updates: { state: 'closed' }
})
expect(mocks.addGitHubIssueCommentBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
number: 123,
body: 'done'
})
expect(mocks.updateGitHubIssueCommentBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
commentId: 101,
body: 'edited'
})
expect(mocks.deleteGitHubIssueCommentBySlug).toHaveBeenCalledWith({
owner: 'stablyai',
repo: 'orca',
commentId: 101
})
expect(mocks.updateRepoIssue).toHaveBeenCalledWith('id:repo-1', 123, {
title: 'New title',
addLabels: ['bug']
})
expect(mocks.listRepoLabels).toHaveBeenCalledWith('id:repo-1')
expect(mocks.listRepoAssignableUsers).toHaveBeenCalledWith('id:repo-1')
expect(mocks.addRepoIssueComment).toHaveBeenCalledWith('id:repo-1', 123, 'done', null)
expect(mocks.addRepoPRReviewComment).toHaveBeenCalledWith('id:repo-1', {
prNumber: 456,
commitId: 'abc123',
path: 'src/app.ts',
line: 10,
startLine: undefined,
body: 'please fix',
prRepo: null
})
expect(mocks.addRepoPRReviewCommentReply).toHaveBeenCalledWith('id:repo-1', {
prNumber: 456,
commentId: 99,
body: 'fixed',
threadId: 'thread-1',
path: 'src/app.ts',
line: 10,
prRepo: null
})
expect(mocks.getRepoPRFileContents).toHaveBeenCalledWith('id:repo-1', {
prNumber: 456,
path: 'src/app.ts',
oldPath: undefined,
status: 'modified',
headSha: 'abc123',
baseSha: 'def456',
prRepo: null
})
expect(mocks.rerunRepoPRChecks).toHaveBeenCalledWith('id:repo-1', 456, {
headSha: 'abc123',
failedOnly: true,
prRepo: null
})
expect(mocks.resolveRepoReviewThread).toHaveBeenCalledWith('id:repo-1', 'thread-1', true, null)
expect(mocks.setRepoPRFileViewed).toHaveBeenCalledWith('id:repo-1', {
pullRequestId: 'PR_kw',
path: 'src/app.ts',
viewed: true,
prRepo: null
})
expect(mocks.requestRepoPRReviewers).toHaveBeenCalledWith('id:repo-1', 456, ['alex'], null)
expect(mocks.mergeRepoPR).toHaveBeenCalledWith('id:repo-1', 456, 'squash', null)
expect(mocks.addGitLabRepoIssueComment).toHaveBeenCalledWith(
'id:repo-1',
123,
'done',
undefined
)
expect(mocks.addGitLabRepoMRComment).toHaveBeenCalledWith(
'id:repo-1',
456,
'ship it',
undefined
)
expect(mocks.resolveGitLabRepoMRDiscussion).toHaveBeenCalledWith(
'id:repo-1',
456,
'discussion-1',
true,
undefined
)
expect(mocks.mergeGitLabRepoMR).toHaveBeenCalledWith('id:repo-1', 456, 'merge', undefined)
expect(mocks.updateGitHubProjectItemField).toHaveBeenCalledWith({
projectId: 'project-1',
itemId: 'item-1',
fieldId: 'field-1',
value: { kind: 'text', text: 'Ready' }
})
expect(mocks.clearGitHubProjectItemField).toHaveBeenCalledWith({
projectId: 'project-1',
itemId: 'item-1',
fieldId: 'field-1'
})
expect(mocks.linearSearchIssues).toHaveBeenCalledWith('auth', 10, 'workspace-1')
expect(mocks.linearSelectWorkspace).toHaveBeenCalledWith('workspace-1')
expect(mocks.linearTeamLabels).toHaveBeenCalledWith('team-1', 'workspace-1')
expect(mocks.linearTeamMembers).toHaveBeenCalledWith('team-1', 'workspace-1')
expect(mocks.linearAddIssueComment).toHaveBeenCalledWith('issue-1', 'done', 'workspace-1')
expect(mocks.removeClaudeAccount).not.toHaveBeenCalled()
})
})