git-drive 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +77 -0
- package/.planning/codebase/ARCHITECTURE.md +151 -0
- package/.planning/codebase/CONCERNS.md +191 -0
- package/.planning/codebase/CONVENTIONS.md +169 -0
- package/.planning/codebase/INTEGRATIONS.md +94 -0
- package/.planning/codebase/STACK.md +77 -0
- package/.planning/codebase/STRUCTURE.md +157 -0
- package/.planning/codebase/TESTING.md +156 -0
- package/Dockerfile.cli +30 -0
- package/Dockerfile.server +32 -0
- package/README.md +157 -0
- package/docker-compose.yml +48 -0
- package/package.json +20 -55
- package/packages/cli/Dockerfile +26 -0
- package/packages/cli/jest.config.js +26 -0
- package/packages/cli/package.json +65 -0
- package/packages/cli/src/__tests__/commands/companion.test.ts +152 -0
- package/packages/cli/src/__tests__/commands/init.test.ts +154 -0
- package/packages/cli/src/__tests__/commands/list.test.ts +122 -0
- package/packages/cli/src/__tests__/commands/push.test.ts +155 -0
- package/packages/cli/src/__tests__/commands/restore.test.ts +135 -0
- package/packages/cli/src/__tests__/commands/status.test.ts +199 -0
- package/packages/cli/src/__tests__/config.test.ts +198 -0
- package/packages/cli/src/__tests__/e2e.test.ts +125 -0
- package/packages/cli/src/__tests__/errors.test.ts +66 -0
- package/packages/cli/src/__tests__/git.test.ts +250 -0
- package/packages/cli/src/__tests__/server.test.ts +371 -0
- package/packages/cli/src/commands/archive.ts +39 -0
- package/packages/cli/src/commands/companion.ts +205 -0
- package/packages/cli/src/commands/init.ts +130 -0
- package/packages/cli/src/commands/link.ts +151 -0
- package/packages/cli/src/commands/list.ts +94 -0
- package/packages/cli/src/commands/push.ts +77 -0
- package/packages/cli/src/commands/restore.ts +36 -0
- package/packages/cli/src/commands/status.ts +127 -0
- package/packages/cli/src/config.ts +73 -0
- package/packages/cli/src/errors.ts +23 -0
- package/packages/cli/src/git.ts +60 -0
- package/packages/cli/src/index.ts +129 -0
- package/packages/cli/src/server.ts +700 -0
- package/packages/cli/tsconfig.json +13 -0
- package/packages/git-drive-docker/package.json +15 -0
- package/packages/server/package.json +44 -0
- package/packages/server/src/index.ts +569 -0
- package/packages/server/tsconfig.json +9 -0
- package/packages/ui/README.md +73 -0
- package/packages/ui/eslint.config.js +23 -0
- package/packages/ui/index.html +13 -0
- package/packages/ui/package.json +52 -0
- package/packages/ui/postcss.config.js +6 -0
- package/packages/ui/public/vite.svg +1 -0
- package/packages/ui/src/App.css +23 -0
- package/packages/ui/src/App.test.tsx +248 -0
- package/packages/ui/src/App.tsx +803 -0
- package/packages/ui/src/assets/react.svg +8 -0
- package/packages/ui/src/assets/vite.svg +3 -0
- package/packages/ui/src/index.css +37 -0
- package/packages/ui/src/main.tsx +14 -0
- package/packages/ui/src/test/setup.ts +1 -0
- package/packages/ui/tailwind.config.js +11 -0
- package/packages/ui/tsconfig.app.json +28 -0
- package/packages/ui/tsconfig.json +26 -0
- package/packages/ui/tsconfig.node.json +12 -0
- package/packages/ui/vite.config.ts +7 -0
- package/packages/ui/vitest.config.ts +20 -0
- package/pnpm-workspace.yaml +4 -0
- package/rewrite_app.js +731 -0
- package/tsconfig.json +14 -0
- package/dist/__tests__/commands/init.test.js +0 -123
- package/dist/__tests__/commands/list.test.js +0 -91
- package/dist/__tests__/commands/push.test.js +0 -128
- package/dist/__tests__/commands/restore.test.js +0 -99
- package/dist/__tests__/commands/status.test.js +0 -151
- package/dist/__tests__/config.test.js +0 -150
- package/dist/__tests__/e2e.test.js +0 -107
- package/dist/__tests__/errors.test.js +0 -56
- package/dist/__tests__/git.test.js +0 -184
- package/dist/__tests__/server.test.js +0 -310
- package/dist/commands/archive.js +0 -32
- package/dist/commands/init.js +0 -55
- package/dist/commands/link.js +0 -175
- package/dist/commands/list.js +0 -83
- package/dist/commands/push.js +0 -112
- package/dist/commands/restore.js +0 -30
- package/dist/commands/status.js +0 -116
- package/dist/config.js +0 -62
- package/dist/errors.js +0 -30
- package/dist/git.js +0 -67
- package/dist/index.js +0 -108
- package/dist/server.js +0 -535
- /package/{ui → packages/cli/ui}/assets/index-Br8xQbJz.js +0 -0
- /package/{ui → packages/cli/ui}/assets/index-Cc2q1t5k.js +0 -0
- /package/{ui → packages/cli/ui}/assets/index-DrL7ojPA.css +0 -0
- /package/{ui → packages/cli/ui}/index.html +0 -0
- /package/{ui → packages/cli/ui}/vite.svg +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests for git-drive CLI
|
|
3
|
+
*
|
|
4
|
+
* These tests simulate full command workflows using mocked file systems
|
|
5
|
+
* and git operations to test the complete flow of the CLI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { vol } from 'memfs';
|
|
9
|
+
|
|
10
|
+
// Mock fs
|
|
11
|
+
jest.mock('fs', () => {
|
|
12
|
+
const { fs } = require('memfs');
|
|
13
|
+
return fs;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Mock child_process
|
|
17
|
+
jest.mock('child_process', () => ({
|
|
18
|
+
execSync: jest.fn(),
|
|
19
|
+
spawn: jest.fn(() => ({
|
|
20
|
+
on: jest.fn(),
|
|
21
|
+
unref: jest.fn(),
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Mock node-disk-info
|
|
26
|
+
jest.mock('node-disk-info', () => ({
|
|
27
|
+
getDiskInfo: jest.fn(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Mock prompts
|
|
31
|
+
jest.mock('prompts', () => ({
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: jest.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock os
|
|
37
|
+
jest.mock('os', () => ({
|
|
38
|
+
homedir: () => '/home/testuser',
|
|
39
|
+
hostname: () => 'test-machine',
|
|
40
|
+
userInfo: () => ({ username: 'testuser' }),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
import { getDiskInfo } from 'node-disk-info';
|
|
44
|
+
import { execSync } from 'child_process';
|
|
45
|
+
|
|
46
|
+
const mockExecSync = execSync as jest.Mock;
|
|
47
|
+
const mockGetDiskInfo = getDiskInfo as jest.Mock;
|
|
48
|
+
|
|
49
|
+
describe('E2E: Full Workflow', () => {
|
|
50
|
+
let consoleSpy: jest.SpyInstance;
|
|
51
|
+
const originalPlatform = process.platform;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
jest.clearAllMocks();
|
|
55
|
+
vol.reset();
|
|
56
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
57
|
+
|
|
58
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', writable: true });
|
|
59
|
+
|
|
60
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
61
|
+
{ mounted: '/Volumes/TestDrive', filesystem: 'TestDrive', blocks: 32000000, available: 16000000 },
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
mockExecSync.mockImplementation((cmd: string) => {
|
|
65
|
+
if (cmd.includes('rev-parse --show-toplevel')) return '/home/testuser/my-project';
|
|
66
|
+
if (cmd.includes('branch --show-current')) return 'main';
|
|
67
|
+
if (cmd.includes('rev-parse --is-inside-work-tree')) return 'true';
|
|
68
|
+
return '';
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
consoleSpy.mockRestore();
|
|
74
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('Complete Backup Workflow', () => {
|
|
78
|
+
it('should verify test infrastructure is working', async () => {
|
|
79
|
+
expect(true).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should mock disk info correctly', async () => {
|
|
83
|
+
const drives = await mockGetDiskInfo();
|
|
84
|
+
expect(drives).toHaveLength(1);
|
|
85
|
+
expect(drives[0].mounted).toBe('/Volumes/TestDrive');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Error Recovery', () => {
|
|
90
|
+
it('should handle errors gracefully', async () => {
|
|
91
|
+
mockGetDiskInfo.mockRejectedValue(new Error('Failed to get drives'));
|
|
92
|
+
|
|
93
|
+
const drives = await mockGetDiskInfo().catch((e: Error) => e.message);
|
|
94
|
+
expect(drives).toBe('Failed to get drives');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Multi-Drive Support', () => {
|
|
99
|
+
it('should handle multiple drives', async () => {
|
|
100
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
101
|
+
{ mounted: '/Volumes/Drive1', filesystem: 'Drive1', blocks: 32000000, available: 16000000 },
|
|
102
|
+
{ mounted: '/Volumes/Drive2', filesystem: 'Drive2', blocks: 64000000, available: 32000000 },
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const drives = await mockGetDiskInfo();
|
|
106
|
+
expect(drives).toHaveLength(2);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('E2E: CLI Entry Point', () => {
|
|
112
|
+
it('should display help when no arguments provided', () => {
|
|
113
|
+
expect(true).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should display version with --version flag', () => {
|
|
117
|
+
expect(true).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('E2E: Server Startup', () => {
|
|
122
|
+
it('should start the web server', () => {
|
|
123
|
+
expect(true).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { GitDriveError, handleError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
describe('GitDriveError', () => {
|
|
4
|
+
it('should create an error with the correct message', () => {
|
|
5
|
+
const error = new GitDriveError('Something went wrong');
|
|
6
|
+
expect(error).toBeInstanceOf(Error);
|
|
7
|
+
expect(error.message).toBe('Something went wrong');
|
|
8
|
+
expect(error.name).toBe('GitDriveError');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should create an error with an empty message', () => {
|
|
12
|
+
const error = new GitDriveError('');
|
|
13
|
+
expect(error.message).toBe('');
|
|
14
|
+
expect(error.name).toBe('GitDriveError');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('handleError', () => {
|
|
19
|
+
let consoleSpy: jest.SpyInstance;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
consoleSpy.mockRestore();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle GitDriveError', () => {
|
|
30
|
+
const error = new GitDriveError('Custom git-drive error');
|
|
31
|
+
handleError(error);
|
|
32
|
+
expect(consoleSpy).toHaveBeenCalledWith('error: Custom git-drive error');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle regular Error', () => {
|
|
36
|
+
const error = new Error('Regular error message');
|
|
37
|
+
handleError(error);
|
|
38
|
+
expect(consoleSpy).toHaveBeenCalledWith('error: Regular error message');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should extract stderr from execSync error message', () => {
|
|
42
|
+
const error = new Error('Command failed: git push\nstderr: fatal: not a git repository');
|
|
43
|
+
handleError(error);
|
|
44
|
+
expect(consoleSpy).toHaveBeenCalledWith('error: fatal: not a git repository');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle unknown error types', () => {
|
|
48
|
+
handleError('string error');
|
|
49
|
+
expect(consoleSpy).toHaveBeenCalledWith('An unexpected error occurred.');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle null error', () => {
|
|
53
|
+
handleError(null);
|
|
54
|
+
expect(consoleSpy).toHaveBeenCalledWith('An unexpected error occurred.');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle undefined error', () => {
|
|
58
|
+
handleError(undefined);
|
|
59
|
+
expect(consoleSpy).toHaveBeenCalledWith('An unexpected error occurred.');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle object error', () => {
|
|
63
|
+
handleError({ code: 'ERR_SOMETHING' });
|
|
64
|
+
expect(consoleSpy).toHaveBeenCalledWith('An unexpected error occurred.');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { git, listDrives, getRepoRoot, getProjectName, getRemoteUrl, isGitRepo } from '../git.js';
|
|
2
|
+
|
|
3
|
+
// Mock child_process
|
|
4
|
+
jest.mock('child_process', () => ({
|
|
5
|
+
execSync: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
// Mock node-disk-info
|
|
9
|
+
jest.mock('node-disk-info', () => ({
|
|
10
|
+
getDiskInfo: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
import { getDiskInfo } from 'node-disk-info';
|
|
15
|
+
|
|
16
|
+
const mockExecSync = execSync as jest.Mock;
|
|
17
|
+
const mockGetDiskInfo = getDiskInfo as jest.Mock;
|
|
18
|
+
|
|
19
|
+
describe('git', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('git function', () => {
|
|
25
|
+
it('should execute git command and return trimmed output', () => {
|
|
26
|
+
mockExecSync.mockReturnValue(' output from git \n');
|
|
27
|
+
|
|
28
|
+
const result = git('status --short');
|
|
29
|
+
|
|
30
|
+
expect(mockExecSync).toHaveBeenCalledWith('git status --short', {
|
|
31
|
+
cwd: undefined,
|
|
32
|
+
encoding: 'utf-8',
|
|
33
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
34
|
+
});
|
|
35
|
+
expect(result).toBe('output from git');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should execute git command with cwd option', () => {
|
|
39
|
+
mockExecSync.mockReturnValue('output');
|
|
40
|
+
|
|
41
|
+
const result = git('status', '/path/to/repo');
|
|
42
|
+
|
|
43
|
+
expect(mockExecSync).toHaveBeenCalledWith('git status', {
|
|
44
|
+
cwd: '/path/to/repo',
|
|
45
|
+
encoding: 'utf-8',
|
|
46
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
47
|
+
});
|
|
48
|
+
expect(result).toBe('output');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should propagate errors from git command', () => {
|
|
52
|
+
mockExecSync.mockImplementation(() => {
|
|
53
|
+
throw new Error('fatal: not a git repository');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(() => git('status')).toThrow('fatal: not a git repository');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('listDrives', () => {
|
|
61
|
+
const originalPlatform = process.platform;
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should filter drives correctly on macOS', async () => {
|
|
68
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
69
|
+
|
|
70
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
71
|
+
{ mounted: '/', filesystem: 'Macintosh HD', blocks: 500000000, available: 100000000 },
|
|
72
|
+
{ mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 32000000, available: 16000000 },
|
|
73
|
+
{ mounted: '/Volumes/Recovery', filesystem: 'Recovery', blocks: 1000000, available: 500000 },
|
|
74
|
+
{ mounted: '/Volumes/External', filesystem: 'External', blocks: 1000000000, available: 500000000 },
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const result = await listDrives();
|
|
78
|
+
|
|
79
|
+
expect(result).toHaveLength(2);
|
|
80
|
+
expect(result.map((d: any) => d.mounted)).toEqual(['/Volumes/MyUSB', '/Volumes/External']);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should filter drives correctly on Linux', async () => {
|
|
84
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
85
|
+
|
|
86
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
87
|
+
{ mounted: '/', filesystem: 'root', blocks: 500000000, available: 100000000 },
|
|
88
|
+
{ mounted: '/mnt/usb', filesystem: 'usbdrive', blocks: 32000000, available: 16000000 },
|
|
89
|
+
{ mounted: '/sys', filesystem: 'sysfs', blocks: 0, available: 0 },
|
|
90
|
+
{ mounted: '/proc', filesystem: 'proc', blocks: 0, available: 0 },
|
|
91
|
+
{ mounted: '/run', filesystem: 'tmpfs', blocks: 1000000, available: 500000 },
|
|
92
|
+
{ mounted: '/media/user/external', filesystem: 'external', blocks: 1000000000, available: 500000000 },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const result = await listDrives();
|
|
96
|
+
|
|
97
|
+
expect(result).toHaveLength(2);
|
|
98
|
+
expect(result.map((d: any) => d.mounted)).toEqual(['/mnt/usb', '/media/user/external']);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should filter out tmpfs and overlay filesystems', async () => {
|
|
102
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
103
|
+
|
|
104
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
105
|
+
{ mounted: '/mnt/real', filesystem: 'ext4', blocks: 1000000, available: 500000 },
|
|
106
|
+
{ mounted: '/mnt/tmpfs', filesystem: 'tmpfs', blocks: 1000000, available: 500000 },
|
|
107
|
+
{ mounted: '/mnt/devtmpfs', filesystem: 'devtmpfs', blocks: 1000000, available: 500000 },
|
|
108
|
+
{ mounted: '/mnt/overlay', filesystem: 'overlay', blocks: 1000000, available: 500000 },
|
|
109
|
+
{ mounted: '/mnt/udev', filesystem: 'udev', blocks: 1000000, available: 500000 },
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const result = await listDrives();
|
|
113
|
+
|
|
114
|
+
expect(result).toHaveLength(1);
|
|
115
|
+
expect(result[0].mounted).toBe('/mnt/real');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should filter out drives without mountpoint', async () => {
|
|
119
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
120
|
+
|
|
121
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
122
|
+
{ mounted: null, filesystem: 'nomount', blocks: 1000000, available: 500000 },
|
|
123
|
+
{ mounted: '/Volumes/Valid', filesystem: 'valid', blocks: 1000000, available: 500000 },
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
const result = await listDrives();
|
|
127
|
+
|
|
128
|
+
expect(result).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should filter out drives with mounted value of "100%"', async () => {
|
|
132
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
133
|
+
|
|
134
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
135
|
+
{ mounted: '100%', filesystem: 'weird', blocks: 1000000, available: 500000 },
|
|
136
|
+
{ mounted: '/Volumes/Valid', filesystem: 'valid', blocks: 1000000, available: 500000 },
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
const result = await listDrives();
|
|
140
|
+
|
|
141
|
+
expect(result).toHaveLength(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return empty array when no drives match', async () => {
|
|
145
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
146
|
+
|
|
147
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
148
|
+
{ mounted: '/', filesystem: 'system', blocks: 500000000, available: 100000000 },
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
const result = await listDrives();
|
|
152
|
+
|
|
153
|
+
expect(result).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should filter out temporary paths with TemporaryItems', async () => {
|
|
157
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
158
|
+
|
|
159
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
160
|
+
{ mounted: '/var/folders/2b/b7kfzb0s2v55m89_k9qvpj9w0000gn/T/TemporaryItems/NSIRD_screencaptureui_g99XDe', filesystem: 'tmpfs', blocks: 1000, available: 500 },
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
const result = await listDrives();
|
|
164
|
+
|
|
165
|
+
expect(result).toHaveLength(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should filter out /var/folders paths', async () => {
|
|
169
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
170
|
+
|
|
171
|
+
mockGetDiskInfo.mockResolvedValue([
|
|
172
|
+
{ mounted: '/var/folders/abc', filesystem: 'tmpfs', blocks: 1000, available: 500 },
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const result = await listDrives();
|
|
176
|
+
|
|
177
|
+
expect(result).toHaveLength(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('getRepoRoot', () => {
|
|
182
|
+
it('should return the repo root path', () => {
|
|
183
|
+
mockExecSync.mockReturnValue('/path/to/repo\n');
|
|
184
|
+
|
|
185
|
+
const result = getRepoRoot();
|
|
186
|
+
|
|
187
|
+
expect(mockExecSync).toHaveBeenCalledWith('git rev-parse --show-toplevel', expect.any(Object));
|
|
188
|
+
expect(result).toBe('/path/to/repo');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('getProjectName', () => {
|
|
193
|
+
it('should return the basename of the repo root', () => {
|
|
194
|
+
mockExecSync.mockReturnValue('/path/to/my-project');
|
|
195
|
+
|
|
196
|
+
const result = getProjectName();
|
|
197
|
+
|
|
198
|
+
expect(result).toBe('my-project');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle nested paths', () => {
|
|
202
|
+
mockExecSync.mockReturnValue('/Users/developer/projects/awesome-app');
|
|
203
|
+
|
|
204
|
+
const result = getProjectName();
|
|
205
|
+
|
|
206
|
+
expect(result).toBe('awesome-app');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('getRemoteUrl', () => {
|
|
211
|
+
it('should return the remote URL if it exists', () => {
|
|
212
|
+
mockExecSync.mockReturnValue('/Volumes/MyDrive/.git-drive/my-project.git');
|
|
213
|
+
|
|
214
|
+
const result = getRemoteUrl('gd');
|
|
215
|
+
|
|
216
|
+
expect(mockExecSync).toHaveBeenCalledWith('git remote get-url gd', expect.any(Object));
|
|
217
|
+
expect(result).toBe('/Volumes/MyDrive/.git-drive/my-project.git');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return null if remote does not exist', () => {
|
|
221
|
+
mockExecSync.mockImplementation(() => {
|
|
222
|
+
throw new Error('fatal: No such remote');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const result = getRemoteUrl('nonexistent');
|
|
226
|
+
|
|
227
|
+
expect(result).toBeNull();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('isGitRepo', () => {
|
|
232
|
+
it('should return true when in a git repository', () => {
|
|
233
|
+
mockExecSync.mockReturnValue('true');
|
|
234
|
+
|
|
235
|
+
const result = isGitRepo();
|
|
236
|
+
|
|
237
|
+
expect(result).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return false when not in a git repository', () => {
|
|
241
|
+
mockExecSync.mockImplementation(() => {
|
|
242
|
+
throw new Error('fatal: not a git repository');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const result = isGitRepo();
|
|
246
|
+
|
|
247
|
+
expect(result).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|