Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/just-cameron/tangled-cli. Automation-first CLI for Tangled.org
Something went wrong. Try again.
8.3 kB · 264 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265import { homedir } from 'node:os';import { join } from 'node:path';import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';import { clearLocalRemote, clearUserRemote, getConfiguredRemote, loadConfig, setLocalRemote, setUserRemote,} from '../../src/lib/config.js';
// Mock modulesvi.mock('node:fs/promises');vi.mock('simple-git');vi.mock('cosmiconfig');
// Import mocked modulesimport * as fs from 'node:fs/promises';import { cosmiconfig } from 'cosmiconfig';import { simpleGit } from 'simple-git';
describe('Config Management', () => { let originalEnv: string | undefined;
beforeEach(() => { vi.clearAllMocks(); originalEnv = process.env.TANGLED_REMOTE; // biome-ignore lint/performance/noDelete: Need to actually delete env var, not set to undefined delete process.env.TANGLED_REMOTE; });
afterEach(() => { if (originalEnv !== undefined) { process.env.TANGLED_REMOTE = originalEnv; } else { // biome-ignore lint/performance/noDelete: Need to actually delete env var, not set to undefined delete process.env.TANGLED_REMOTE; } });
describe('loadConfig', () => { it('should return config from TANGLED_REMOTE environment variable', async () => { process.env.TANGLED_REMOTE = 'upstream';
const config = await loadConfig();
expect(config).toEqual({ remote: 'upstream' }); });
it('should load config from file when env var not set', async () => { const mockExplorer = { search: vi.fn().mockResolvedValue({ config: { remote: 'origin' }, filepath: '/test/.tangledrc', isEmpty: false, }), };
vi.mocked(cosmiconfig).mockReturnValue(mockExplorer as never);
// Mock Git root const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(true), revparse: vi.fn().mockResolvedValue('/test/repo\n'), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
const config = await loadConfig('/test/repo');
expect(config).toEqual({ remote: 'origin' }); expect(mockExplorer.search).toHaveBeenCalledWith('/test/repo'); });
it('should return empty config when no config file found', async () => { const mockExplorer = { search: vi.fn().mockResolvedValue(null), };
vi.mocked(cosmiconfig).mockReturnValue(mockExplorer as never);
// Mock Git root const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(false), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
const config = await loadConfig();
expect(config).toEqual({}); });
it('should handle cosmiconfig errors gracefully', async () => { const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const mockExplorer = { search: vi.fn().mockRejectedValue(new Error('Config read error')), };
vi.mocked(cosmiconfig).mockReturnValue(mockExplorer as never);
// Mock Git root const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(false), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
const config = await loadConfig();
expect(config).toEqual({}); expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to load config'));
consoleWarnSpy.mockRestore(); }); });
describe('getConfiguredRemote', () => { it('should return remote name from config', async () => { process.env.TANGLED_REMOTE = 'upstream';
const remote = await getConfiguredRemote();
expect(remote).toBe('upstream'); });
it('should return null when no config found', async () => { const mockExplorer = { search: vi.fn().mockResolvedValue(null), };
vi.mocked(cosmiconfig).mockReturnValue(mockExplorer as never);
// Mock not in Git repo const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(false), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
const remote = await getConfiguredRemote();
expect(remote).toBeNull(); }); });
describe('setLocalRemote', () => { it('should write config to Git root directory', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(true), revparse: vi.fn().mockResolvedValue('/test/repo\n'), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never); vi.mocked(fs.writeFile).mockResolvedValue(undefined);
await setLocalRemote('origin', '/test/repo');
expect(fs.writeFile).toHaveBeenCalledWith( '/test/repo/.tangledrc', `${JSON.stringify({ remote: 'origin' }, null, 2)}\n`, 'utf-8' ); });
it('should throw error when not in Git repository', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(false), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
await expect(setLocalRemote('origin')).rejects.toThrow('Not in a Git repository'); });
it('should throw error on write failure', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(true), revparse: vi.fn().mockResolvedValue('/test/repo\n'), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never); vi.mocked(fs.writeFile).mockRejectedValue(new Error('Write failed'));
await expect(setLocalRemote('origin')).rejects.toThrow('Failed to write local config'); }); });
describe('setUserRemote', () => { it('should write config to user home directory', async () => { vi.mocked(fs.mkdir).mockResolvedValue(undefined); vi.mocked(fs.writeFile).mockResolvedValue(undefined);
await setUserRemote('origin');
const expectedPath = join(homedir(), '.tangledrc'); expect(fs.mkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPath, `${JSON.stringify({ remote: 'origin' }, null, 2)}\n`, 'utf-8' ); });
it('should throw error on write failure', async () => { vi.mocked(fs.mkdir).mockResolvedValue(undefined); vi.mocked(fs.writeFile).mockRejectedValue(new Error('Write failed'));
await expect(setUserRemote('origin')).rejects.toThrow('Failed to write user config'); }); });
describe('clearLocalRemote', () => { it('should delete local config file', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(true), revparse: vi.fn().mockResolvedValue('/test/repo\n'), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never); vi.mocked(fs.unlink).mockResolvedValue(undefined);
await clearLocalRemote('/test/repo');
expect(fs.unlink).toHaveBeenCalledWith('/test/repo/.tangledrc'); });
it('should not throw error if file does not exist', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(true), revparse: vi.fn().mockResolvedValue('/test/repo\n'), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
const error = new Error('File not found') as NodeJS.ErrnoException; error.code = 'ENOENT'; vi.mocked(fs.unlink).mockRejectedValue(error);
await expect(clearLocalRemote()).resolves.not.toThrow(); });
it('should throw error when not in Git repository', async () => { const mockGit = { checkIsRepo: vi.fn().mockResolvedValue(false), }; vi.mocked(simpleGit).mockReturnValue(mockGit as never);
await expect(clearLocalRemote()).rejects.toThrow('Not in a Git repository'); }); });
describe('clearUserRemote', () => { it('should delete user config file', async () => { vi.mocked(fs.unlink).mockResolvedValue(undefined);
await clearUserRemote();
const expectedPath = join(homedir(), '.tangledrc'); expect(fs.unlink).toHaveBeenCalledWith(expectedPath); });
it('should not throw error if file does not exist', async () => { const error = new Error('File not found') as NodeJS.ErrnoException; error.code = 'ENOENT'; vi.mocked(fs.unlink).mockRejectedValue(error);
await expect(clearUserRemote()).resolves.not.toThrow(); }); });});