proxy_server.spec.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. const mockAgent = {};
  2. const mockOn = jest.fn();
  3. jest.mock('http', () => ({
  4. request: jest.fn(() => ({
  5. on: mockOn,
  6. })),
  7. Server: jest.fn(function Server() {
  8. this.addListener = jest.fn();
  9. }),
  10. }));
  11. jest.mock('socks', () => ({
  12. createConnection: jest.fn(),
  13. Agent: jest.fn(() => mockAgent),
  14. }));
  15. function last(array) {
  16. return array[array.length - 1];
  17. }
  18. function getLastMockOn(event) {
  19. return last(mockOn.mock.calls.filter(args => args[0] === event));
  20. }
  21. const http = require('http');
  22. const Socks = require('socks');
  23. const {
  24. createServer,
  25. getProxyObject,
  26. parseProxyLine,
  27. requestListener,
  28. connectListener,
  29. } = require('../proxy_server');
  30. describe('proxy_server', () => {
  31. const requestURL = 'https://google.com';
  32. let getProxyInfo;
  33. let request;
  34. let socksRequest;
  35. let response;
  36. let socketRequest;
  37. let socket;
  38. beforeEach(() => {
  39. getProxyInfo = jest.fn(() => ({
  40. ipaddress: '127.0.0.1',
  41. port: 8080,
  42. type: 5,
  43. authentication: { username: '', password: '' },
  44. }));
  45. request = {
  46. on: jest.fn(),
  47. pipe: jest.fn(),
  48. url: requestURL,
  49. };
  50. socksRequest = {
  51. on: jest.fn(),
  52. pipe: jest.fn(),
  53. url: requestURL.slice('https://'.length),
  54. };
  55. response = {
  56. on: jest.fn(),
  57. writeHead: jest.fn(),
  58. end: jest.fn(),
  59. };
  60. socketRequest = {
  61. on: jest.fn(),
  62. write: jest.fn(),
  63. pipe: jest.fn(),
  64. };
  65. socket = {
  66. on: jest.fn(),
  67. pipe: jest.fn(),
  68. write: jest.fn(),
  69. resume: jest.fn(),
  70. };
  71. });
  72. describe('getProxyObject', () => {
  73. it('should return a object with "ipaddress", "port", "type", "authentication" properties', () => {
  74. const host = '127.0.0.1';
  75. const port = '8080';
  76. const res = getProxyObject(host, port);
  77. expect(typeof res).toBe('object');
  78. expect(res.ipaddress).toBe(host);
  79. expect(res.port).toBe(parseInt(port, 10));
  80. expect(res.type).toBe(5);
  81. expect(typeof res.authentication).toBe('object');
  82. expect(Object.hasOwnProperty.apply(res.authentication, ['username'])).toBeTruthy();
  83. expect(Object.hasOwnProperty.apply(res.authentication, ['password'])).toBeTruthy();
  84. });
  85. });
  86. describe('parseProxyLine', () => {
  87. it('should return a object with "host" and "port" extracted from proxy string', () => {
  88. const proxyLine = '127.0.0.1:1080';
  89. const res = parseProxyLine(proxyLine);
  90. expect(typeof res).toBe('object');
  91. expect(res.ipaddress).toBe('127.0.0.1');
  92. expect(res.port).toBe(1080);
  93. });
  94. it('should also contain "username" and "password" properties when it contains these info', () => {
  95. const proxyLine = '127.0.0.1:1080:oyyd:password';
  96. const res = parseProxyLine(proxyLine);
  97. expect(typeof res).toBe('object');
  98. expect(res.ipaddress).toBe('127.0.0.1');
  99. expect(res.port).toBe(1080);
  100. expect(typeof res.authentication).toBe('object');
  101. expect(res.authentication.username).toBe('oyyd');
  102. expect(res.authentication.password).toBe('password');
  103. });
  104. it('should throw error when the proxy string seems not good', () => {
  105. let proxyLine = '127.0.0.1';
  106. let error = null;
  107. try {
  108. error = parseProxyLine(proxyLine);
  109. } catch (err) {
  110. error = err;
  111. }
  112. expect(error instanceof Error).toBeTruthy();
  113. proxyLine = '127.0.0.1:8080:oyyd';
  114. error = null;
  115. try {
  116. error = parseProxyLine(proxyLine);
  117. } catch (err) {
  118. error = err;
  119. }
  120. expect(error instanceof Error).toBeTruthy();
  121. });
  122. });
  123. describe('requestListener', () => {
  124. it('should create an socks agent and take it as request agent', () => {
  125. requestListener(getProxyInfo, request, response);
  126. const lastCall = last(Socks.Agent.mock.calls);
  127. const httpLastCall = last(http.request.mock.calls);
  128. expect(requestURL.indexOf(lastCall[0].target.host) > -1).toBeTruthy();
  129. expect(httpLastCall[0].agent === mockAgent).toBeTruthy();
  130. });
  131. it('should return 500 when error thrown', () => {
  132. requestListener(getProxyInfo, request, response);
  133. const onErrorArgs = getLastMockOn('error');
  134. expect(onErrorArgs).toBeTruthy();
  135. const error = new Error('500');
  136. onErrorArgs[1](error);
  137. expect(response.writeHead.mock.calls[0][0]).toBe(500);
  138. expect(response.end.mock.calls[0][0].indexOf('error') > -1).toBeTruthy();
  139. });
  140. it('should pipe response when "response"', () => {
  141. const proxyResponse = {
  142. statusCode: 200,
  143. headers: {},
  144. pipe: jest.fn(),
  145. };
  146. requestListener(getProxyInfo, request, response);
  147. const onResponseArgs = getLastMockOn('response');
  148. expect(onResponseArgs).toBeTruthy();
  149. onResponseArgs[1](proxyResponse);
  150. expect(proxyResponse.pipe.mock.calls[0][0]).toBe(response);
  151. });
  152. });
  153. describe('connectListener', () => {
  154. it('should create socks connections', () => {
  155. const head = '';
  156. connectListener(getProxyInfo, socksRequest, socketRequest, head);
  157. const lastCreateConnectionCall = last(Socks.createConnection.mock.calls);
  158. expect(lastCreateConnectionCall[0].target.host).toBe('google.com');
  159. });
  160. it('should write 500 when error thrown', () => {
  161. const head = '';
  162. connectListener(getProxyInfo, socksRequest, socketRequest, head);
  163. const lastCreateConnectionCall = last(Socks.createConnection.mock.calls);
  164. const error = new Error('500');
  165. lastCreateConnectionCall[1](error, socket);
  166. expect(socketRequest.write.mock.calls[0][0].indexOf('500') > -1).toBeTruthy();
  167. expect(socket.pipe.mock.calls.length === 0).toBeTruthy();
  168. });
  169. it('should pipe sockets when socket connected', () => {
  170. const head = '';
  171. connectListener(getProxyInfo, socksRequest, socketRequest, head);
  172. const lastCreateConnectionCall = last(Socks.createConnection.mock.calls);
  173. lastCreateConnectionCall[1](null, socket);
  174. expect(socketRequest.pipe.mock.calls[0][0]).toBe(socket);
  175. expect(socket.pipe.mock.calls[0][0]).toBe(socketRequest);
  176. expect(socketRequest.write.mock.calls[0][0].indexOf('200') > -1).toBeTruthy();
  177. expect(socket.write.mock.calls[0][0]).toBe(head);
  178. });
  179. });
  180. describe('createServer', () => {
  181. it('should push this.proxyList', () => {
  182. const options = {
  183. socks: '127.0.0.1:1080',
  184. };
  185. createServer(options);
  186. const { proxyList } = http.Server.mock.instances[0];
  187. expect(proxyList[0].ipaddress).toBe('127.0.0.1');
  188. expect(proxyList[0].port).toBe(1080);
  189. });
  190. it('should listen both "request" and "connect" events', () => {
  191. const options = {
  192. proxy: '127.0.0.1:1080',
  193. };
  194. createServer(options);
  195. const { addListener } = http.Server.mock.instances[0];
  196. const onRequestArgs = addListener.mock.calls.filter(args => args[0] === 'request');
  197. const onConnectArgs = addListener.mock.calls.filter(args => args[0] === 'connect');
  198. expect(onRequestArgs.length > 0).toBeTruthy();
  199. expect(onConnectArgs.length > 0).toBeTruthy();
  200. });
  201. });
  202. });