UNPKG

@aaronshaf/ger

Version:

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

324 lines (276 loc) 10.6 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 { groupsShowCommand } from '@/cli/commands/groups-show' import { ConfigService } from '@/services/config' import { createMockConfigService } from './helpers/config-mock' const mockGroupDetail = { id: 'administrators', name: 'Administrators', description: 'Site administrators with full access', owner: 'Administrators', owner_id: 'administrators', group_id: 1, options: { visible_to_all: true }, created_on: '2024-01-01 10:00:00.000000000', url: 'https://gerrit.example.com/admin/groups/uuid-administrators', members: [ { _account_id: 1000, name: 'John Doe', email: 'john@example.com', username: 'jdoe', }, { _account_id: 1001, name: 'Jane Smith', email: 'jane@example.com', username: 'jsmith', }, ], includes: [ { id: 'project-admins', name: 'Project Admins', }, { id: 'system-admins', name: 'System Admins', }, ], } const server = setupServer( 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('groups-show 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 show group details with members and subgroups', async () => { server.use( http.get('*/a/groups/administrators/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockGroupDetail)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('administrators', {}).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('Group: Administrators') expect(output).toContain('ID: administrators') expect(output).toContain('Numeric ID: 1') expect(output).toContain('Owner: Administrators') expect(output).toContain('Description: Site administrators with full access') expect(output).toContain('Visible to all: Yes') expect(output).toContain('Members (2):') expect(output).toContain('John Doe') expect(output).toContain('john@example.com') expect(output).toContain('Jane Smith') expect(output).toContain('Subgroups (2):') expect(output).toContain('Project Admins') expect(output).toContain('System Admins') }) it('should show group by numeric ID', async () => { server.use( http.get('*/a/groups/1/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockGroupDetail)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('1', {}).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('Group: Administrators') }) it('should show group by UUID', async () => { server.use( http.get('*/a/groups/uuid-123456/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockGroupDetail)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('uuid-123456', {}).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('Group: Administrators') }) it('should output XML format when --xml flag is used', async () => { server.use( http.get('*/a/groups/administrators/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(mockGroupDetail)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('administrators', { 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('<group_detail_result>') expect(output).toContain('<status>success</status>') expect(output).toContain('<group>') expect(output).toContain('<id><![CDATA[administrators]]></id>') expect(output).toContain('<name><![CDATA[Administrators]]></name>') expect(output).toContain( '<description><![CDATA[Site administrators with full access]]></description>', ) expect(output).toContain('<members>') expect(output).toContain('<member>') expect(output).toContain('<account_id>1000</account_id>') expect(output).toContain('<name><![CDATA[John Doe]]></name>') expect(output).toContain('<email><![CDATA[john@example.com]]></email>') expect(output).toContain('</member>') expect(output).toContain('</members>') expect(output).toContain('<subgroups>') expect(output).toContain('<subgroup>') expect(output).toContain('<id><![CDATA[project-admins]]></id>') expect(output).toContain('</subgroup>') expect(output).toContain('</subgroups>') expect(output).toContain('</group>') expect(output).toContain('</group_detail_result>') }) it('should handle group not found (404)', async () => { server.use( http.get('*/a/groups/nonexistent/detail', () => { return HttpResponse.text('Not Found', { status: 404 }) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('nonexistent', {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await expect(Effect.runPromise(program)).rejects.toThrow() }) it('should handle permission denied (403)', async () => { server.use( http.get('*/a/groups/secret-group/detail', () => { return HttpResponse.text('Forbidden', { status: 403 }) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('secret-group', {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await expect(Effect.runPromise(program)).rejects.toThrow() }) it('should handle network errors gracefully', async () => { server.use( http.get('*/a/groups/administrators/detail', () => { return HttpResponse.error() }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('administrators', {}).pipe( Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer), ) await expect(Effect.runPromise(program)).rejects.toThrow() }) it('should handle groups without members', async () => { const groupWithoutMembers = { ...mockGroupDetail, members: undefined, } server.use( http.get('*/a/groups/empty-group/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(groupWithoutMembers)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('empty-group', {}).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('Members: None') }) it('should handle groups without subgroups', async () => { const groupWithoutSubgroups = { ...mockGroupDetail, includes: undefined, } server.use( http.get('*/a/groups/no-subgroups/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(groupWithoutSubgroups)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('no-subgroups', {}).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('Subgroups: None') }) it('should handle members without optional fields', async () => { const groupWithMinimalMembers = { ...mockGroupDetail, members: [ { _account_id: 1000, }, ], } server.use( http.get('*/a/groups/minimal-members/detail', () => { return HttpResponse.text(`)]}'\n${JSON.stringify(groupWithMinimalMembers)}`) }), ) const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService()) const program = groupsShowCommand('minimal-members', { 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('<account_id>1000</account_id>') // Extract just the member section to check optional fields aren't present const memberMatch = output.match(/<member>[\s\S]*?<\/member>/) expect(memberMatch).toBeTruthy() const memberSection = memberMatch?.[0] || '' expect(memberSection).not.toContain('<name>') expect(memberSection).not.toContain('<email>') expect(memberSection).not.toContain('<username>') }) })