Files
EJClaw/src/github-ci.test.ts
Eyejoker 33fcf9c788 feat: add host-driven GitHub Actions CI watcher (Level 3)
Replace LLM-per-tick polling with direct `gh api` calls for GitHub
Actions watchers. New `ci_provider` discriminator column routes tasks
to either the host-driven path (zero LLM tokens) or the existing
generic LLM path. GitHub watchers poll at 15s intervals (min 10s)
via `checkGitHubActionsRun()` in the scheduler, with completion
messages sent directly to chat on terminal state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 22:31:42 +09:00

150 lines
3.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
const { execFileMock } = vi.hoisted(() => ({
execFileMock: vi.fn(),
}));
vi.mock('child_process', () => ({
execFile: execFileMock,
}));
import {
checkGitHubActionsRun,
parseGitHubCiMetadata,
serializeGitHubCiMetadata,
} from './github-ci.js';
describe('github-ci helpers', () => {
afterEach(() => {
execFileMock.mockReset();
});
it('round-trips GitHub CI metadata', () => {
const raw = serializeGitHubCiMetadata({
repo: 'owner/repo',
run_id: 123456,
poll_count: 1,
});
expect(parseGitHubCiMetadata(raw)).toEqual({
repo: 'owner/repo',
run_id: 123456,
poll_count: 1,
consecutive_errors: undefined,
});
expect(parseGitHubCiMetadata('{"repo":"","run_id":0}')).toBeNull();
});
it('returns non-terminal status for in-progress runs', async () => {
execFileMock.mockImplementation(
(
_cmd: string,
_args: string[],
_opts: unknown,
cb: (error: Error | null, stdout: string, stderr: string) => void,
) => {
cb(
null,
JSON.stringify({
status: 'in_progress',
conclusion: null,
}),
'',
);
},
);
await expect(
checkGitHubActionsRun({
prompt: `
[BACKGROUND CI WATCH]
Watch target:
GitHub Actions run 123456
Task ID:
task-github
Check instructions:
Managed by host-driven watcher.
`.trim(),
ci_metadata: JSON.stringify({
repo: 'owner/repo',
run_id: 123456,
}),
}),
).resolves.toEqual({
terminal: false,
resultSummary: 'GitHub Actions run 123456 is in_progress',
});
});
it('renders a concise completion message for terminal runs', async () => {
execFileMock
.mockImplementationOnce(
(
_cmd: string,
_args: string[],
_opts: unknown,
cb: (error: Error | null, stdout: string, stderr: string) => void,
) => {
cb(
null,
JSON.stringify({
status: 'completed',
conclusion: 'failure',
name: 'CI',
html_url: 'https://github.com/owner/repo/actions/runs/654321',
head_branch: 'main',
}),
'',
);
},
)
.mockImplementationOnce(
(
_cmd: string,
_args: string[],
_opts: unknown,
cb: (error: Error | null, stdout: string, stderr: string) => void,
) => {
cb(
null,
JSON.stringify({
jobs: [
{ name: 'build', conclusion: 'success' },
{ name: 'test', conclusion: 'failure' },
],
}),
'',
);
},
);
const result = await checkGitHubActionsRun({
prompt: `
[BACKGROUND CI WATCH]
Watch target:
GitHub Actions run 654321
Task ID:
task-github
Check instructions:
Managed by host-driven watcher.
`.trim(),
ci_metadata: JSON.stringify({
repo: 'owner/repo',
run_id: 654321,
}),
});
expect(result.terminal).toBe(true);
expect(result.resultSummary).toContain('실패');
expect(result.completionMessage).toContain('CI 완료: GitHub Actions run 654321');
expect(result.completionMessage).toContain('판정: 실패');
expect(result.completionMessage).toContain('- 실패 job: test');
});
});