Files
orca/src/shared/source-control-ai-actions.test.ts
T
Jinjinganddalveytech-vincent 070a956a72 feat(source-control): add push failure AI recovery (#7826)
* feat(source-control): add Fix push failure with AI for pre-push hooks

Detect pre-push hook failures separately from auth/transport errors so
push toasts and inline messages no longer suggest checking repo access.
Mirror the commit-failure recovery flow with a fixPushFailure action,
summary panel, details dialog, and agent launch recipe in Settings.

Fixes #6497

* feat(source-control): add push failure AI recovery

Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>

---------

Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>
2026-07-08 18:59:28 -07:00

172 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
normalizeSourceControlAiActionDefaults,
SOURCE_CONTROL_ACTION_VARIABLES,
SOURCE_CONTROL_LAUNCH_ACTION_IDS,
SOURCE_CONTROL_LAUNCH_ACTION_LABELS,
DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES,
readSourceControlActionDefault,
renderSourceControlActionCommandTemplate,
resolveSourceControlActionCommandTemplate,
setSourceControlActionAgentDefault
} from './source-control-ai-actions'
describe('source-control AI launch action defaults', () => {
it('normalizes safe launch action defaults', () => {
expect(
normalizeSourceControlAiActionDefaults({
fixChecks: {
agentId: 'codex',
commandInputTemplate: ' {basePrompt} ',
agentArgs: ' --model gpt-5.5 '
},
resolveConflicts: { agentId: null },
resolveComments: {
commandInputTemplate: 'Resolve {basePrompt}'
},
pullRequest: { agentId: 'claude' }
})
).toEqual({
fixChecks: {
agentId: 'codex',
commandInputTemplate: ' {basePrompt} ',
agentArgs: ' --model gpt-5.5 '
},
resolveConflicts: { agentId: null },
resolveComments: {
commandInputTemplate: 'Resolve {basePrompt}'
},
pullRequest: { agentId: 'claude' }
})
})
it('rejects unsafe prototype keys and invalid agent ids', () => {
expect(
normalizeSourceControlAiActionDefaults({
__proto__: { agentId: 'codex' },
constructor: { agentId: 'codex' },
prototype: { agentId: 'codex' },
fixCommitFailure: { agentId: 'not-real', commandInputTemplate: 42 }
})
).toBeUndefined()
})
it('normalizes the custom command sentinel for text action recipes', () => {
expect(
normalizeSourceControlAiActionDefaults({
pullRequest: {
agentId: 'custom',
commandInputTemplate: '{basePrompt}'
}
})
).toEqual({
pullRequest: {
agentId: 'custom',
commandInputTemplate: '{basePrompt}'
}
})
})
it('trims command templates and CLI args only when reading them', () => {
const defaults = normalizeSourceControlAiActionDefaults({
fixCommitFailure: {
agentId: 'claude',
commandInputTemplate: ' {basePrompt} ',
agentArgs: ' --model sonnet '
}
})
expect(defaults?.fixCommitFailure?.commandInputTemplate).toBe(' {basePrompt} ')
expect(defaults?.fixCommitFailure?.agentArgs).toBe(' --model sonnet ')
expect(readSourceControlActionDefault(defaults, 'fixCommitFailure')).toEqual({
agentId: 'claude',
commandInputTemplate: '{basePrompt}',
agentArgs: '--model sonnet'
})
})
it('preserves explicitly empty command templates when resolving defaults', () => {
expect(
resolveSourceControlActionCommandTemplate(
{ fixCommitFailure: { commandInputTemplate: '' } },
'fixCommitFailure'
)
).toBe('')
expect(resolveSourceControlActionCommandTemplate(undefined, 'fixCommitFailure')).toBe(
'{basePrompt}'
)
})
it('renders command template placeholders that start with underscores', () => {
expect(
renderSourceControlActionCommandTemplate('agent {_prompt} {{_context}}', {
_prompt: 'PROMPT',
_context: 'CONTEXT'
})
).toBe('agent PROMPT CONTEXT')
})
it('sets agent defaults without dropping neighboring action defaults', () => {
expect(
setSourceControlActionAgentDefault(
{ fixChecks: { agentId: 'codex' } },
'resolveConflicts',
'claude'
)
).toEqual({
fixChecks: { agentId: 'codex' },
resolveConflicts: { agentId: 'claude' }
})
})
it('exposes review-comment resolution as a launch action', () => {
expect(SOURCE_CONTROL_LAUNCH_ACTION_IDS).toContain('resolveComments')
expect(SOURCE_CONTROL_LAUNCH_ACTION_LABELS.resolveComments).toBe('Review comment resolution')
expect(resolveSourceControlActionCommandTemplate(undefined, 'resolveComments')).toBe(
'{basePrompt}'
)
expect(SOURCE_CONTROL_ACTION_VARIABLES.resolveComments).toEqual(['basePrompt'])
})
it('exposes push failure recovery as a launch action with basePrompt defaults', () => {
expect(SOURCE_CONTROL_LAUNCH_ACTION_IDS).toContain('fixPushFailure')
expect(SOURCE_CONTROL_LAUNCH_ACTION_LABELS.fixPushFailure).toBe('Push failure fixes')
expect(DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES.fixPushFailure).toBe('{basePrompt}')
expect(resolveSourceControlActionCommandTemplate(undefined, 'fixPushFailure')).toBe(
'{basePrompt}'
)
expect(SOURCE_CONTROL_ACTION_VARIABLES.fixPushFailure).toEqual(['basePrompt'])
expect(
normalizeSourceControlAiActionDefaults({
fixPushFailure: {
agentId: 'codex',
commandInputTemplate: '{basePrompt}',
agentArgs: '--model gpt-5.4-mini'
}
})
).toEqual({
fixPushFailure: {
agentId: 'codex',
commandInputTemplate: '{basePrompt}',
agentArgs: '--model gpt-5.4-mini'
}
})
})
it('renders known template variables and leaves unknown variables visible', () => {
expect(
renderSourceControlActionCommandTemplate('fix {thing} with {missing}', {
thing: 'CI'
})
).toBe('fix CI with {missing}')
})
it('leaves inherited prototype names visible instead of rendering function source', () => {
expect(
renderSourceControlActionCommandTemplate('use {constructor} and {toString}', {
thing: 'CI'
})
).toBe('use {constructor} and {toString}')
})
})