UNPKG

@luminati-io/luminati-proxy

Version:

A configurable local proxy for luminati.io

908 lines (906 loc) 42.6 kB
// LICENSE_CODE ZON ISC 'use strict'; /*jslint node:true, mocha:true*/ const _ = require('lodash'); const nock = require('nock'); const fs = require('fs'); const os = require('os'); const path = require('path'); const assert = require('assert'); const request = require('request'); const sinon = require('sinon'); const winston = require('winston'); // needed to make lpm_file.js to set work dir to tmp dir process.argv.push('--dir', os.tmpdir()); const Manager = require('../lib/manager.js'); const cities = require('../lib/cities'); sinon.stub(cities, 'ensure_data', ()=>null); const logger = require('../lib/logger.js'); const etask = require('../util/etask.js'); const pkg = require('../package.json'); const qw = require('../util/string.js').qw; const user_agent = require('../util/user_agent.js'); const lpm_util = require('../util/lpm_util.js'); const util_lib = require('../lib/util.js'); const lpm_config = require('../util/lpm_config.js'); const {stub: sstub, match: smatch} = sinon; const customer = 'abc'; const password = 'xyz'; const {assert_has} = require('./common.js'); const api_base = 'https://'+pkg.api_domain; describe('manager', ()=>{ let app, temp_files, logger_stub; const stub_logger = ()=>{ logger.transports.forEach(t=>{ t.silent = true; }); logger_stub = sinon.stub(logger, 'notice'); }; const unstub_logger = ()=>{ logger_stub.restore(); logger.transports.forEach(t=>{ t.silent = false; }); }; const get_param = (args, param)=>{ let i = args.indexOf(param)+1; return i ? args[i] : null; }; const app_with_args = (args, only_explicit)=>etask(function*(){ let manager; this.finally(()=>{ if (this.error && manager) return manager.stop(true); }); args = args||[]; if (!only_explicit) { if (!get_param(args, '--proxy')) args = args.concat(['--proxy', '127.0.0.1']); if (!get_param(args, '--proxy_port')) args = args.concat(['--proxy_port', 24000]); if (!get_param(args, '--config')&&!get_param(args, '--no-config')) args.push('--no-config'); if (!get_param(args, '--customer')) args = args.concat(['--customer', customer]); if (!get_param(args, '--password')) args = args.concat(['--password', password]); if (!get_param(args, '--dropin')&&!get_param(args, '--no-dropin')) args = args.concat(['--no-dropin']); if (!get_param(args, '--cookie')&&!get_param(args, '--no-cookie')) args.push('--no-cookie'); if (!get_param(args, '--local_login') && !get_param(args, '--no-local_login')) { args = args.concat(['--no-local_login']); } args = args.concat('--loki', '/tmp/testdb'); } Manager.prototype.set_current_country = ()=>null; Manager.prototype.check_conn = ()=>null; Manager.prototype.lpm_users_get = ()=>null; Manager.prototype.init_lpm_f_ws = ()=>null; Manager.prototype.get_lum_local_conf = function(){ this.lum_conf = {}; return {}; }; manager = new Manager(lpm_util.init_args(args)); yield manager.start(); return {manager}; }); let tmp_file_counter = 0; const temp_file_path = (ext='tmp')=>{ const p = path.join(os.tmpdir(), `test-${Date.now()}-${tmp_file_counter++}.${ext}`); const done = ()=>{ if (this.path) { try { fs.unlinkSync(path); } catch(e){ console.error(e.message); } this.path = null; } }; return {path: p, done}; }; const temp_file = (content, ext)=>{ const temp = temp_file_path(ext); fs.writeFileSync(temp.path, JSON.stringify(content)); return temp; }; const app_with_config = opt=>etask(function*(){ const args = []; const cli = opt.cli||{}; Object.keys(cli).forEach(k=>{ if (typeof cli[k]=='boolean') { if (cli[k]) args.push('--'+k); else args.push('--no-'+k); return; } args.push('--'+k); if (Array.isArray(cli[k])) args.push(...cli[k]); else args.push(cli[k]); }); if (opt.config) { const config_file = temp_file(opt.config, 'json'); args.push('--config'); args.push(config_file.path); temp_files.push(config_file); } return yield app_with_args(args, opt.only_explicit); }); const app_with_proxies = (proxies, cli)=>etask(function*(){ return yield app_with_config({config: {proxies}, cli}); }); const api = (_path, method, data, json, headers)=>etask(function*(){ const admin = 'http://127.0.0.1:'+Manager.default.www; const opt = { url: admin+'/'+_path, method: method||'GET', json, body: data, headers: headers || {'x-lpm-fake': true}, }; return yield etask.nfn_apply(request, [opt]); }); const api_json = (_path, opt={})=>etask(function*(){ return yield api(_path, opt.method, opt.body, true, opt.headers); }); const json = (_path, method, data)=>etask(function*(){ const res = yield api(_path, method, data, true); assert.equal(res.statusCode, 200); return res.body; }); const make_user_req = (port=24000, status=200)=>{ return api_json('api/test/'+port, { method: 'POST', body: { url: 'http://lumtest.com/myip.json', headers: {'x-lpm-fake': true, 'x-lpm-fake-status': status}, }, }); }; before(stub_logger); afterEach('after manager', etask._fn(function*(_this){ nock.cleanAll(); if (!app) return; yield app.manager.stop(true); if (process.platform=='win32') yield etask.sleep(10); if (!app) return; app = null; })); beforeEach(()=>{ temp_files = []; }); afterEach('after manager 2', ()=>{ temp_files.forEach(f=>f.done()); }); describe('get_params', ()=>{ const t = (name, _args, expected)=>it(name, etask._fn(function(_this){ const mgr = new Manager(lpm_util.init_args(_args)); assert.deepEqual(expected, mgr.get_params()); })); const def = '--throttle 2'; t('default', qw(def), ['--throttle', 2]); t('credentials', qw`${def} --customer test_user --password abcdefgh`, ['--throttle', 2]); t('credentials with no-config', ['--no-config', '--customer', 'usr', '--google_token', 't', '--password', 'abc', '--zone', 'z'], ['--no-config', '--customer', 'usr', '--google_token', 't', '--password', 'abc', '--zone', 'z']); }); describe('config load', ()=>{ const t = (name, config, expected)=>it(name, etask._fn( function*(_this){ _this.timeout(6000); app = yield app_with_config(config); const proxies = yield json('api/proxies_running'); assert_has(proxies, expected, 'proxies'); for (let prop of ['hosts', 'mobile', 'ssl_perm', 'static']) { assert(prop in proxies[0]); assert(!(prop in proxies[0].config), 'calculated config values are leaked into raw config'); } })); const simple_proxy = {port: 24024}; t('cli only', {cli: simple_proxy, config: []}, [Object.assign({}, simple_proxy, {proxy_type: 'persist'})]); t('main config only', {config: {proxies: [simple_proxy]}}, [Object.assign({}, simple_proxy, {proxy_type: 'persist'})]); t('config file', {config: {proxies: [simple_proxy]}}, [simple_proxy]); describe('default zone', ()=>{ const zone_static = {password: ['pass1']}; const zone_gen = {password: ['pass2']}; const zones = {static: Object.assign({}, zone_static), gen: Object.assign({}, zone_gen)}; const t2 = (name, config, expected, _defaults={zone: 'static'})=>{ nock(api_base).get('/').reply(200, {}); nock(api_base).post('/update_lpm_stats').reply(200, {}); nock(api_base).get('/cp/lum_local_conf') .query({customer: 'testc1', proxy: pkg.version}) .reply(200, {_defaults}); t(name, _.set(config, 'cli.customer', 'testc1'), expected); }; t2('from defaults', { config: {_defaults: {zone: 'foo'}, proxies: [simple_proxy]}, }, [Object.assign({zone: 'foo'}, simple_proxy)], {zone: 'static', zones}); t2('keep default', { config: {_defaults: {zone: 'gen'}, proxies: [simple_proxy]}, }, [Object.assign({zone: 'gen'}, simple_proxy)]); t2('empty zone should be overriden by default', {config: { _defaults: {}, proxies: [Object.assign({zone: ''}, simple_proxy)], }}, [{zone: 'static'}]); }); describe('args as default params for proxy ports', ()=>{ it('should use proxy from args', etask._fn(function*(_this){ _this.timeout(6000); app = yield app_with_args(['--proxy', '1.2.3.4', '--proxy_port', '3939', '--dropin']); const dropin = app.manager.proxy_ports[22225]; assert.equal(dropin.opt.proxy, '1.2.3.4'); assert.equal(dropin.opt.proxy_port, 3939); })); }); }); describe('report_bug', ()=>{ // XXX krzysztof: get rid of this before(()=>{ unstub_logger(); const console_t = logger.transports.find( t=>t instanceof winston.transports.Console); console_t.silent = true; }); after(stub_logger); // can't rm current log file because transports rely on it beforeEach(()=>fs.truncateSync(logger.lpm_filename, 0)); it('should send logs, har and config', etask._fn(function*(_this){ _this.timeout(6000); app = yield app_with_config({config: {}, cli: {log: 'notice'}}); const desc = 'bug description', email = 'test@luminati.io'; const ua = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) '+ 'Gecko/20100101 Firefox/47.0'; const req = {body: {desc, email}, get: ()=>ua}; const res = { status: sinon.stub().returnsThis(), json: sinon.stub(), }; const request_stub = sinon.stub(app.manager, 'api_request'); request_stub.returns({statusCode: 200, body: 'ok'}); yield app.manager.report_bug_api(req, res); const report = request_stub.firstCall.args[0].form.report; assert.ok(report.config); assert.ok(report.log); assert.ok(report.har); assert.equal(report.desc, desc); assert.equal(report.email, email); assert.equal(report.browser, user_agent.guess_browser(ua).browser); assert.equal(report.os, util_lib.format_platform(os.platform())); sinon.assert.calledWith(res.status, 200); sinon.assert.calledWith(res.json, 'ok'); })); }); describe('dropin', function(){ this.timeout(6000); it('off', etask._fn(function*(_this){ app = yield app_with_args(['--no-dropin']); assert.ok(!app.manager.proxy_ports[22225]); })); it('on', etask._fn(function*(_this){ app = yield app_with_args(['--dropin']); assert.ok(!!app.manager.proxy_ports[22225]); })); }); describe('api', ()=>{ it('ssl', etask._fn(function*(_this){ app = yield app_with_args(); const res = yield api('ssl'); assert_has(res.headers, { 'content-type': 'application/x-x509-ca-cert', 'content-disposition': 'filename=luminati.crt', }, 'headers'); assert.equal(res.body, fs.readFileSync(path.join(__dirname, '../bin/ca.crt')), 'certificate'); })); describe('version info', ()=>{ it('current', ()=>etask(function*(){ app = yield app_with_args(); const body = yield json('api/version'); assert.equal(body.version, pkg.version); })); }); describe('proxies', ()=>{ describe('get', ()=>{ it('normal', etask._fn(function*(_this){ const proxies = [{port: 24023}, {port: 24024}]; app = yield app_with_proxies(proxies); let res = yield json('api/proxies'); assert_has(res, proxies, 'proxies'); res = yield json('api/proxies_running'); assert_has(res, proxies, 'proxies_running'); })); }); describe('post', ()=>{ it('normal persist', etask._fn(function*(_this){ let sample_proxy = {port: 24001}; const res_proxy = Object.assign({customer, password}, sample_proxy); app = yield app_with_proxies([], {}); let res = yield json('api/proxies', 'post', {proxy: sample_proxy}); assert_has(res, {data: res_proxy}, 'proxies'); res = yield json('api/proxies_running'); assert_has(res, [res_proxy], 'proxies'); res = yield json('api/proxies'); assert.equal(res.length, 1); })); it('conflict', etask._fn(function*(_this){ const sample_proxy = {port: 24000}; const proxies = [sample_proxy]; app = yield app_with_proxies(proxies, {}); const res = yield api_json('api/proxies', {method: 'post', body: {proxy: sample_proxy}}); assert.equal(res.statusCode, 400); assert_has(res.body, {errors: []}, 'proxies'); })); const t = (name, status_code)=>it(name, ()=>etask(function*(){ const sample_proxy = {port: 24000, ext_proxies: ['127.0.0.1:8888']}; nock(api_base).get('/').times(2).reply(200, {}); nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); nock(api_base).post('/update_lpm_stats').query(true) .reply(200, {}); nock(api_base).post('/ext_proxy_created').query(true) .reply(status_code, {}); app = yield app_with_config({cli: {google_token: '123'}}); let res = yield json('api/proxies', 'post', {proxy: sample_proxy}); assert_has(res, {data: sample_proxy}, 'proxies'); res = yield json('api/proxies_running'); assert_has(res, [sample_proxy], 'proxies'); })); t('external', 200); t('external, backend is down', 500); }); describe('put', ()=>{ it('normal', etask._fn(function*(_this){ const put_proxy = {port: 24001}; const proxies = [{port: 24000}]; app = yield app_with_proxies(proxies, {}); let res = yield json('api/proxies/24000', 'put', {proxy: put_proxy}); assert_has(res, {data: put_proxy}); res = yield json('api/proxies_running'); assert_has(res, [put_proxy], 'proxies'); })); it('inherit defaults', ()=>etask(function*(){ const put_proxy = {port: 24001}; const proxies = [{port: 24000}]; const res_proxy = Object.assign({}, {customer, password}, put_proxy); app = yield app_with_proxies(proxies, {}); let res = yield json('api/proxies/24000', 'put', {proxy: put_proxy}); assert_has(res, {data: res_proxy}); res = yield json('api/proxies_running'); assert_has(res, [res_proxy], 'proxies'); })); it('conflict', etask._fn(function*(_this){ let proxies = [{port: 24000}, {port: 24001}]; app = yield app_with_proxies(proxies, {}); let res = yield api_json('api/proxies/24001', {method: 'put', body: {proxy: {port: 24000}}}); assert.equal(res.statusCode, 400); assert_has(res.body, {errors: []}, 'proxies'); })); }); describe('delete', ()=>{ it('normal', etask._fn(function*(_this){ app = yield app_with_args([]); let res = yield api_json('api/proxies/24000', {method: 'delete'}); assert.equal(res.statusCode, 204); })); }); describe('banip', ()=>{ let t = (name, body, status_code)=>it(name, etask._fn( function*(_this){ app = yield app_with_proxies([{port: 24000}], {}); let res = yield api_json('api/proxies/24000/banip', {method: 'post', body}); assert.equal(res.statusCode, status_code); })); t('no ip', {}, 400); t('ip', {ip: '1.1.1.1'}, 204); t('no ip', {ip: 'r0123456789abcdef0123456789ABCDEF'}, 204); }); }); xdescribe('user credentials', ()=>{ it('success', etask._fn(function*(_this){ nock(api_base).get('/').reply(200, {}); nock(api_base).post('/update_lpm_stats').reply(200, {}); nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); app = yield app_with_args(['--customer', 'mock_user']); const res = yield app.manager.get_lum_local_conf(null, '123'); assert_has(res, {mock_result: true}); })); it('login required', etask._fn(function*(_this){ nock(api_base).get('/').reply(200, {}); nock(api_base).get('/cp/lum_local_conf') .query(true) .reply(403, 'login_required'); nock(api_base).get('/cp/lum_local_conf') .query(true) .reply(403, 'login_required'); app = yield app_with_args(['--customer', 'mock_user']); try { yield app.manager.get_lum_local_conf(null, '123'); assert.fail('should have thrown exception'); } catch(e){ assert_has(e, {status: 403, message: 'login_required'}); } })); }); describe('har logs', function(){ this.timeout(6000); beforeEach(()=>etask(function*(){ app = yield app_with_args(['--customer', 'mock_user', '--port', '24000']); app.manager.loki.requests_clear(); app.manager.proxy_ports[24000].emit('usage', { timeline: null, url: 'http://bbc.com', username: 'lum-customer-test_user-zone-static-session-qwe', request: {url: 'http://bbc.com'}, response: {}, }); })); it('fetches all the logs', etask._fn(function*(_this){ const res = yield api_json(`api/logs_har`); assert_has(res.body.log.entries[0], {request: {url: 'http://bbc.com'}}); assert.equal(res.body.log.entries.length, 1); })); it('search by url', etask._fn(function*(_this){ const res = yield api_json('api/logs_har?search=bbc'); assert_has(res.body.log.entries[0], {request: {url: 'http://bbc.com'}}); assert.equal(res.body.log.entries.length, 1); })); it('search by url, no results', etask._fn(function*(_this){ const res = yield api_json('api/logs_har?search=bbcc'); assert.equal(res.body.log.entries.length, 0); })); it('search by session', etask._fn(function*(_this){ const res = yield api_json('api/logs_har?search=qwe'); assert_has(res.body.log.entries[0], {request: {url: 'http://bbc.com'}}); assert.equal(res.body.log.entries.length, 1); })); it('search only by session', etask._fn(function*(_this){ const res = yield api_json('api/logs_har?search=test_user'); assert.equal(res.body.log.entries.length, 0); })); }); describe('add_wip', ()=>{ it('forbidden when google_token is not set', etask._fn(function*(_this){ app = yield app_with_config({config: {}}); const res = yield api_json('api/add_wip', { method: 'POST', headers: {Authorization: 'aaa'}, }); assert.equal(res.statusMessage, 'Forbidden'); assert.equal(res.statusCode, 403); })); it('forbidden when google_token is not correct', etask._fn(function*(_this){ const config = {_defaults: {token_auth: 'aaa'}}; app = yield app_with_config({config}); const res = yield api_json('api/add_wip', {method: 'POST'}); assert.equal(res.statusMessage, 'Forbidden'); assert.equal(res.statusCode, 403); })); it('bad requests if no IP is passed', etask._fn(function*(_this){ const config = {_defaults: {token_auth: 'aaa'}}; app = yield app_with_config({config}); const res = yield api_json('api/add_wip', { method: 'POST', headers: {Authorization: 'aaa'}, }); assert.equal(res.statusMessage, 'Bad Request'); assert.equal(res.statusCode, 400); })); it('adds IP without a mask', etask._fn(function*(_this){ const config = {_defaults: {token_auth: 'aaa'}}; app = yield app_with_config({config}); const res = yield api_json('api/add_wip', { method: 'POST', headers: {Authorization: 'aaa'}, body: {ip: '1.1.1.1'}, }); assert.equal(res.statusCode, 200); assert.equal(app.manager._defaults.whitelist_ips.length, 1); assert.equal(app.manager._defaults.whitelist_ips[0], '1.1.1.1'); })); it('adds IP with a mask', etask._fn(function*(_this){ const config = {_defaults: {token_auth: 'aaa'}}; app = yield app_with_config({config}); const res = yield api_json('api/add_wip', { method: 'POST', headers: {Authorization: 'aaa'}, body: {ip: '1.1.1.1/20'}, }); assert.equal(res.statusCode, 200); assert.equal(app.manager._defaults.whitelist_ips.length, 1); assert.equal(app.manager._defaults.whitelist_ips[0], '1.1.0.0/20'); })); }); }); xdescribe('crash on load error', ()=>{ beforeEach(()=>{ logger_stub.reset(); }); const t = (name, proxies, msg)=>it(name, etask._fn(function*(_this){ const err_matcher = sinon.match(msg); app = yield app_with_proxies(proxies); sinon.assert.calledWith(logger_stub, err_matcher); })); t('conflict proxy port', [{port: 24024}, {port: 24024}], 'Port %s is already in use by #%s - skipped'); const www_port = Manager.default.www; t('conflict with www', [{port: www_port}], `Port %s is already in use by %s - skipped`); }); xdescribe('using passwords', ()=>{ it('take password from provided zone', etask._fn(function*(_this){ const config = {proxies: []}; const _defaults = {zone: 'static', password: 'xyz', zones: {zone1: {password: ['zone1_pass']}}}; nock(api_base).get('/').times(2).reply(200, {}); nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {_defaults}); nock(api_base).post('/update_lpm_stats').query(true) .reply(200, {}); app = yield app_with_config({config, cli: {google_token: '123'}}); const res = yield json('api/proxies', 'post', {proxy: {port: 24000, zone: 'zone1'}}); assert.equal(res.data.password, 'zone1_pass'); })); it('uses password from default zone', etask._fn(function*(_this){ const config = {proxies: []}; const _defaults = {zone: 'static', password: 'xyz', zones: {static: {password: ['static_pass']}}}; nock(api_base).get('/').times(2).reply(200, {}); nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {_defaults}); nock(api_base).post('/update_lpm_stats').query(true) .reply(200, {}); app = yield app_with_config({config, cli: {google_token: '123'}}); const res = yield json('api/proxies', 'post', {proxy: {port: 24000, zone: 'static'}}); assert.equal(res.data.password, 'static_pass'); })); it('uses new proxy custom password', etask._fn(function*(_this){ const config = {proxies: []}; const _defaults = {zone: 'static', password: 'xyz', zones: {static: {password: ['static_pass']}}}; app = yield app_with_config({config, cli: {}}); nock(api_base).get('/cp/lum_local_conf') .query({customer: 'abc', proxy: pkg.version, google_token: ''}) .reply(200, {_defaults}); const res = yield json('api/proxies', 'post', {proxy: {port: 24000, zone: 'static', password: 'p1_pass'}}); assert.equal(res.data.password, 'p1_pass'); })); it('uses existing proxy custom password', etask._fn(function*(_this){ const _defaults = { zone: 'static', password: 'xyz', zones: { static: {password: ['static_pass']}, zone2: {password: ['zone2_pass']}, }, }; nock(api_base).get('/').times(2).reply(200, {}); nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {_defaults}); nock(api_base).post('/update_lpm_stats').query(true) .reply(200, {}); const config = {proxies: [ {port: 24000, zone: 'static', password: 'p1_pass'}, {port: 24001, zone: 'zone2', password: 'p2_pass'}, {port: 24002, zone: 'static'}, {port: 24003, zone: 'zone2'}, {port: 24004}, {port: 24005, zone: 'unknown', password: 'p3_pass'}, ]}; app = yield app_with_config({config, cli: {google_token: '123'}}); const res = yield json('api/proxies_running'); assert.equal(res.find(p=>p.port==24000).password, 'static_pass'); assert.equal(res.find(p=>p.port==24001).password, 'zone2_pass'); assert.equal(res.find(p=>p.port==24002).password, 'static_pass'); assert.equal(res.find(p=>p.port==24003).password, 'zone2_pass'); assert.equal(res.find(p=>p.port==24004).password, 'static_pass'); assert.equal(res.find(p=>p.port==24005).password, 'p3_pass'); })); }); describe('flags', ()=>{ it('exits immediately with version on -v', etask._fn(function*(_this){ const exec = require('child_process').execFile; exec('node', ['./bin/index.js', '--version'], (err, res)=>{ this.continue(); assert.equal(res, pkg.version+'\n'); }); yield this.wait(); })); }); describe('whitelisting', ()=>{ let t = (name, proxies, default_calls, wh, cli, www_whitelist)=> it(name, etask._fn(function*(_this){ _this.timeout(6000); const port = proxies[0].port; app = yield app_with_proxies(proxies, cli); app.manager.set_www_whitelist_ips(www_whitelist||[]); for (const c of default_calls) app.manager.set_whitelist_ips(c); const {whitelist_ips} = app.manager.proxy_ports[port].opt; assert.deepEqual(whitelist_ips, wh); const res = yield make_user_req(); const whitelists = res.body.response.headers.find( h=>h.name=='x-lpm-whitelist'); assert.ok(!!whitelists); assert.equal(whitelists.value, wh.join(' ')); })); const p = [{port: 24000}]; const p_w = [{port: 24000, whitelist_ips: ['1.1.1.1']}]; const p_wi = [{port: 24000, whitelist_ips: ['1.1.1.1', '300/40']}]; const w_cli = {whitelist_ips: ['1.2.3.4', '4.3.2.1']}; t('invalid ips in config', p_wi, [], ['1.1.1.1']); t('sets from cmd', p, [], ['1.2.3.4', '4.3.2.1'], w_cli); t('sets from www', p, [], ['1.1.1.1'], null, ['1.1.1.1']); t('sets default', p, [['2.2.2.2']], ['2.2.2.2']); t('sets specific', p_w, [], ['1.1.1.1']); t('sets cmd and default', p, [['2.2.2.2']], ['1.2.3.4', '4.3.2.1', '2.2.2.2'], w_cli); t('sets cmd and specific', p_w, [], ['1.2.3.4', '4.3.2.1', '1.1.1.1'], w_cli); t('sets cmd and www', p, [], ['1.2.3.4', '4.3.2.1', '1.1.1.1'], w_cli, ['1.1.1.1']); t('sets default and specific', p_w, [['2.2.2.2']], ['2.2.2.2', '1.1.1.1']); t('sets default and www', p, [['2.2.2.2']], ['1.1.1.1', '2.2.2.2'], null, ['1.1.1.1']); t('sets cmd, default and specific', p_w, [['2.2.2.2']], ['1.2.3.4', '4.3.2.1', '2.2.2.2', '1.1.1.1'], w_cli); t('sets cmd, default, www and specific', p_w, [['2.2.2.2']], ['1.2.3.4', '4.3.2.1', '10.1.1.1', '2.2.2.2', '1.1.1.1'], w_cli, ['10.1.1.1']); t('removes IPs from proxy port config when removed in default ', p_w, [['2.2.2.2', '3.3.3.3'], []], ['1.2.3.4', '4.3.2.1', '1.1.1.1'], w_cli); it('updates proxy', ()=>etask(function*(){ app = yield app_with_proxies(p); const whitelist_ips = ['1.1.1.1', '2.2.2.2', '3.0.0.0/8']; const new_proxy = Object.assign({}, p[0], {whitelist_ips}); const opt = yield app.manager.proxy_update(p[0], new_proxy); assert.deepEqual(opt.whitelist_ips, whitelist_ips); })); t = (name, def, default_calls, expected)=> it(name, etask._fn(function*(_this){ const proxies = [{port: 24000, whitelist_ips: w_cli.whitelist_ips.concat(def).concat(expected)}]; app = yield app_with_proxies(proxies, w_cli); const m = app.manager; default_calls.forEach(d=>m.set_whitelist_ips(d)); const s = m.config._serialize(m.proxies, m._defaults); const config = JSON.parse(s); const proxy = config.proxies[0]; assert.equal(proxy.port, proxies[0].port); assert.deepEqual(proxy.whitelist_ips, expected); })); const def = ['3.3.3.3', '4.4.4.4']; t('should not save default/cmd whitelist', def, [def], ['7.8.9.10']); t('should rm from port after rm from default/cmd whitelist', def, [def, def.slice(1)], ['7.8.9.10']); }); describe('pool ips', ()=>{ let t = (name, proxies, expected)=>it(name, etask._fn(function*(_this){ _this.timeout(6000); app = yield app_with_proxies(proxies); let data = {port: 24000, ip: '1.1.1.1'}; app.manager.proxy_ports[24000].emit('add_static_ip', data); sinon.assert.match(app.manager.proxies, [sinon.match(expected)]); })); t('add ip', [{port: 24000, pool_size: 1}], {port: 24000, ips: ['1.1.1.1']}); t('overloading', [{port: 24000, pool_size: 1, ips: ['2.2.2.2']}], {port: 24000, ips: ['2.2.2.2']}); }); xdescribe('migrating', ()=>{ beforeEach(()=>{ logger_stub.reset(); }); const t = (name, should_run_migrations, config={}, cli={})=> it(name, etask._fn(function*(_this){ const notice = 'NOTICE: Migrating config file 1.116.387'; const first_migration_match = sinon.match(notice); app = yield app_with_config({config, cli}); if (should_run_migrations) sinon.assert.calledWith(logger_stub, first_migration_match); else { sinon.assert.neverCalledWith(logger_stub, first_migration_match); } })); t('should run migrations if config file exists and version is old', true, {proxies: [{}]}); t('should not run migrations if --no-config flag is passed', false, {proxies: [{}]}, {'no-config': true}); t('should not run migrations if config does not exist', false); t('should not run migrations if config exists and version is new', false, {_defaults: {version: '1.120.0'}}); }); xdescribe('first actions', function(){ this.timeout(6000); const filepath = path.join(os.tmpdir(), 'first_actions.json'); const rm_actions_file = ()=>{ if (fs.existsSync(filepath)) fs.unlinkSync(filepath); }; let perr_stub; before(()=>lpm_config.first_actions = filepath); beforeEach(()=>{ nock(api_base).get('/').reply(200, {}).persist(); nock(api_base).post('/update_lpm_stats').query(true).reply(200, {}) .persist(); rm_actions_file(); perr_stub = sstub(util_lib, 'perr'); }); afterEach(()=>{ rm_actions_file(); perr_stub.restore(); }); const m = a=>smatch(`first_${a}`); const perr_called_n_times_with = (a, n)=>{ const event = `first_${a}`; const calls = perr_stub.getCalls().filter(c=>c.args[0]==event); assert.equal(calls.length, n); }; const t = (name, called, action, config, conf_success=true)=> it(name, etask._fn(function*(_this){ const i = nock(api_base).get('/cp/lum_local_conf').query(true); if (conf_success) i.reply(200, {mock_result: true, _defaults: true}); else i.reply(403); app = yield app_with_config({config, cli: {google_token: '123'}}); if (called) sinon.assert.calledWith(perr_stub, m(action)); else sinon.assert.neverCalledWith(perr_stub, m(action)); })); t('triggers login on startup if logged', true, 'login'); t('does not trigger login on startup if not logged', false, 'login', null, false); t('triggers create_proxy_port on startup if custom port created', true, 'create_proxy_port', {proxies: [{port: 24023}, {port: 24024}]}); t('does not trigger create_proxy_port on startup if no custom ports', false, 'create_proxy_port'); t('never triggers send_request on startup', false, 'send_request'); t('never triggers send_request_successful on startup', false, 'send_request_successful'); it('maintains actions object structure when file does not exist', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); app = yield app_with_config({cli: {google_token: '123'}}); sinon.assert.match(app.manager.first_actions, smatch({sent: {}, sending: {}, pending: []})); })); it('maintains actions object structure when file is missing fields', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); fs.writeFileSync(lpm_config.first_actions, JSON.stringify({})); app = yield app_with_config({cli: {google_token: '123'}}); sinon.assert.match(app.manager.first_actions, smatch({sent: {}, sending: {}, pending: []})); })); it('does not trigger actions on zone password authentication', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true).reply(403); app = yield app_with_config({config: {proxies: [{port: 24010}]}, cli: {customer: false, google_token: '123'}}); yield make_user_req(24010); sinon.assert.neverCalledWith(perr_stub, smatch('first')); })); it('triggers create_proxy_port_def when using dropin', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); app = yield app_with_config({cli: {google_token: '123', dropin: true}}); yield make_user_req(22225); const matches = ['login', 'create_proxy_port_def', 'send_request', 'send_request_successful'].map(m); matches.forEach(_m=>sinon.assert.calledWith(perr_stub, _m)); sinon.assert.neverCalledWith(perr_stub, smatch(/^first_create_proxy_port$/)); })); it('triggers failed actions after error has happened', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); perr_stub.returns(etask.reject(Error('Network error'))); app = yield app_with_config({cli: {google_token: '123', dropin: true}}); yield make_user_req(22225); perr_stub.resetBehavior(); yield make_user_req(22225); // called 3 times due to logged_update and retry on mgr.start perr_called_n_times_with('login', 3); perr_called_n_times_with('create_proxy_port_def', 2); perr_called_n_times_with('send_request', 2); perr_called_n_times_with('send_request_successful', 2); })); it('stops retrying if action has already been retried', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); perr_stub.restore(); perr_stub = sstub(util_lib, 'perr', id=>{ if (!id.startsWith('first')) return; if (id=='first_send_request') return; return etask.reject(Error('Network error')); }); app = yield app_with_config({cli: {google_token: '123', dropin: true}}); yield make_user_req(22225); yield make_user_req(22225); const failed = ['login', 'create_proxy_port_def', 'send_request_successful']; perr_called_n_times_with('send_request', 1); // called 3 times due to logged_update and retry on mgr.start perr_called_n_times_with('login', 3); failed.slice(1).forEach(a=>perr_called_n_times_with(a, 2)); assert.equal(app.manager.first_actions.pending.filter( d=>failed.includes(d.action)).length, failed.length); })); it('does not trigger send_request events on proxy status request', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); app = yield app_with_config({cli: {google_token: '123', dropin: true}}); yield api('api/proxy_status/22225'); sinon.assert.neverCalledWith(perr_stub, m('send_request')); })); it('proxy_status_get_api return no errors for persist proxy', etask._fn(function*(_this){ nock(api_base).get('/cp/lum_local_conf').query(true) .reply(200, {mock_result: true, _defaults: true}); app = yield app_with_config({ config: {proxies: [{port: 24010}, {port: 24010}]}}); let res = yield api('api/proxy_status/24010'); assert.equal(res.body, '{"status":"ok","status_details":[]}'); })); }); });