updated
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { JobRunner } from '../src/job.js';
|
||||
import { createLogger } from '../src/logger.js';
|
||||
|
||||
process.env.LOG_LEVEL = 'error'; // keep test output clean
|
||||
const logger = createLogger({});
|
||||
|
||||
function baseTrigger(overrides = {}) {
|
||||
return {
|
||||
repo: 'o/r',
|
||||
issueNumber: 3,
|
||||
issueTitle: 'Do a thing',
|
||||
issueBody: 'body',
|
||||
isPullRequest: false,
|
||||
defaultBranch: 'main',
|
||||
requestText: '@claude do the thing',
|
||||
author: 'selimaj-dev',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function fakeGitea({ existingPRs = [] } = {}) {
|
||||
const calls = { comments: [], createdPRs: [] };
|
||||
return {
|
||||
baseUrl: 'https://git.example.com',
|
||||
token: 'tok',
|
||||
getPullRequest: async () => null,
|
||||
listOpenPullRequests: async () => existingPRs,
|
||||
createPullRequest: async (repo, body) => {
|
||||
calls.createdPRs.push({ repo, body });
|
||||
return { html_url: 'https://git.example.com/o/r/pulls/99' };
|
||||
},
|
||||
createIssueComment: async (repo, n, body) => {
|
||||
calls.comments.push({ repo, n, body });
|
||||
},
|
||||
calls,
|
||||
};
|
||||
}
|
||||
|
||||
function fakeGitFactory({ hasChanges = false } = {}) {
|
||||
const calls = { cloned: false, checkedOutBranch: null, committed: null, pushed: null };
|
||||
const factory = () => ({
|
||||
clone: async () => { calls.cloned = true; },
|
||||
checkoutWorkBranch: async (b) => { calls.checkedOutBranch = b; },
|
||||
hasChanges: async () => hasChanges,
|
||||
commitAll: async (msg) => { calls.committed = msg; },
|
||||
push: async (b) => { calls.pushed = b; },
|
||||
});
|
||||
factory.calls = calls;
|
||||
return factory;
|
||||
}
|
||||
|
||||
test('posts a comment with claude output when nothing changed', async () => {
|
||||
const gitea = fakeGitea();
|
||||
const gitFactory = fakeGitFactory({ hasChanges: false });
|
||||
const claude = { runOnce: async () => 'Just an answer, no code change needed.' };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await runner.run(baseTrigger());
|
||||
|
||||
assert.equal(gitea.calls.comments.length, 1);
|
||||
assert.match(gitea.calls.comments[0].body, /Just an answer/);
|
||||
assert.equal(gitFactory.calls.committed, null);
|
||||
assert.equal(gitea.calls.createdPRs.length, 0);
|
||||
});
|
||||
|
||||
test('commits, pushes and opens a PR when changes are made on an issue', async () => {
|
||||
const gitea = fakeGitea();
|
||||
const gitFactory = fakeGitFactory({ hasChanges: true });
|
||||
const claude = { runOnce: async () => 'Fixed it.' };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await runner.run(baseTrigger());
|
||||
|
||||
assert.equal(gitFactory.calls.checkedOutBranch, 'claude/issue-3');
|
||||
assert.match(gitFactory.calls.committed, /#3/);
|
||||
assert.equal(gitFactory.calls.pushed, 'claude/issue-3');
|
||||
assert.equal(gitea.calls.createdPRs.length, 1);
|
||||
assert.match(gitea.calls.comments[0].body, /pulls\/99/);
|
||||
});
|
||||
|
||||
test('reuses an existing open PR instead of creating a duplicate', async () => {
|
||||
const gitea = fakeGitea({
|
||||
existingPRs: [{ head: { ref: 'claude/issue-3' }, html_url: 'https://git.example.com/o/r/pulls/7' }],
|
||||
});
|
||||
const gitFactory = fakeGitFactory({ hasChanges: true });
|
||||
const claude = { runOnce: async () => 'Fixed it.' };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await runner.run(baseTrigger());
|
||||
|
||||
assert.equal(gitea.calls.createdPRs.length, 0);
|
||||
assert.match(gitea.calls.comments[0].body, /pulls\/7/);
|
||||
});
|
||||
|
||||
test('does not open a PR when the trigger is already a PR conversation', async () => {
|
||||
const gitea = fakeGitea();
|
||||
gitea.getPullRequest = async () => ({ head: { ref: 'existing-branch' } });
|
||||
const gitFactory = fakeGitFactory({ hasChanges: true });
|
||||
const claude = { runOnce: async () => 'Updated per review comment.' };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await runner.run(baseTrigger({ isPullRequest: true }));
|
||||
|
||||
assert.equal(gitFactory.calls.pushed, 'existing-branch');
|
||||
assert.equal(gitFactory.calls.checkedOutBranch, null); // issue-only path skipped
|
||||
assert.equal(gitea.calls.createdPRs.length, 0);
|
||||
});
|
||||
|
||||
test('posts a failure comment when claude throws, and never throws itself', async () => {
|
||||
const gitea = fakeGitea();
|
||||
const gitFactory = fakeGitFactory();
|
||||
const claude = { runOnce: async () => { throw new Error('claude exploded'); } };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await assert.doesNotReject(runner.run(baseTrigger()));
|
||||
|
||||
assert.equal(gitea.calls.comments.length, 1);
|
||||
assert.match(gitea.calls.comments[0].body, /claude exploded/);
|
||||
});
|
||||
|
||||
test('swallows an error thrown while posting the failure comment itself', async () => {
|
||||
const gitea = fakeGitea();
|
||||
gitea.createIssueComment = async () => { throw new Error('gitea is down'); };
|
||||
const gitFactory = fakeGitFactory();
|
||||
const claude = { runOnce: async () => { throw new Error('claude exploded'); } };
|
||||
const runner = new JobRunner({ gitea, claude, config: {}, logger, gitFactory });
|
||||
|
||||
await assert.doesNotReject(runner.run(baseTrigger()));
|
||||
});
|
||||
Reference in New Issue
Block a user