OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / reserved-names.test.js

114 lines · javascript ref:
/**
 * Unit tests for lib/reserved-names.js
 *
 * Tests name validation, reserved names, and edge cases.
 */

import test from 'brittle'
import { RESERVED_NAMES, NAME_REGEX, validateName } from '../../lib/reserved-names.js'

// ========================================
// Reserved Names Set
// ========================================

test('RESERVED_NAMES - contains all system route prefixes', (t) => {
  const required = ['api', 'admin', 'profile', 'search', 'login', 'deploy', 'merge', 'groups', 'join']
  for (const name of required) {
    t.ok(RESERVED_NAMES.has(name), `"${name}" is reserved`)
  }
})

test('RESERVED_NAMES - contains future route prefixes', (t) => {
  const future = ['static', 'assets', 'settings', 'help', 'new', 'explore', 'notifications']
  for (const name of future) {
    t.ok(RESERVED_NAMES.has(name), `"${name}" is reserved`)
  }
})

test('RESERVED_NAMES - contains separator characters', (t) => {
  t.ok(RESERVED_NAMES.has('-'), '"-" is reserved')
  t.ok(RESERVED_NAMES.has('_'), '"_" is reserved')
})

// ========================================
// NAME_REGEX
// ========================================

test('NAME_REGEX - accepts valid names', (t) => {
  const valid = ['a', 'abc', 'my-repo', 'repo123', 'a1b2c3', 'x']
  for (const name of valid) {
    t.ok(NAME_REGEX.test(name), `"${name}" should match`)
  }
})

test('NAME_REGEX - rejects names with leading/trailing hyphens', (t) => {
  t.not(NAME_REGEX.test('-repo'), '"-repo" should not match')
  t.not(NAME_REGEX.test('repo-'), '"repo-" should not match')
  t.not(NAME_REGEX.test('-'), '"-" alone should not match (length 1)')
})

test('NAME_REGEX - rejects names with uppercase', (t) => {
  t.not(NAME_REGEX.test('MyRepo'), '"MyRepo" should not match')
  t.not(NAME_REGEX.test('REPO'), '"REPO" should not match')
})

test('NAME_REGEX - rejects names with special characters', (t) => {
  t.not(NAME_REGEX.test('repo.name'), '"repo.name" should not match')
  t.not(NAME_REGEX.test('repo_name'), '"repo_name" should not match')
  t.not(NAME_REGEX.test('repo/name'), '"repo/name" should not match')
  t.not(NAME_REGEX.test('repo name'), '"repo name" should not match')
})

// ========================================
// validateName
// ========================================

test('validateName - accepts valid names', (t) => {
  t.ok(validateName('my-repo').valid, '"my-repo" is valid')
  t.ok(validateName('repo123').valid, '"repo123" is valid')
  t.ok(validateName('a').valid, 'single char "a" is valid')
  t.ok(validateName('x1').valid, '"x1" is valid')
})

test('validateName - rejects empty/null/undefined', (t) => {
  t.not(validateName('').valid, 'empty string rejected')
  t.not(validateName(null).valid, 'null rejected')
  t.not(validateName(undefined).valid, 'undefined rejected')
})

test('validateName - rejects names over 64 chars', (t) => {
  const long = 'a'.repeat(65)
  const result = validateName(long)
  t.not(result.valid, '65-char name rejected')
  t.ok(result.error.includes('1-64'), 'error mentions length limit')
})

test('validateName - accepts exactly 64 chars', (t) => {
  const name = 'a'.repeat(64)
  t.ok(validateName(name).valid, '64-char name accepted')
})

test('validateName - rejects consecutive hyphens', (t) => {
  const result = validateName('my--repo')
  t.not(result.valid, '"my--repo" rejected')
  t.ok(result.error.includes('consecutive'), 'error mentions consecutive hyphens')
})

test('validateName - rejects all reserved names', (t) => {
  for (const name of RESERVED_NAMES) {
    // Skip '-' and '_' since they fail regex before reaching reserved check
    if (name === '-' || name === '_') continue
    const result = validateName(name)
    t.not(result.valid, `"${name}" should be rejected as reserved`)
    t.ok(result.error.includes('reserved'), `"${name}" error mentions reserved`)
  }
})

test('validateName - rejects path traversal attempts', (t) => {
  // These fail the regex (contain . / or uppercase)
  t.not(validateName('../etc').valid, '"../etc" rejected')
  t.not(validateName('../../passwd').valid, '"../../passwd" rejected')
  t.not(validateName('.git').valid, '".git" rejected')
  t.not(validateName('a/b').valid, '"a/b" rejected')
})