@aaronshaf/ger
Version:
Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS
133 lines (108 loc) • 4.77 kB
text/typescript
import { test, expect, describe } from 'bun:test'
import { CHANGE_ID_PATTERN } from '@/services/commit-hook'
// Tests for commit-hook service patterns
// Note: Tests that require actual git operations are skipped in the full test suite
// due to mock pollution from other test files. Run these tests in isolation for full coverage:
// bun test tests/unit/services/commit-hook.test.ts
describe('Commit Hook Service', () => {
describe('CHANGE_ID_PATTERN', () => {
test('should match valid Change-Id', () => {
const validIds = [
'Change-Id: I1234567890123456789012345678901234567890',
'Change-Id: Iabcdefabcdefabcdefabcdefabcdefabcdefabcd',
'Change-Id: I0000000000000000000000000000000000000000',
'Change-Id: Iffffffffffffffffffffffffffffffffffffffff',
]
for (const id of validIds) {
expect(CHANGE_ID_PATTERN.test(id)).toBe(true)
}
})
test('should not match invalid Change-Id', () => {
const invalidIds = [
'Change-Id: 1234567890123456789012345678901234567890', // Missing I prefix
'Change-Id: I123456789012345678901234567890123456789', // Too short (39 chars)
'Change-Id: I12345678901234567890123456789012345678901', // Too long (41 chars)
'Change-Id: IGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', // Invalid hex chars
'Change-Id: i1234567890123456789012345678901234567890', // Lowercase I
'change-id: I1234567890123456789012345678901234567890', // Lowercase prefix
]
for (const id of invalidIds) {
expect(CHANGE_ID_PATTERN.test(id)).toBe(false)
}
})
test('should match Change-Id in multiline commit message', () => {
const commitMessage = `Fix authentication bug
This commit fixes the login issue where users
were being logged out unexpectedly.
Change-Id: I1234567890123456789012345678901234567890
Signed-off-by: Test User <test@example.com>`
expect(CHANGE_ID_PATTERN.test(commitMessage)).toBe(true)
})
test('should not match Change-Id in wrong position', () => {
// Change-Id should be at start of line
const wrongPosition = ' Change-Id: I1234567890123456789012345678901234567890'
expect(CHANGE_ID_PATTERN.test(wrongPosition)).toBe(false)
})
})
describe('hook path patterns', () => {
test('should construct correct hooks directory path', () => {
const gitDir = '.git'
const hooksDir = `${gitDir}/hooks`
expect(hooksDir).toBe('.git/hooks')
})
test('should construct correct commit-msg hook path', () => {
const gitDir = '.git'
const hookPath = `${gitDir}/hooks/commit-msg`
expect(hookPath).toBe('.git/hooks/commit-msg')
})
test('should handle absolute git dir path', () => {
const gitDir = '/home/user/project/.git'
const hookPath = `${gitDir}/hooks/commit-msg`
expect(hookPath).toBe('/home/user/project/.git/hooks/commit-msg')
})
})
describe('hook URL construction', () => {
test('should construct correct hook URL', () => {
const host = 'https://gerrit.example.com'
const hookUrl = `${host}/tools/hooks/commit-msg`
expect(hookUrl).toBe('https://gerrit.example.com/tools/hooks/commit-msg')
})
test('should handle host with trailing slash', () => {
const host = 'https://gerrit.example.com/'
const normalizedHost = host.replace(/\/$/, '')
const hookUrl = `${normalizedHost}/tools/hooks/commit-msg`
expect(hookUrl).toBe('https://gerrit.example.com/tools/hooks/commit-msg')
})
})
describe('hook content validation', () => {
test('should validate shell script header', () => {
const validHook = '#!/bin/sh\necho "Adding Change-Id"'
expect(validHook.startsWith('#!')).toBe(true)
})
test('should reject non-script content', () => {
const invalidHook = 'This is not a script'
expect(invalidHook.startsWith('#!')).toBe(false)
})
test('should validate bash script header', () => {
const bashHook = '#!/bin/bash\necho "Adding Change-Id"'
expect(bashHook.startsWith('#!')).toBe(true)
})
})
describe('executable bit checking', () => {
test('should identify executable mode', () => {
const executableMode = 0o755
const ownerExecuteBit = 0o100
expect((executableMode & ownerExecuteBit) !== 0).toBe(true)
})
test('should identify non-executable mode', () => {
const nonExecutableMode = 0o644
const ownerExecuteBit = 0o100
expect((nonExecutableMode & ownerExecuteBit) !== 0).toBe(false)
})
test('should handle read-only mode', () => {
const readOnlyMode = 0o444
const ownerExecuteBit = 0o100
expect((readOnlyMode & ownerExecuteBit) !== 0).toBe(false)
})
})
})