UNPKG

@aaronshaf/ger

Version:

Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS

260 lines (219 loc) 8.08 kB
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock } from 'bun:test' import { Effect, Layer } from 'effect' import { HttpResponse, http } from 'msw' import { setupServer } from 'msw/node' import { GerritApiServiceLive } from '@/api/gerrit' import { projectsCommand } from '@/cli/commands/projects' import { ConfigService } from '@/services/config' import { createMockConfigService } from './helpers/config-mock' const mockProjects = { 'project-a': { id: 'project-a', name: 'project-a', parent: 'All-Projects', state: 'ACTIVE' as const, }, 'project-b': { id: 'project-b', name: 'project-b', parent: 'All-Projects', state: 'ACTIVE' as const, }, 'test-project': { id: 'test-project', name: 'test-project', parent: 'All-Projects', state: 'ACTIVE' as const, }, } // Create MSW server const server = setupServer( // Default handler for auth check http.get('*/a/accounts/self', ({ request }) => { const auth = request.headers.get('Authorization') if (!auth || !auth.startsWith('Basic ')) { return HttpResponse.text('Unauthorized', { status: 401 }) } return HttpResponse.json({ _account_id: 1000, name: 'Test User', email: 'test@example.com', }) }), ) describe('projects command', () => { let mockConsoleLog: ReturnType<typeof mock> let mockConsoleError: ReturnType<typeof mock> beforeAll(() => { server.listen({ onUnhandledRequest: 'bypass' }) }) afterAll(() => { server.close() }) beforeEach(() => { mockConsoleLog = mock(() => {}) mockConsoleError = mock(() => {}) console.log = mockConsoleLog console.error = mockConsoleError }) afterEach(() => { server.resetHandlers() }) it('should list all projects', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockProjects)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') // Projects should be sorted alphabetically expect(output).toContain('project-a') expect(output).toContain('project-b') expect(output).toContain('test-project') // Check that they appear in alphabetical order const lines = output.split('\n').filter((line) => line.trim()) expect(lines[0]).toBe('project-a') expect(lines[1]).toBe('project-b') expect(lines[2]).toBe('test-project') }) it('should list projects with pattern filter', async () => { server.use( http.get('*/a/projects/', ({ request }) => { const url = new URL(request.url) const pattern = url.searchParams.get('p') expect(pattern).toBe('test-*') // Return filtered projects const filtered = { 'test-project': mockProjects['test-project'], } return HttpResponse.text(`)]}'\n${JSON.stringify(filtered)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({ pattern: 'test-*' }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') expect(output).toContain('test-project') expect(output).not.toContain('project-a') expect(output).not.toContain('project-b') }) it('should output XML format when --xml flag is used', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockProjects)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({ xml: true }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>') expect(output).toContain('<projects_result>') expect(output).toContain('<status>success</status>') expect(output).toContain('<projects>') expect(output).toContain('<project>') expect(output).toContain('<id>project-a</id>') expect(output).toContain('<name>project-a</name>') expect(output).toContain('<parent>All-Projects</parent>') expect(output).toContain('<state>ACTIVE</state>') expect(output).toContain('</project>') expect(output).toContain('</projects>') expect(output).toContain('</projects_result>') }) it('should handle empty results', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.text(`)]}'\n${JSON.stringify({})}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') expect(output).toContain('No projects found') }) it('should handle empty results with XML format', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.text(`)]}'\n${JSON.stringify({})}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({ xml: true }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') expect(output).toContain('<projects_result>') expect(output).toContain('<status>success</status>') expect(output).toContain('<projects />') }) it('should handle network errors gracefully', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.error() }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) // Should throw/fail await expect(Effect.runPromise(program)).rejects.toThrow() }) it('should handle API errors', async () => { server.use( http.get('*/a/projects/', () => { return HttpResponse.text('Forbidden', { status: 403 }) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) // Should throw/fail await expect(Effect.runPromise(program)).rejects.toThrow() }) it('should handle projects without parent or state', async () => { const minimalProjects = { 'minimal-project': { id: 'minimal-project', name: 'minimal-project', }, } server.use( http.get('*/a/projects/', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(minimalProjects)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = projectsCommand({ xml: true }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await Effect.runPromise(program) const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n') expect(output).toContain('<id>minimal-project</id>') expect(output).toContain('<name>minimal-project</name>') expect(output).not.toContain('<parent>') expect(output).not.toContain('<state>') }) })