Files
orca/tests/e2e/source-control-commit-message-ai.spec.ts
T
Jinjing f17c509be4 fix(source-control-ai): make the {linkedIssue} e2e guard and dialog test falsifiable
The e2e unlinked case extracted the echoed issue with `ORCA_E2E_ISSUE=(\d*)`,
which matches zero digits in front of an unexpanded `{linkedIssue}` and reported
it as `empty` — so the case that exists to catch a literal token surviving into
a prompt passed on exactly that regression. Capture the whole line instead: a
literal now arrives as `saw-issue:{linkedIssue}` and fails, verified by dropping
the substitution key for unlinked contexts and watching the case go red.

Also drop the inert `not.toContain('Command input is empty.')` assertion — that
copy is click-driven `generationError` state and this suite renders statically,
so it could never fail; the claim it reached for is carried by the plan test.
Rename two plan tests off the "plan preview" framing the design now rejects.

Local review artifacts (design doc, implementation notes, final review) were
swept to match the tree in the same pass; they are gitignored here.
2026-07-25 14:34:32 -07:00

173 lines
6.6 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import { rmSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { test, expect } from './helpers/orca-app'
import { waitForSessionReady } from './helpers/store'
import { openSourceControlForWorktree } from './helpers/worktree-registration'
function createWorktreeWithStagedChange(repoPath: string): {
branchName: string
worktreePath: string
} {
const branchName = `e2e-ai-commit-${Date.now()}-${Math.random().toString(16).slice(2)}`
const worktreePath = path.join(os.tmpdir(), branchName)
execFileSync('git', ['worktree', 'add', worktreePath, '-b', branchName], {
cwd: repoPath,
stdio: 'pipe'
})
writeFileSync(
path.join(worktreePath, 'README.md'),
'# AI Commit Message E2E\n\nGenerated flow.\n'
)
execFileSync('git', ['add', 'README.md'], { cwd: worktreePath, stdio: 'pipe' })
return { branchName, worktreePath }
}
function cleanupWorktree(repoPath: string, worktreePath: string, branchName: string): void {
try {
execFileSync('git', ['worktree', 'remove', '--force', worktreePath], {
cwd: repoPath,
stdio: 'pipe'
})
} catch {
rmSync(worktreePath, { recursive: true, force: true })
}
try {
execFileSync('git', ['branch', '-D', branchName], { cwd: repoPath, stdio: 'pipe' })
} catch {
// The branch is already gone when git prunes it with the worktree.
}
}
/**
* Echoes back whichever issue number reached the prompt, so the assertion covers the
* whole chain (renderer → IPC → worktree meta → template render → agent stdin) rather
* than any single hop.
*/
function writeLinkedIssueEchoGenerator(scriptPath: string): void {
writeFileSync(
scriptPath,
[
'const chunks = []',
"process.stdin.on('data', (chunk) => chunks.push(chunk))",
"process.stdin.on('end', () => {",
" const prompt = Buffer.concat(chunks).toString('utf8')",
// Why: capture the whole line, not `\d*` — a `\d*` capture matches zero digits before
// an unexpanded `{linkedIssue}` and reports it as `empty`, hiding a literal token.
' const match = prompt.match(/ORCA_E2E_ISSUE=([^\\r\\n]*)/)',
" process.stdout.write(`saw-issue:${match ? match[1] || 'empty' : 'missing'}`)",
'})'
].join('\n')
)
}
test.describe('Source Control AI commit messages', () => {
// Why: the unlinked case separates a real resolver from one that always returns a number,
// and — because the generator echoes the whole line — a literal `{linkedIssue}` reaches the
// assertion as `saw-issue:{linkedIssue}` instead of masquerading as the empty expansion.
for (const { label, linkedIssue, expected } of [
{ label: 'substitutes the workspace-linked issue into', linkedIssue: 4242, expected: '4242' },
{
label: 'expands the issue token to nothing for an unlinked workspace in',
linkedIssue: null,
expected: 'empty'
}
]) {
test(`${label} the commit-message recipe`, async ({ orcaPage, testRepoPath }) => {
const { branchName, worktreePath } = createWorktreeWithStagedChange(testRepoPath)
const generatorPath = path.join(os.tmpdir(), `${branchName}-linked-issue-generator.cjs`)
writeLinkedIssueEchoGenerator(generatorPath)
try {
await waitForSessionReady(orcaPage)
await openSourceControlForWorktree(orcaPage, testRepoPath, worktreePath)
await orcaPage.evaluate(
async ({ generatorPath, linkedIssue }) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
const worktreeId = store.getState().activeWorktreeId
if (!worktreeId) {
throw new Error('No worktree was active after opening Source Control')
}
await window.api.worktrees.updateMeta({ worktreeId, updates: { linkedIssue } })
const customAgentCommand = `node ${JSON.stringify(generatorPath)}`
await store.getState().updateSettings({
activeRuntimeEnvironmentId: null,
sourceControlAi: {
enabled: true,
agentId: 'custom' as const,
selectedModelByAgent: {},
selectedThinkingByModel: {},
customAgentCommand,
instructionsByOperation: {},
actions: {
commitMessage: {
agentId: 'custom' as const,
commandInputTemplate: 'ORCA_E2E_ISSUE={linkedIssue}\n\n{basePrompt}'
}
}
}
})
},
{ generatorPath, linkedIssue }
)
const textarea = orcaPage.getByRole('textbox', { name: 'Commit message' })
await expect(textarea).toBeVisible({ timeout: 10_000 })
const generate = orcaPage.getByRole('button', { name: 'Generate commit message with AI' })
await expect(generate).toBeEnabled()
await generate.click()
await expect(textarea).toHaveValue(`saw-issue:${expected}`, { timeout: 15_000 })
} finally {
rmSync(generatorPath, { force: true })
cleanupWorktree(testRepoPath, worktreePath, branchName)
}
})
}
test('generates a commit message from staged changes through the Source Control UI', async ({
orcaPage,
testRepoPath
}) => {
const { branchName, worktreePath } = createWorktreeWithStagedChange(testRepoPath)
const agentCommand =
'node -e "setTimeout(() => process.stdout.write(\'Add generated E2E message\'), 250)"'
try {
await waitForSessionReady(orcaPage)
await openSourceControlForWorktree(orcaPage, testRepoPath, worktreePath, {
commitMessageAi: {
enabled: true,
agentId: 'custom',
selectedModelByAgent: {},
selectedThinkingByModel: {},
customPrompt: '',
customAgentCommand: agentCommand
}
})
const textarea = orcaPage.getByRole('textbox', { name: 'Commit message' })
await expect(textarea).toBeVisible({ timeout: 10_000 })
await expect(textarea).toHaveValue('')
const generate = orcaPage.getByRole('button', { name: 'Generate commit message with AI' })
await expect(generate).toBeVisible()
await expect(generate).toBeEnabled()
await generate.click()
await expect(
orcaPage.getByRole('button', { name: 'Stop generating commit message' })
).toBeVisible()
await expect(textarea).toHaveValue('Add generated E2E message', { timeout: 10_000 })
} finally {
cleanupWorktree(testRepoPath, worktreePath, branchName)
}
})
})