@aaronshaf/ger
Version:
Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS
272 lines (219 loc) • 9.72 kB
text/typescript
import { describe, expect, test } from 'bun:test'
import { extractChangeNumber, isValidChangeId, normalizeGerritHost } from './url-parser'
describe('extractChangeNumber', () => {
test('extracts change number from standard Gerrit URL', () => {
const url = 'https://gerrit.example.com/c/my-project/+/384571'
expect(extractChangeNumber(url)).toBe('384571')
})
test('extracts change number from URL with trailing slash', () => {
const url = 'https://gerrit.example.com/c/my-project/+/384571/'
expect(extractChangeNumber(url)).toBe('384571')
})
test('extracts change number from URL with patchset', () => {
const url = 'https://gerrit.example.com/c/my-project/+/384571/2'
expect(extractChangeNumber(url)).toBe('384571')
})
test('extracts change number from hash-based URL', () => {
const url = 'https://gerrit.example.com/#/c/my-project/+/384571/'
expect(extractChangeNumber(url)).toBe('384571')
})
test('extracts change number from simplified URL format', () => {
const url = 'https://gerrit.example.com/c/+/384571'
expect(extractChangeNumber(url)).toBe('384571')
})
test('extracts change number from hash-based simplified URL', () => {
const url = 'https://gerrit.example.com/#/c/+/384571'
expect(extractChangeNumber(url)).toBe('384571')
})
test('returns plain change number as-is', () => {
expect(extractChangeNumber('384571')).toBe('384571')
})
test('returns Change-Id format as-is', () => {
const changeId = 'Iabcdef1234567890abcdef1234567890abcdef12'
expect(extractChangeNumber(changeId)).toBe(changeId)
})
test('returns original input for invalid URLs', () => {
const invalidUrl = 'https://gerrit.example.com/invalid/path'
expect(extractChangeNumber(invalidUrl)).toBe(invalidUrl)
})
test('handles malformed URLs gracefully', () => {
const malformed = 'not-a-url-at-all'
expect(extractChangeNumber(malformed)).toBe(malformed)
})
test('handles http:// URLs', () => {
const httpUrl = 'http://gerrit.example.com/c/project/+/123456'
expect(extractChangeNumber(httpUrl)).toBe('123456')
})
test('handles malformed https URLs that throw in URL constructor', () => {
const malformed = 'https://[invalid-url'
expect(extractChangeNumber(malformed)).toBe(malformed)
})
test('handles empty string', () => {
expect(extractChangeNumber('')).toBe('')
})
test('handles whitespace', () => {
expect(extractChangeNumber(' 384571 ')).toBe('384571')
})
})
describe('isValidChangeId', () => {
test('validates numeric change IDs', () => {
expect(isValidChangeId('384571')).toBe(true)
expect(isValidChangeId('1')).toBe(true)
expect(isValidChangeId('999999')).toBe(true)
})
test('rejects zero and negative numbers', () => {
expect(isValidChangeId('0')).toBe(false)
expect(isValidChangeId('-1')).toBe(false)
})
test('validates Change-Id format', () => {
const validChangeId = 'Iabcdef1234567890abcdef1234567890abcdef12'
expect(isValidChangeId(validChangeId)).toBe(true)
})
test('rejects invalid Change-Id format', () => {
// Only reject if it doesn't follow the strict Change-Id format when it starts with 'I' and is long
expect(isValidChangeId('abcdef1234567890abcdef1234567890abcdef12')).toBe(true) // valid topic name
expect(isValidChangeId('Iabc')).toBe(true) // could be a valid topic or branch name
})
test('validates other identifier formats', () => {
expect(isValidChangeId('topic-branch')).toBe(true)
expect(isValidChangeId('feature/new-thing')).toBe(true)
})
test('rejects empty and whitespace-only strings', () => {
expect(isValidChangeId('')).toBe(false)
expect(isValidChangeId(' ')).toBe(false)
expect(isValidChangeId('has spaces')).toBe(false)
})
test('handles exact Change-Id format validation', () => {
// Valid Change-Id: starts with 'I' and exactly 40 hex chars
expect(isValidChangeId('I1234567890abcdef1234567890abcdef12345678')).toBe(true)
// Invalid: wrong length
expect(isValidChangeId('I123')).toBe(true) // this is treated as a valid topic name
expect(isValidChangeId('I1234567890abcdef1234567890abcdef123456789')).toBe(true) // too long, treated as topic
// Invalid: non-hex characters
expect(isValidChangeId('I1234567890abcdef1234567890abcdef1234567g')).toBe(true) // treated as topic name
})
test('rejects strings starting with dash', () => {
expect(isValidChangeId('-123')).toBe(false)
expect(isValidChangeId('-abc')).toBe(false)
})
})
describe('normalizeGerritHost', () => {
describe('adding protocol', () => {
test('adds https:// when no protocol is provided', () => {
expect(normalizeGerritHost('gerrit.example.com')).toBe('https://gerrit.example.com')
})
test('adds https:// to hostname with port', () => {
expect(normalizeGerritHost('gerrit.example.com:8080')).toBe('https://gerrit.example.com:8080')
})
test('adds https:// to localhost', () => {
expect(normalizeGerritHost('localhost:8080')).toBe('https://localhost:8080')
})
test('adds https:// to IP address', () => {
expect(normalizeGerritHost('192.168.1.100')).toBe('https://192.168.1.100')
})
test('adds https:// to IP address with port', () => {
expect(normalizeGerritHost('192.168.1.100:8080')).toBe('https://192.168.1.100:8080')
})
})
describe('preserving existing protocol', () => {
test('preserves https:// when already present', () => {
expect(normalizeGerritHost('https://gerrit.example.com')).toBe('https://gerrit.example.com')
})
test('preserves http:// when explicitly provided', () => {
expect(normalizeGerritHost('http://gerrit.example.com')).toBe('http://gerrit.example.com')
})
test('preserves https:// with port', () => {
expect(normalizeGerritHost('https://gerrit.example.com:8080')).toBe(
'https://gerrit.example.com:8080',
)
})
test('preserves http:// with port', () => {
expect(normalizeGerritHost('http://gerrit.example.com:8080')).toBe(
'http://gerrit.example.com:8080',
)
})
})
describe('removing trailing slashes', () => {
test('removes single trailing slash', () => {
expect(normalizeGerritHost('https://gerrit.example.com/')).toBe('https://gerrit.example.com')
})
test('removes trailing slash from URL without protocol', () => {
expect(normalizeGerritHost('gerrit.example.com/')).toBe('https://gerrit.example.com')
})
test('removes trailing slash from URL with port', () => {
expect(normalizeGerritHost('https://gerrit.example.com:8080/')).toBe(
'https://gerrit.example.com:8080',
)
})
test('handles URL without trailing slash', () => {
expect(normalizeGerritHost('https://gerrit.example.com')).toBe('https://gerrit.example.com')
})
test('does not remove slash from path', () => {
expect(normalizeGerritHost('https://gerrit.example.com/gerrit')).toBe(
'https://gerrit.example.com/gerrit',
)
})
test('removes trailing slash from path', () => {
expect(normalizeGerritHost('https://gerrit.example.com/gerrit/')).toBe(
'https://gerrit.example.com/gerrit',
)
})
})
describe('whitespace handling', () => {
test('trims leading whitespace', () => {
expect(normalizeGerritHost(' gerrit.example.com')).toBe('https://gerrit.example.com')
})
test('trims trailing whitespace', () => {
expect(normalizeGerritHost('gerrit.example.com ')).toBe('https://gerrit.example.com')
})
test('trims whitespace from URL with protocol', () => {
expect(normalizeGerritHost(' https://gerrit.example.com ')).toBe(
'https://gerrit.example.com',
)
})
test('trims whitespace and removes trailing slash', () => {
expect(normalizeGerritHost(' gerrit.example.com/ ')).toBe('https://gerrit.example.com')
})
})
describe('combined scenarios', () => {
test('adds protocol and removes trailing slash', () => {
expect(normalizeGerritHost('gerrit.example.com/')).toBe('https://gerrit.example.com')
})
test('trims, adds protocol, and removes trailing slash', () => {
expect(normalizeGerritHost(' gerrit.example.com/ ')).toBe('https://gerrit.example.com')
})
test('handles subdomain with port', () => {
expect(normalizeGerritHost('review.git.example.com:8443')).toBe(
'https://review.git.example.com:8443',
)
})
test('handles complex URL with path', () => {
expect(normalizeGerritHost('gerrit.example.com/gerrit')).toBe(
'https://gerrit.example.com/gerrit',
)
})
test('normalizes complete real-world example', () => {
expect(normalizeGerritHost('gerrit-review.example.org')).toBe(
'https://gerrit-review.example.org',
)
})
})
describe('edge cases', () => {
test('handles empty string', () => {
// Empty string becomes 'https:/' after normalization (protocol added, then trailing slash removed)
expect(normalizeGerritHost('')).toBe('https:/')
})
test('handles whitespace-only string', () => {
// Whitespace-only string becomes 'https:/' after normalization
expect(normalizeGerritHost(' ')).toBe('https:/')
})
test('handles just a slash', () => {
// Just a slash becomes 'https://' (protocol added to '/', then trailing slash removed leaving '//')
expect(normalizeGerritHost('/')).toBe('https://')
})
test('handles protocol only', () => {
// Protocol only becomes 'https:/' (trailing slash removed)
expect(normalizeGerritHost('https://')).toBe('https:/')
})
})
})