Prevent duplicate CI completion notifications in paired rooms by checking for existing active watchers with the same channel+provider+metadata before creating a new one. Remove Task ID from watcher prompt construction to avoid router secret redaction (task- IDs contain sk- pattern), passing EJCLAW_RUNTIME_TASK_ID via env var instead and making cancel_task auto-resolve when task_id is omitted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
146 lines
3.4 KiB
TypeScript
146 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
|
|
|
|
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
|
|
|
|
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');
|
|
});
|
|
});
|