OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / git-browser.test.js

97 lines · javascript ref:
/**
 * 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)
})