Skip to content

Commit 4230bc5

Browse files
committed
refactor(test): convert remaining test files from JavaScript to TypeScript
Converted pullRemote, performance, and SSH integration tests to TypeScript for better type safety and consistency with the codebase migration.
1 parent 7662e6a commit 4230bc5

File tree

8 files changed

+841
-3021
lines changed

8 files changed

+841
-3021
lines changed

src/proxy/ssh/hostKeyManager.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function ensureHostKey(config: HostKeyConfig): Buffer {
3737

3838
// Validate paths to prevent command injection
3939
// Only allow alphanumeric, dots, slashes, underscores, hyphens
40-
const safePathRegex = /^[a-zA-Z0-9._\-\/]+$/;
40+
const safePathRegex = /^[a-zA-Z0-9._\-/]+$/;
4141
if (!safePathRegex.test(privateKeyPath) || !safePathRegex.test(publicKeyPath)) {
4242
throw new Error(
4343
`Invalid SSH host key path: paths must contain only alphanumeric characters, dots, slashes, underscores, and hyphens`,
@@ -59,7 +59,9 @@ export function ensureHostKey(config: HostKeyConfig): Buffer {
5959
// Generate a new host key
6060
console.log(`[SSH] Proxy host key not found at ${privateKeyPath}`);
6161
console.log('[SSH] Generating new SSH host key for the proxy server...');
62-
console.log('[SSH] Note: This key identifies the proxy to connecting clients (like an SSL certificate)');
62+
console.log(
63+
'[SSH] Note: This key identifies the proxy to connecting clients (like an SSL certificate)',
64+
);
6365

6466
try {
6567
// Create directory if it doesn't exist
@@ -75,13 +77,10 @@ export function ensureHostKey(config: HostKeyConfig): Buffer {
7577
// - Faster key generation
7678
// - Better security properties
7779
console.log('[SSH] Generating Ed25519 host key...');
78-
execSync(
79-
`ssh-keygen -t ed25519 -f "${privateKeyPath}" -N "" -C "git-proxy-host-key"`,
80-
{
81-
stdio: 'pipe', // Suppress ssh-keygen output
82-
timeout: 10000, // 10 second timeout
83-
},
84-
);
80+
execSync(`ssh-keygen -t ed25519 -f "${privateKeyPath}" -N "" -C "git-proxy-host-key"`, {
81+
stdio: 'pipe', // Suppress ssh-keygen output
82+
timeout: 10000, // 10 second timeout
83+
});
8584

8685
console.log(`[SSH] ✓ Successfully generated proxy host key`);
8786
console.log(`[SSH] Private key: ${privateKeyPath}`);
@@ -99,23 +98,20 @@ export function ensureHostKey(config: HostKeyConfig): Buffer {
9998
return fs.readFileSync(privateKeyPath);
10099
} catch (error) {
101100
// If generation fails, provide helpful error message
102-
const errorMessage =
103-
error instanceof Error
104-
? error.message
105-
: String(error);
101+
const errorMessage = error instanceof Error ? error.message : String(error);
106102

107103
console.error('[SSH] Failed to generate host key');
108104
console.error(`[SSH] Error: ${errorMessage}`);
109105
console.error('[SSH]');
110106
console.error('[SSH] To fix this, you can either:');
111107
console.error('[SSH] 1. Install ssh-keygen (usually part of OpenSSH)');
112108
console.error('[SSH] 2. Manually generate a key:');
113-
console.error(`[SSH] ssh-keygen -t ed25519 -f "${privateKeyPath}" -N "" -C "git-proxy-host-key"`);
109+
console.error(
110+
`[SSH] ssh-keygen -t ed25519 -f "${privateKeyPath}" -N "" -C "git-proxy-host-key"`,
111+
);
114112
console.error('[SSH] 3. Disable SSH in proxy.config.json: "ssh": { "enabled": false }');
115113

116-
throw new Error(
117-
`Failed to generate SSH host key: ${errorMessage}. See console for details.`,
118-
);
114+
throw new Error(`Failed to generate SSH host key: ${errorMessage}. See console for details.`);
119115
}
120116
}
121117

test/processors/pullRemote.test.js

Lines changed: 0 additions & 103 deletions
This file was deleted.

test/processors/pullRemote.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
2+
import { Action } from '../../src/proxy/actions/Action';
3+
4+
// Mock modules
5+
vi.mock('fs');
6+
vi.mock('isomorphic-git');
7+
vi.mock('simple-git');
8+
vi.mock('isomorphic-git/http/node', () => ({}));
9+
10+
describe('pullRemote processor', () => {
11+
let fsStub: any;
12+
let gitCloneStub: any;
13+
let simpleGitStub: any;
14+
let pullRemote: any;
15+
16+
const setupModule = async () => {
17+
gitCloneStub = vi.fn().mockResolvedValue(undefined);
18+
simpleGitStub = vi.fn().mockReturnValue({
19+
clone: vi.fn().mockResolvedValue(undefined),
20+
});
21+
22+
// Mock the dependencies
23+
vi.doMock('fs', () => ({
24+
promises: fsStub.promises,
25+
}));
26+
vi.doMock('isomorphic-git', () => ({
27+
clone: gitCloneStub,
28+
}));
29+
vi.doMock('simple-git', () => ({
30+
simpleGit: simpleGitStub,
31+
}));
32+
33+
// Import after mocking
34+
const module = await import('../../src/proxy/processors/push-action/pullRemote');
35+
pullRemote = module.exec;
36+
};
37+
38+
beforeEach(async () => {
39+
fsStub = {
40+
promises: {
41+
mkdtemp: vi.fn(),
42+
writeFile: vi.fn(),
43+
rm: vi.fn(),
44+
rmdir: vi.fn(),
45+
mkdir: vi.fn(),
46+
},
47+
};
48+
await setupModule();
49+
});
50+
51+
afterEach(() => {
52+
vi.restoreAllMocks();
53+
});
54+
55+
it('uses service token when cloning SSH repository', async () => {
56+
const action = new Action(
57+
'123',
58+
'push',
59+
'POST',
60+
Date.now(),
61+
'https://github.com/example/repo.git',
62+
);
63+
action.protocol = 'ssh';
64+
action.sshUser = {
65+
username: 'ssh-user',
66+
sshKeyInfo: {
67+
keyType: 'ssh-rsa',
68+
keyData: Buffer.from('public-key'),
69+
},
70+
};
71+
72+
const req = {
73+
headers: {},
74+
authContext: {
75+
cloneServiceToken: {
76+
username: 'svc-user',
77+
password: 'svc-token',
78+
},
79+
},
80+
};
81+
82+
await pullRemote(req, action);
83+
84+
expect(gitCloneStub).toHaveBeenCalledOnce();
85+
const cloneOptions = gitCloneStub.mock.calls[0][0];
86+
expect(cloneOptions.url).toBe(action.url);
87+
expect(cloneOptions.onAuth()).toEqual({
88+
username: 'svc-user',
89+
password: 'svc-token',
90+
});
91+
expect(action.pullAuthStrategy).toBe('ssh-service-token');
92+
});
93+
94+
it('throws descriptive error when HTTPS authorization header is missing', async () => {
95+
const action = new Action(
96+
'456',
97+
'push',
98+
'POST',
99+
Date.now(),
100+
'https://github.com/example/repo.git',
101+
);
102+
action.protocol = 'https';
103+
104+
const req = {
105+
headers: {},
106+
};
107+
108+
try {
109+
await pullRemote(req, action);
110+
expect.fail('Expected pullRemote to throw');
111+
} catch (error: any) {
112+
expect(error.message).toBe('Missing Authorization header for HTTPS clone');
113+
}
114+
});
115+
});

0 commit comments

Comments
 (0)