Skip to content

Commit 04d912e

Browse files
committed
test: add tests for proxy
1 parent 1a0e295 commit 04d912e

6 files changed

Lines changed: 276 additions & 8 deletions

File tree

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
"proxyquire": "^2.1.3",
120120
"quicktype": "^23.2.6",
121121
"sinon": "^21.0.0",
122+
"sinon-chai": "^3.7.0",
122123
"ts-mocha": "^11.1.0",
123124
"ts-node": "^10.9.2",
124125
"tsx": "^4.19.3",

src/config/ConfigLoader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { execFile } from 'child_process';
55
import { promisify } from 'util';
66
import { EventEmitter } from 'events';
77
import envPaths from 'env-paths';
8-
import { GitProxyConfig, Convert } from './config';
8+
import { GitProxyConfig, Convert } from './generated/config';
99

1010
const execFileAsync = promisify(execFile);
1111

src/config/file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { readFileSync } from 'fs';
22
import { join } from 'path';
3-
import { Convert } from './config';
3+
import { Convert } from './generated/config';
44

55
export let configFile: string = join(process.cwd(), 'proxy.config.json');
66

src/config/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { existsSync, readFileSync } from 'fs';
22

33
import defaultSettings from '../../proxy.config.json';
4-
import { GitProxyConfig, Convert } from './config';
4+
import { GitProxyConfig, Convert } from './generated/config';
55
import { ConfigLoader, Configuration } from './ConfigLoader';
66
import { serverConfig } from './env';
77
import { configFile } from './file';
@@ -20,7 +20,7 @@ function cleanUndefinedValues(obj: any): any {
2020
if (obj === null || obj === undefined) return obj;
2121
if (typeof obj !== 'object') return obj;
2222
if (Array.isArray(obj)) return obj.map(cleanUndefinedValues);
23-
23+
2424
const cleaned: any = {};
2525
for (const [key, value] of Object.entries(obj)) {
2626
if (value !== undefined) {
@@ -40,7 +40,7 @@ function loadFullConfiguration(): GitProxyConfig {
4040
}
4141

4242
const rawDefaultConfig = Convert.toGitProxyConfig(JSON.stringify(defaultSettings));
43-
43+
4444
// Clean undefined values from defaultConfig
4545
const defaultConfig = cleanUndefinedValues(rawDefaultConfig);
4646

@@ -77,7 +77,7 @@ function mergeConfigurations(
7777
): GitProxyConfig {
7878
// Special handling for TLS configuration when legacy fields are used
7979
let tlsConfig = userSettings.tls || defaultConfig.tls;
80-
80+
8181
// If user doesn't specify tls but has legacy SSL fields, use only legacy fallback
8282
if (!userSettings.tls && (userSettings.sslKeyPemPath || userSettings.sslCertPemPath)) {
8383
tlsConfig = {
@@ -257,12 +257,12 @@ export const getPlugins = () => {
257257

258258
export const getTLSKeyPemPath = (): string | undefined => {
259259
const config = loadFullConfiguration();
260-
return (config.tls?.key && config.tls.key !== '') ? config.tls.key : config.sslKeyPemPath;
260+
return config.tls?.key && config.tls.key !== '' ? config.tls.key : config.sslKeyPemPath;
261261
};
262262

263263
export const getTLSCertPemPath = (): string | undefined => {
264264
const config = loadFullConfiguration();
265-
return (config.tls?.cert && config.tls.cert !== '') ? config.tls.cert : config.sslCertPemPath;
265+
return config.tls?.cert && config.tls.cert !== '' ? config.tls.cert : config.sslCertPemPath;
266266
};
267267

268268
export const getTLSEnabled = (): boolean => {

test/proxy.test.js

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
const chai = require('chai');
2+
const chaiHttp = require('chai-http');
3+
const sinon = require('sinon');
4+
const sinonChai = require('sinon-chai');
5+
const http = require('http');
6+
const https = require('https');
7+
const fs = require('fs');
8+
const express = require('express');
9+
const proxyModule = require('../src/proxy/index').default;
10+
11+
chai.use(chaiHttp);
12+
chai.use(sinonChai);
13+
chai.should();
14+
const { expect } = chai;
15+
16+
describe('Proxy Module', () => {
17+
let sandbox;
18+
let mockConfig;
19+
let mockPluginLoader;
20+
let mockDb;
21+
22+
beforeEach(() => {
23+
sandbox = sinon.createSandbox();
24+
25+
mockConfig = {
26+
getPlugins: sandbox.stub().returns([]),
27+
getAuthorisedList: sandbox.stub().returns([]),
28+
getTLSEnabled: sandbox.stub().returns(false),
29+
getTLSKeyPemPath: sandbox.stub().returns(null),
30+
getTLSCertPemPath: sandbox.stub().returns(null),
31+
};
32+
33+
mockDb = {
34+
getRepos: sandbox.stub().resolves([]),
35+
createRepo: sandbox.stub().resolves(),
36+
addUserCanPush: sandbox.stub().resolves(),
37+
addUserCanAuthorise: sandbox.stub().resolves(),
38+
};
39+
40+
mockPluginLoader = {
41+
load: sandbox.stub().resolves(),
42+
};
43+
44+
sandbox.stub(require('../src/plugin'), 'PluginLoader').returns(mockPluginLoader);
45+
46+
const configModule = require('../src/config');
47+
sandbox.stub(configModule, 'getPlugins').callsFake(mockConfig.getPlugins);
48+
sandbox.stub(configModule, 'getAuthorisedList').callsFake(mockConfig.getAuthorisedList);
49+
sandbox.stub(configModule, 'getTLSEnabled').callsFake(mockConfig.getTLSEnabled);
50+
sandbox.stub(configModule, 'getTLSKeyPemPath').callsFake(mockConfig.getTLSKeyPemPath);
51+
sandbox.stub(configModule, 'getTLSCertPemPath').callsFake(mockConfig.getTLSCertPemPath);
52+
53+
const dbModule = require('../src/db');
54+
sandbox.stub(dbModule, 'getRepos').callsFake(mockDb.getRepos);
55+
sandbox.stub(dbModule, 'createRepo').callsFake(mockDb.createRepo);
56+
sandbox.stub(dbModule, 'addUserCanPush').callsFake(mockDb.addUserCanPush);
57+
sandbox.stub(dbModule, 'addUserCanAuthorise').callsFake(mockDb.addUserCanAuthorise);
58+
59+
const chain = require('../src/proxy/chain');
60+
chain.chainPluginLoader = null;
61+
62+
process.env.NODE_ENV = 'test';
63+
process.env.GIT_PROXY_SERVER_PORT = '8080';
64+
process.env.GIT_PROXY_HTTPS_SERVER_PORT = '8443';
65+
});
66+
67+
afterEach(async () => {
68+
try {
69+
await proxyModule.stop();
70+
} catch (error) {
71+
// Ignore errors during cleanup
72+
}
73+
sandbox.restore();
74+
});
75+
76+
describe('proxyPreparations', () => {
77+
it('should load plugins successfully', async () => {
78+
mockConfig.getPlugins.returns([{ name: 'test-plugin' }]);
79+
80+
await proxyModule.proxyPreparations();
81+
82+
expect(mockPluginLoader.load).to.have.been.calledOnce;
83+
});
84+
85+
it('should setup default repositories', async () => {
86+
const defaultRepo = { project: 'test', name: 'repo' };
87+
mockConfig.getAuthorisedList.returns([defaultRepo]);
88+
mockDb.getRepos.resolves([]);
89+
90+
await proxyModule.proxyPreparations();
91+
92+
expect(mockDb.createRepo).to.have.been.calledWith(defaultRepo);
93+
});
94+
95+
it('should not create existing repositories', async () => {
96+
const existingRepo = { project: 'test', name: 'repo' };
97+
mockConfig.getAuthorisedList.returns([existingRepo]);
98+
mockDb.getRepos.resolves([existingRepo]);
99+
100+
await proxyModule.proxyPreparations();
101+
102+
expect(mockDb.createRepo).not.to.have.been.called;
103+
});
104+
105+
it('should handle plugin loading errors', async () => {
106+
mockPluginLoader.load.rejects(new Error('Plugin load failed'));
107+
108+
try {
109+
await proxyModule.proxyPreparations();
110+
expect.fail('Should have thrown an error');
111+
} catch (error) {
112+
expect(error.message).to.equal('Plugin load failed');
113+
}
114+
});
115+
});
116+
117+
describe('createApp', () => {
118+
it('should create an Express application', async () => {
119+
const app = await proxyModule.createApp();
120+
121+
expect(app).to.be.a('function');
122+
expect(app).to.have.property('use');
123+
expect(app).to.have.property('listen');
124+
});
125+
126+
it('should setup router', async () => {
127+
const mockUse = sandbox.spy();
128+
sandbox.stub(express, 'Router').returns(mockUse);
129+
130+
await proxyModule.createApp();
131+
});
132+
});
133+
134+
describe('start', () => {
135+
let httpCreateServerStub;
136+
let httpsCreateServerStub;
137+
let mockHttpServer;
138+
let mockHttpsServer;
139+
140+
beforeEach(() => {
141+
mockHttpServer = {
142+
listen: sandbox.stub().callsFake((port, callback) => {
143+
if (callback) callback();
144+
return mockHttpServer;
145+
}),
146+
close: sandbox.stub().callsFake((callback) => {
147+
if (callback) callback();
148+
}),
149+
};
150+
151+
mockHttpsServer = {
152+
listen: sandbox.stub().callsFake((port, callback) => {
153+
if (callback) callback();
154+
return mockHttpsServer;
155+
}),
156+
close: sandbox.stub().callsFake((callback) => {
157+
if (callback) callback();
158+
}),
159+
};
160+
161+
httpCreateServerStub = sandbox.stub(http, 'createServer').returns(mockHttpServer);
162+
httpsCreateServerStub = sandbox.stub(https, 'createServer').returns(mockHttpsServer);
163+
sandbox.stub(fs, 'readFileSync').returns(Buffer.from('mock-cert'));
164+
});
165+
166+
it('should start HTTP server', async () => {
167+
mockConfig.getTLSEnabled.returns(false);
168+
169+
const app = await proxyModule.start();
170+
171+
expect(app).to.be.a('function');
172+
expect(httpCreateServerStub).to.have.been.calledOnce;
173+
expect(mockHttpServer.listen).to.have.been.calledWith(8000);
174+
});
175+
176+
it('should start both HTTP and HTTPS servers when TLS enabled', async () => {
177+
mockConfig.getTLSEnabled.returns(true);
178+
mockConfig.getTLSKeyPemPath.returns('/path/to/key.pem');
179+
mockConfig.getTLSCertPemPath.returns('/path/to/cert.pem');
180+
181+
const app = await proxyModule.start();
182+
183+
expect(app).to.be.a('function');
184+
expect(httpCreateServerStub).to.have.been.calledOnce;
185+
expect(httpsCreateServerStub).to.have.been.calledOnce;
186+
expect(mockHttpServer.listen).to.have.been.calledWith(8000);
187+
expect(mockHttpsServer.listen).to.have.been.calledWith(8443);
188+
});
189+
190+
it('should call proxyPreparations', async () => {
191+
const app = await proxyModule.start();
192+
193+
expect(app).to.be.a('function');
194+
});
195+
});
196+
197+
describe('stop', () => {
198+
let mockHttpServer;
199+
let mockHttpsServer;
200+
201+
beforeEach(() => {
202+
mockHttpServer = {
203+
listen: sandbox.stub().callsFake((port, callback) => {
204+
if (callback) callback();
205+
return mockHttpServer;
206+
}),
207+
close: sandbox.stub().callsFake((callback) => {
208+
if (callback) callback();
209+
}),
210+
};
211+
212+
mockHttpsServer = {
213+
listen: sandbox.stub().callsFake((port, callback) => {
214+
if (callback) callback();
215+
return mockHttpsServer;
216+
}),
217+
close: sandbox.stub().callsFake((callback) => {
218+
if (callback) callback();
219+
}),
220+
};
221+
222+
sandbox.stub(http, 'createServer').returns(mockHttpServer);
223+
sandbox.stub(https, 'createServer').returns(mockHttpsServer);
224+
sandbox.stub(fs, 'readFileSync').returns(Buffer.from('mock-cert'));
225+
});
226+
227+
it('should stop servers gracefully', async () => {
228+
mockConfig.getTLSEnabled.returns(true);
229+
mockConfig.getTLSKeyPemPath.returns('/path/to/key.pem');
230+
mockConfig.getTLSCertPemPath.returns('/path/to/cert.pem');
231+
232+
await proxyModule.start();
233+
234+
await proxyModule.stop();
235+
236+
expect(mockHttpServer.close).to.have.been.calledOnce;
237+
expect(mockHttpsServer.close).to.have.been.calledOnce;
238+
});
239+
240+
it('should handle server close errors', async () => {
241+
mockHttpServer.close.callsFake((callback) => {
242+
throw new Error('Close error');
243+
});
244+
245+
await proxyModule.start();
246+
247+
try {
248+
await proxyModule.stop();
249+
expect.fail('Should have thrown an error');
250+
} catch (error) {
251+
expect(error.message).to.equal('Close error');
252+
}
253+
});
254+
});
255+
});

0 commit comments

Comments
 (0)