/**
* Unit tests for lib/git-browser.js
*
* Tests path traversal protection and repo path construction.
* Does NOT test actual git commands (requires real repos).
*/
import test from 'brittle'
import { GitBrowser } from '../../lib/git-browser.js'
// ========================================
// getRepoPath - Path Traversal Protection
// ========================================
test('getRepoPath - constructs valid path', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
const result = browser.getRepoPath('my-repo')
t.ok(result.endsWith('my-repo.git'), 'ends with .git')
t.ok(result.startsWith('/home/user/git-repos'), 'within gitReposPath')
})
test('getRepoPath - rejects path traversal with ../', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
try {
browser.getRepoPath('../../../etc/passwd')
t.fail('should have thrown')
} catch (err) {
t.ok(err.message.includes('Invalid'), 'throws on path traversal')
}
})
test('getRepoPath - bare .. becomes ...git (stays in directory)', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
// '..' + '.git' = '...git' which is a valid (if odd) filename within gitReposPath
// This is NOT a traversal — the real danger is '../something'
const result = browser.getRepoPath('..')
t.ok(result.includes('...git'), '.. becomes ...git')
t.ok(result.startsWith('/home/user/git-repos'), 'stays within gitReposPath')
})
test('getRepoPath - rejects path with embedded ..', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
try {
browser.getRepoPath('foo/../../../etc')
t.fail('should have thrown')
} catch (err) {
t.ok(err.message.includes('Invalid'), 'throws on embedded ..')
}
})
test('getRepoPath - allows simple names', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
// These should all work fine
const names = ['repo', 'my-repo', 'repo123', 'opencog-atomspace']
for (const name of names) {
const result = browser.getRepoPath(name)
t.ok(result.includes(name), `"${name}" resolves correctly`)
}
})
test('getRepoPath - allows names with dots (not traversal)', (t) => {
const browser = new GitBrowser({ gitReposPath: '/home/user/git-repos' })
// Single dots in names are fine (not traversal)
const result = browser.getRepoPath('my.repo')
t.ok(result.includes('my.repo'), 'dot in name works')
})
// ========================================
// parseRepoNameFromURL
// ========================================
test('parseRepoNameFromURL - extracts from HTTPS URL', (t) => {
const browser = new GitBrowser()
t.is(browser.parseRepoNameFromURL('https://github.com/user/repo.git'), 'repo')
})
test('parseRepoNameFromURL - extracts from git:// URL', (t) => {
const browser = new GitBrowser()
t.is(browser.parseRepoNameFromURL('git://tabertron.local/holepunchto-kb.git'), 'holepunchto-kb')
})
test('parseRepoNameFromURL - extracts without .git suffix', (t) => {
const browser = new GitBrowser()
t.is(browser.parseRepoNameFromURL('https://github.com/user/repo'), 'repo')
})
test('parseRepoNameFromURL - returns null for invalid URL', (t) => {
const browser = new GitBrowser()
t.is(browser.parseRepoNameFromURL('invalid'), null)
})