jest.mock('simple-git', () => ({ simpleGit: jest.fn() })); jest.mock('../src/services/gitlabClient'); jest.mock('../src/services/startTaskAgent'); import { simpleGit } from 'simple-git'; import { GitlabClient } from '../src/services/gitlabClient'; import { sendTaskAgent } from '../src/services/sendTaskAgent'; import { startTaskAgent } from '../src/services/startTaskAgent'; describe('sendTaskAgent', () => { const repoPath = '/fake/repo'; const id = '123'; const defaultUrl = 'https://gitlab.teleport.ftprod.fr/'; const defaultWorkDir = '/home/.ftprod-ai/tbot'; let gitMock: any; let getMock: jest.Mock; let postMock: jest.Mock; beforeEach(() => { jest.clearAllMocks(); delete process.env.GITLAB_TOKEN; gitMock = { getRemotes: jest.fn() }; (simpleGit as jest.Mock).mockReturnValue(gitMock); getMock = jest.fn(); postMock = jest.fn(); (GitlabClient as jest.Mock).mockImplementation(() => ({ instance: { get: getMock, post: postMock }, })); (startTaskAgent as jest.Mock).mockResolvedValue({ branch: 'feat/gl-123-test', commitSha: 'sha123', }); }); it('throws if no GitLab token is provided', async () => { await expect(sendTaskAgent({ id })).rejects.toThrow( 'Le jeton GitLab est requis' ); }); it('throws if origin remote is missing', async () => { process.env.GITLAB_TOKEN = 'tok'; gitMock.getRemotes.mockResolvedValue([]); await expect( sendTaskAgent({ id, repoPath }) ).rejects.toThrow('Remote origin introuvable'); }); it('creates MR with issue title and description', async () => { process.env.GITLAB_TOKEN = 'tok'; gitMock.getRemotes.mockResolvedValue([ { name: 'origin', refs: { fetch: 'git@host:ns/proj.git', push: null } }, ]); getMock.mockResolvedValueOnce({ data: { title: 'Issue title', description: 'Issue desc' } }); postMock.mockResolvedValueOnce({ data: { iid: 10, web_url: 'https://gitlab/proj/mr/10' } }); const res = await sendTaskAgent({ id, repoPath }); expect(startTaskAgent).toHaveBeenCalledWith({ id, repoPath, gitlabToken: 'tok', gitlabUrl: defaultUrl, workDir: expect.any(String), teleportToken: undefined, }); const projectPath = encodeURIComponent('ns/proj'); expect(getMock).toHaveBeenCalledWith( `/projects/${projectPath}/issues/${id}` ); expect(postMock).toHaveBeenCalledWith( `/projects/${projectPath}/merge_requests`, { source_branch: 'feat/gl-123-test', target_branch: 'main', title: 'Issue title', description: 'Issue desc\n\nCloses #123', } ); expect(res).toEqual({ mrIid: 10, branch: 'feat/gl-123-test', commitSha: 'sha123', webUrl: 'https://gitlab/proj/mr/10', }); }); it('uses custom targetBranch and handles missing description', async () => { process.env.GITLAB_TOKEN = 'tok'; gitMock.getRemotes.mockResolvedValue([ { name: 'origin', refs: { fetch: 'https://host/ns/proj.git', push: null } }, ]); getMock.mockResolvedValueOnce({ data: { title: 'Title only' } }); postMock.mockResolvedValueOnce({ data: { iid: 5, web_url: 'url' } }); const res = await sendTaskAgent({ id, repoPath, targetBranch: 'dev', gitlabToken: 'tok2', gitlabUrl: 'http://gitlab.com/', workDir: '/tmp', teleportToken: 'join', }); expect(startTaskAgent).toHaveBeenCalledWith({ id, repoPath, gitlabToken: 'tok2', gitlabUrl: 'http://gitlab.com/', workDir: '/tmp', teleportToken: 'join', }); const projectPath = encodeURIComponent('ns/proj'); expect(postMock).toHaveBeenCalledWith( `/projects/${projectPath}/merge_requests`, { source_branch: 'feat/gl-123-test', target_branch: 'dev', title: 'Title only', description: `Closes #${id}`, } ); expect(res).toEqual({ mrIid: 5, branch: 'feat/gl-123-test', commitSha: 'sha123', webUrl: 'url', }); }); it('throws if MR creation returns invalid data', async () => { process.env.GITLAB_TOKEN = 'token'; gitMock.getRemotes.mockResolvedValue([ { name: 'origin', refs: { fetch: 'git@host:ns/p.git', push: null } }, ]); getMock.mockResolvedValueOnce({ data: { title: 'T', description: 'D' } }); postMock.mockResolvedValueOnce({ data: { iid: 'na', web_url: 123 } }); await expect( sendTaskAgent({ id, repoPath, gitlabToken: 'token' }) ).rejects.toThrow('Échec création MR'); }); });