UNPKG

@aaronshaf/ger

Version:

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

325 lines (262 loc) 9.66 kB
import { describe, test, expect, beforeAll, afterAll, afterEach, mock, spyOn } from 'bun:test' import { setupServer } from 'msw/node' import { http, HttpResponse } from 'msw' import { Effect, Layer } from 'effect' import { showCommand } from '@/cli/commands/show' import { GerritApiServiceLive } from '@/api/gerrit' import { ConfigService } from '@/services/config' import { generateMockChange } from '@/test-utils/mock-generator' import { createMockConfigService } from './helpers/config-mock' import * as childProcess from 'node:child_process' import { EventEmitter } from 'node:events' /** * Integration tests for auto-detecting Change-ID from HEAD commit */ const mockChange = generateMockChange({ _number: 392385, change_id: 'If5a3ae8cb5a107e187447802358417f311d0c4b1', subject: 'WIP: test', status: 'NEW', project: 'my-project', branch: 'master', created: '2024-01-15 10:00:00.000000000', updated: '2024-01-15 12:00:00.000000000', owner: { _account_id: 1001, name: 'Test User', email: 'test@example.com', }, }) const mockDiff = `--- a/test.txt +++ b/test.txt @@ -1,1 +1,2 @@ original line +new line` const server = setupServer( http.get('*/a/accounts/self', () => { return HttpResponse.json({ _account_id: 1000, name: 'Test User', email: 'test@example.com', }) }), // Handler that matches the auto-detected Change-ID http.get('*/a/changes/:changeId', ({ params }) => { const { changeId } = params if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') { return HttpResponse.text(`)]}' ${JSON.stringify(mockChange)}`) } return HttpResponse.text('Not Found', { status: 404 }) }), http.get('*/a/changes/:changeId/revisions/current/patch', ({ params }) => { const { changeId } = params if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') { return HttpResponse.text(btoa(mockDiff)) } return HttpResponse.text('Not Found', { status: 404 }) }), http.get('*/a/changes/:changeId/revisions/current/comments', ({ params }) => { const { changeId } = params if (changeId === 'If5a3ae8cb5a107e187447802358417f311d0c4b1') { return HttpResponse.text(`)]}' {}`) } return HttpResponse.text('Not Found', { status: 404 }) }), ) let capturedLogs: string[] = [] let capturedErrors: string[] = [] let capturedStdout: string[] = [] const mockConsoleLog = mock((...args: any[]) => { capturedLogs.push(args.join(' ')) }) const mockConsoleError = mock((...args: any[]) => { capturedErrors.push(args.join(' ')) }) // Mock process.stdout.write to capture JSON/XML output and handle callbacks const mockStdoutWrite = mock((chunk: any, callback?: any) => { capturedStdout.push(String(chunk)) // Call the callback synchronously if provided if (typeof callback === 'function') { callback() } return true }) const originalConsoleLog = console.log const originalConsoleError = console.error const originalStdoutWrite = process.stdout.write let spawnSpy: ReturnType<typeof spyOn> beforeAll(() => { server.listen({ onUnhandledRequest: 'bypass' }) // @ts-ignore console.log = mockConsoleLog // @ts-ignore console.error = mockConsoleError // @ts-ignore process.stdout.write = mockStdoutWrite }) afterAll(() => { server.close() console.log = originalConsoleLog console.error = originalConsoleError // @ts-ignore process.stdout.write = originalStdoutWrite }) afterEach(() => { server.resetHandlers() mockConsoleLog.mockClear() mockConsoleError.mockClear() mockStdoutWrite.mockClear() capturedLogs = [] capturedErrors = [] capturedStdout = [] if (spawnSpy) { spawnSpy.mockRestore() } }) const createMockConfigLayer = (): Layer.Layer<ConfigService, never, never> => Layer.succeed(ConfigService, createMockConfigService()) describe('show command with auto-detection', () => { test('auto-detects Change-ID from HEAD commit when no argument provided', async () => { const commitMessage = `feat: add feature Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1` // Mock git log command const mockChildProcess = new EventEmitter() // @ts-ignore mockChildProcess.stdout = new EventEmitter() // @ts-ignore mockChildProcess.stderr = new EventEmitter() spawnSpy = spyOn(childProcess, 'spawn') spawnSpy.mockReturnValue(mockChildProcess as any) const effect = showCommand(undefined, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) const resultPromise = Effect.runPromise(effect) // Simulate git log success setImmediate(() => { // @ts-ignore mockChildProcess.stdout.emit('data', Buffer.from(commitMessage)) mockChildProcess.emit('close', 0) }) await resultPromise const output = capturedLogs.join('\n') expect(output).toContain('Change 392385') expect(output).toContain('WIP: test') expect(capturedErrors.length).toBe(0) }) test('auto-detects Change-ID with --xml flag', async () => { const commitMessage = `feat: add feature Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1` const mockChildProcess = new EventEmitter() // @ts-ignore mockChildProcess.stdout = new EventEmitter() // @ts-ignore mockChildProcess.stderr = new EventEmitter() spawnSpy = spyOn(childProcess, 'spawn') spawnSpy.mockReturnValue(mockChildProcess as any) const effect = showCommand(undefined, { xml: true }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) const resultPromise = Effect.runPromise(effect) setImmediate(() => { // @ts-ignore mockChildProcess.stdout.emit('data', Buffer.from(commitMessage)) mockChildProcess.emit('close', 0) }) await resultPromise const output = capturedStdout.join('') expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>') expect(output).toContain('<show_result>') expect(output).toContain('<status>success</status>') expect(output).toContain('392385') expect(capturedErrors.length).toBe(0) }) test('shows error when no Change-ID in HEAD commit', async () => { const commitMessage = `feat: add feature without Change-ID This commit has no Change-ID footer.` const mockChildProcess = new EventEmitter() // @ts-ignore mockChildProcess.stdout = new EventEmitter() // @ts-ignore mockChildProcess.stderr = new EventEmitter() spawnSpy = spyOn(childProcess, 'spawn') spawnSpy.mockReturnValue(mockChildProcess as any) const effect = showCommand(undefined, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) const resultPromise = Effect.runPromise(effect) setImmediate(() => { // @ts-ignore mockChildProcess.stdout.emit('data', Buffer.from(commitMessage)) mockChildProcess.emit('close', 0) }) await resultPromise const output = capturedErrors.join('\n') expect(output).toContain('No Change-ID found in HEAD commit') expect(capturedLogs.length).toBe(0) }) test('shows error when not in git repository', async () => { const mockChildProcess = new EventEmitter() // @ts-ignore mockChildProcess.stdout = new EventEmitter() // @ts-ignore mockChildProcess.stderr = new EventEmitter() spawnSpy = spyOn(childProcess, 'spawn') spawnSpy.mockReturnValue(mockChildProcess as any) const effect = showCommand(undefined, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) const resultPromise = Effect.runPromise(effect) setImmediate(() => { // @ts-ignore mockChildProcess.stderr.emit('data', Buffer.from('fatal: not a git repository')) mockChildProcess.emit('close', 128) }) await resultPromise const output = capturedErrors.join('\n') expect(output).toContain('fatal: not a git repository') }) test('still works with explicit change-id argument', async () => { // Don't mock git - should not be called when changeId is provided const effect = showCommand('If5a3ae8cb5a107e187447802358417f311d0c4b1', {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('Change 392385') expect(output).toContain('WIP: test') expect(capturedErrors.length).toBe(0) }) test('shows XML error when no Change-ID in commit with --xml flag', async () => { const commitMessage = `feat: no change id` const mockChildProcess = new EventEmitter() // @ts-ignore mockChildProcess.stdout = new EventEmitter() // @ts-ignore mockChildProcess.stderr = new EventEmitter() spawnSpy = spyOn(childProcess, 'spawn') spawnSpy.mockReturnValue(mockChildProcess as any) const effect = showCommand(undefined, { xml: true }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) const resultPromise = Effect.runPromise(effect) setImmediate(() => { // @ts-ignore mockChildProcess.stdout.emit('data', Buffer.from(commitMessage)) mockChildProcess.emit('close', 0) }) await resultPromise const output = capturedStdout.join('') expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>') expect(output).toContain('<show_result>') expect(output).toContain('<status>error</status>') expect(output).toContain('No Change-ID found in HEAD commit') }) })