// [DEF:frontend.src.services.__tests__.gitService_test:Module] // @RELATION: VERIFIES -> ../gitService.js // @PURPOSE: API client tests ensuring correct endpoints are called per contract import { describe, it, expect, vi, beforeEach } from 'vitest'; import { gitService } from '../gitService'; import { requestApi } from '../../lib/api'; vi.mock('../../lib/api', () => ({ requestApi: vi.fn() })); describe('gitService', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('Config Management', () => { // @PRE User authenticated // @POST Returns list of Git configs it('getConfigs calls /git/config', async () => { requestApi.mockResolvedValue([]); const result = await gitService.getConfigs(); expect(requestApi).toHaveBeenCalledWith('/git/config'); expect(result).toEqual([]); }); // @PRE Config object must be valid // @POST New config created and returned it('createConfig calls POST /git/config', async () => { const config = { name: 'Test', provider: 'GITHUB' }; requestApi.mockResolvedValue({ id: '1', ...config }); const result = await gitService.createConfig(config); expect(requestApi).toHaveBeenCalledWith('/git/config', 'POST', config); expect(result.id).toBe('1'); }); // @PRE configId must exist // @POST Config deleted it('deleteConfig calls DELETE /git/config/:id', async () => { requestApi.mockResolvedValue({ status: 'success' }); await gitService.deleteConfig('config-123'); expect(requestApi).toHaveBeenCalledWith('/git/config/config-123', 'DELETE'); }); // @PRE configId must exist // @POST Config updated it('updateConfig calls PUT /git/config/:id', async () => { const config = { name: 'Updated' }; requestApi.mockResolvedValue({ id: 'config-123', ...config }); await gitService.updateConfig('config-123', config); expect(requestApi).toHaveBeenCalledWith('/git/config/config-123', 'PUT', config); }); // @PRE Config must contain valid URL and PAT // @POST Returns connection status it('testConnection calls POST /git/config/test', async () => { const config = { url: 'https://github.com', pat: 'secret' }; requestApi.mockResolvedValue({ status: 'success' }); await gitService.testConnection(config); expect(requestApi).toHaveBeenCalledWith('/git/config/test', 'POST', config); }); }); describe('Remote Repository Management', () => { // @PRE configId references GITEA // @POST Returns repo metadata it('listGiteaRepositories calls GET /git/config/:id/gitea/repos', async () => { requestApi.mockResolvedValue([]); await gitService.listGiteaRepositories('conf-1'); expect(requestApi).toHaveBeenCalledWith('/git/config/conf-1/gitea/repos'); }); // @PRE configId references GITEA // @POST Repo created on Gitea it('createGiteaRepository calls POST /git/config/:id/gitea/repos', async () => { const payload = { name: 'new-repo' }; requestApi.mockResolvedValue({ name: 'new-repo' }); await gitService.createGiteaRepository('conf-1', payload); expect(requestApi).toHaveBeenCalledWith('/git/config/conf-1/gitea/repos', 'POST', payload); }); // @PRE configId references supported provider // @POST Remote repo created it('createRemoteRepository calls POST /git/config/:id/repositories', async () => { const payload = { name: 'new-remote' }; requestApi.mockResolvedValue({ name: 'new-remote' }); await gitService.createRemoteRepository('conf-1', payload); expect(requestApi).toHaveBeenCalledWith('/git/config/conf-1/repositories', 'POST', payload); }); // @PRE configId references GITEA // @POST Repo deleted on Gitea it('deleteGiteaRepository calls DELETE with encoded owner and repo', async () => { requestApi.mockResolvedValue({ status: 'success' }); await gitService.deleteGiteaRepository('conf-1', 'my-org', 'my-repo'); expect(requestApi).toHaveBeenCalledWith('/git/config/conf-1/gitea/repos/my-org/my-repo', 'DELETE'); }); }); describe('Dashboard Repository Operations', () => { // @PRE Dashboard exists and config_id valid // @POST Repo initialized it('initRepository calls POST with remote_url and config_id', async () => { requestApi.mockResolvedValue({}); await gitService.initRepository(123, 'conf-1', 'http://remote.git'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/init', 'POST', { config_id: 'conf-1', remote_url: 'http://remote.git' }); }); // @PRE Repo initialized // @POST Returns binding it('getRepositoryBinding calls GET', async () => { requestApi.mockResolvedValue({}); await gitService.getRepositoryBinding('dash-slug'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/dash-slug'); }); // @PRE Repo initialized // @POST Returns branches it('getBranches calls GET /branches', async () => { requestApi.mockResolvedValue([]); await gitService.getBranches(123); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/branches'); }); // @PRE Source branch exists // @POST New branch created it('createBranch calls POST /branches with name and from_branch', async () => { requestApi.mockResolvedValue({}); await gitService.createBranch(123, 'feature', 'main'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/branches', 'POST', { name: 'feature', from_branch: 'main' }); }); // @PRE Target branch exists // @POST Repo checked out it('checkoutBranch calls POST /checkout', async () => { requestApi.mockResolvedValue({}); await gitService.checkoutBranch(123, 'feature'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/checkout', 'POST', { name: 'feature' }); }); // @PRE Message not empty // @POST Changes committed it('commit calls POST /commit with message and files', async () => { requestApi.mockResolvedValue({}); await gitService.commit(123, 'Initial commit', ['file.txt']); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/commit', 'POST', { message: 'Initial commit', files: ['file.txt'] }); }); // @PRE Remote configured // @POST Remote updated it('push calls POST /push', async () => { requestApi.mockResolvedValue({}); await gitService.push(123); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/push', 'POST'); }); // @PRE Dashboard ref valid // @POST Repo deleted it('deleteRepository calls DELETE', async () => { requestApi.mockResolvedValue({}); await gitService.deleteRepository(123); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123', 'DELETE'); }); // @PRE Remote configured // @POST Local repo updated it('pull calls POST /pull', async () => { requestApi.mockResolvedValue({}); await gitService.pull(123); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/pull', 'POST'); }); }); describe('Other Operations', () => { // @POST Returns list of environments it('getEnvironments calls GET /environments', async () => { requestApi.mockResolvedValue([]); await gitService.getEnvironments(); expect(requestApi).toHaveBeenCalledWith('/git/environments'); }); // @PRE Environment active // @POST Dashboard deployed it('deploy calls POST /deploy with environment_id', async () => { requestApi.mockResolvedValue({}); await gitService.deploy(123, 'env-1'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/deploy', 'POST', { environment_id: 'env-1' }); }); // @POST Returns commit history it('getHistory calls GET /history with limit', async () => { requestApi.mockResolvedValue([]); await gitService.getHistory(123, 10); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/history?limit=10'); }); // @POST Syncs local and remote it('sync calls POST /sync with optional params', async () => { requestApi.mockResolvedValue({}); await gitService.sync(123, 'env-source', 'env-target'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/sync?source_env_id=env-source&env_id=env-target', 'POST'); }); // @PRE Dashboard id valid // @POST Returns status it('getStatus calls GET /status', async () => { requestApi.mockResolvedValue({}); await gitService.getStatus(123); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/status'); }); // @PRE Dashboard ids array // @POST Returns batch statuses it('getStatusesBatch calls POST /status/batch', async () => { requestApi.mockResolvedValue({}); await gitService.getStatusesBatch([1, 2, 3]); expect(requestApi).toHaveBeenCalledWith('/git/repositories/status/batch', 'POST', { dashboard_ids: [1, 2, 3] }); }); // @PRE Dashboard id valid // @POST Returns diff it('getDiff calls GET /diff with params', async () => { requestApi.mockResolvedValue(''); await gitService.getDiff(123, 'file.txt', true, 'env-1'); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/diff?file_path=file.txt&staged=true&env_id=env-1'); }); // @PRE Repo initialized // @POST Returns promotion metadata it('promote calls POST /promote with payload', async () => { const payload = { from_branch: 'dev', to_branch: 'main', mode: 'mr' }; requestApi.mockResolvedValue({}); await gitService.promote(123, payload); expect(requestApi).toHaveBeenCalledWith('/git/repositories/123/promote', 'POST', payload); }); }); }); // [/DEF:frontend.src.services.__tests__.gitService_test:Module]