UNPKG

@aaronshaf/ger

Version:

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

269 lines (222 loc) 8.15 kB
import { describe, test, expect, beforeAll, afterAll, afterEach, mock } 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 { commentCommand } from '@/cli/commands/comment' import { diffCommand } from '@/cli/commands/diff' import { GerritApiServiceLive } from '@/api/gerrit' import { ConfigService } from '@/services/config' import { generateMockChange } from '@/test-utils/mock-generator' import { createMockConfigService } from './helpers/config-mock' /** * Integration tests to verify that commands accept both change number and Change-ID formats */ const CHANGE_NUMBER = '392385' const CHANGE_ID = 'If5a3ae8cb5a107e187447802358417f311d0c4b1' const mockChange = generateMockChange({ _number: 392385, change_id: CHANGE_ID, 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 both change number and Change-ID http.get('*/a/changes/:changeId', ({ params }) => { const { changeId } = params // Accept both formats if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) { 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 === CHANGE_NUMBER || changeId === CHANGE_ID) { 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 === CHANGE_NUMBER || changeId === CHANGE_ID) { return HttpResponse.text(`)]}' {}`) } return HttpResponse.text('Not Found', { status: 404 }) }), http.post('*/a/changes/:changeId/revisions/current/review', async ({ params }) => { const { changeId } = params if (changeId === CHANGE_NUMBER || changeId === CHANGE_ID) { return HttpResponse.text(`)]}' {}`) } return HttpResponse.text('Not Found', { status: 404 }) }), ) let capturedLogs: string[] = [] let capturedErrors: string[] = [] const mockConsoleLog = mock((...args: any[]) => { capturedLogs.push(args.join(' ')) }) const mockConsoleError = mock((...args: any[]) => { capturedErrors.push(args.join(' ')) }) const originalConsoleLog = console.log const originalConsoleError = console.error beforeAll(() => { server.listen({ onUnhandledRequest: 'bypass' }) // @ts-ignore console.log = mockConsoleLog // @ts-ignore console.error = mockConsoleError }) afterAll(() => { server.close() console.log = originalConsoleLog console.error = originalConsoleError }) afterEach(() => { server.resetHandlers() mockConsoleLog.mockClear() mockConsoleError.mockClear() capturedLogs = [] capturedErrors = [] }) const createMockConfigLayer = (): Layer.Layer<ConfigService, never, never> => Layer.succeed(ConfigService, createMockConfigService()) describe('Change ID format support', () => { describe('show command', () => { test('accepts numeric change number', async () => { const effect = showCommand(CHANGE_NUMBER, {}).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('accepts Change-ID format', async () => { const effect = showCommand(CHANGE_ID, {}).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('rejects invalid change identifier', async () => { const effect = showCommand('invalid-id', {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedErrors.join('\n') expect(output).toContain('Invalid change identifier') }) }) describe('diff command', () => { test('accepts numeric change number', async () => { const effect = diffCommand(CHANGE_NUMBER, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('--- a/test.txt') expect(output).toContain('+++ b/test.txt') expect(capturedErrors.length).toBe(0) }) test('accepts Change-ID format', async () => { const effect = diffCommand(CHANGE_ID, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('--- a/test.txt') expect(output).toContain('+++ b/test.txt') expect(capturedErrors.length).toBe(0) }) }) describe('comment command', () => { test('accepts numeric change number', async () => { const effect = commentCommand(CHANGE_NUMBER, { message: 'LGTM' }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('Comment posted successfully') expect(capturedErrors.length).toBe(0) }) test('accepts Change-ID format', async () => { const effect = commentCommand(CHANGE_ID, { message: 'LGTM' }).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('Comment posted successfully') expect(capturedErrors.length).toBe(0) }) }) describe('edge cases', () => { test('trims whitespace from change identifiers', async () => { const effect = showCommand(` ${CHANGE_NUMBER} `, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedLogs.join('\n') expect(output).toContain('Change 392385') expect(capturedErrors.length).toBe(0) }) test('validates Change-ID format strictly (uppercase I)', async () => { const lowercaseChangeId = 'if5a3ae8cb5a107e187447802358417f311d0c4b1' const effect = showCommand(lowercaseChangeId, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedErrors.join('\n') expect(output).toContain('Invalid change identifier') }) test('rejects Change-ID with incorrect length', async () => { const shortChangeId = 'If5a3ae8cb5a107e18744780235841' const effect = showCommand(shortChangeId, {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(createMockConfigLayer()), ) await Effect.runPromise(effect) const output = capturedErrors.join('\n') expect(output).toContain('Invalid change identifier') }) }) })