GitNexus/gitnexus/test/unit/git-ref.test.ts
Gergő Magyar 9d95af9fc3
refactor(analyze): move detected-branch sanitization off CLI config (#3325)
* refactor(analyze): load detected-branch sanitization from core git-ref

Keep the never-throw helper next to validateBranchName so run-analyze no longer imports CLI config parsing.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(analyze): warn once when a checkout name cannot label the index

After the write lock settles, emit a single onLog warning and keep writing the workspace slot. Pin that run-analyze does not import CLI analyze-config.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(analyze): escape hidden checkout names in the detect-reject warning

Keep the rejected ref visible in onLog without replaying bidi or quote characters, and document that sanitizeDetectedBranch rethrows unexpected errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(analyze): keep detect-reject warnings on one line (#3325)

Git-legal U+2028/U+2029 checkout names were rejected as whitespace but left raw in the new onLog warning, so the message split across two lines. Escape those code points in the formatter without changing validateBranchName.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(autofix): apply prettier + eslint fixes via /autofix command

* fix(analyze): keep C1 and Unicode spaces in detect-reject warnings

Escape NEL and remaining whitespace as \uXXXX so stripControlCharacters cannot drop or disguise the rejected checkout name.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-18 11:03:24 +01:00

62 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
InvalidBranchError,
formatRejectedBranchForLog,
sanitizeDetectedBranch,
validateBranchName,
} from '../../src/core/git-ref.js';
describe('core/git-ref', () => {
it('throws InvalidBranchError with name "InvalidBranchError"', () => {
expect(() => validateBranchName('HEAD', 'src')).toThrow(InvalidBranchError);
try {
validateBranchName('HEAD', 'src');
throw new Error('expected InvalidBranchError');
} catch (err) {
expect(err).toBeInstanceOf(InvalidBranchError);
expect((err as Error).name).toBe('InvalidBranchError');
}
});
it('sanitizeDetectedBranch returns the trimmed name for a legal branch', () => {
expect(sanitizeDetectedBranch('develop')).toBe('develop');
expect(sanitizeDetectedBranch(' feature/foo-bar ')).toBe('feature/foo-bar');
});
it('sanitizeDetectedBranch returns undefined for null, empty, or whitespace', () => {
expect(sanitizeDetectedBranch(null)).toBeUndefined();
expect(sanitizeDetectedBranch(undefined)).toBeUndefined();
expect(sanitizeDetectedBranch('')).toBeUndefined();
expect(sanitizeDetectedBranch(' ')).toBeUndefined();
});
it('sanitizeDetectedBranch swallows InvalidBranchError and does not throw', () => {
expect(sanitizeDetectedBranch('feat`x')).toBeUndefined();
expect(sanitizeDetectedBranch('main`evil')).toBeUndefined();
expect(sanitizeDetectedBranch('HEAD')).toBeUndefined();
expect(() => sanitizeDetectedBranch('feat`x')).not.toThrow();
});
it('formatRejectedBranchForLog keeps backticks visible and escapes bidi, quotes, and line separators', () => {
expect(formatRejectedBranchForLog('feat`x')).toBe('feat`x');
expect(formatRejectedBranchForLog('a"b')).toBe('a\\"b');
expect(formatRejectedBranchForLog(`ok${'\u202e'}bad`)).toBe('ok\\u202ebad');
expect(formatRejectedBranchForLog(`zw${'\u200b'}sp`)).toBe('zw\\u200bsp');
expect(formatRejectedBranchForLog(`foo${'\u2028'}bar`)).toBe('foo\\u2028bar');
expect(formatRejectedBranchForLog(`foo${'\u2029'}bar`)).toBe('foo\\u2029bar');
});
it('formatRejectedBranchForLog escapes NBSP, NEL, and other C1 as \\uXXXX', () => {
expect(formatRejectedBranchForLog(`foo${'\u00a0'}bar`)).toBe('foo\\u00a0bar');
const nelBacktick = formatRejectedBranchForLog(`feat${'\u0085'}\``);
expect(nelBacktick).toContain('\\u0085');
expect(nelBacktick).toContain('`');
expect(nelBacktick).not.toContain('\u0085');
expect(formatRejectedBranchForLog(`x${'\u009f'}y`)).toBe('x\\u009fy');
});
it('sanitizeDetectedBranch rejects git-legal U+2028/U+2029 as whitespace', () => {
expect(sanitizeDetectedBranch(`foo${'\u2028'}bar`)).toBeUndefined();
expect(sanitizeDetectedBranch(`foo${'\u2029'}bar`)).toBeUndefined();
});
});