@testim/testim-cli 3.234.0 → 3.237.0

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.
@@ -0,0 +1,140 @@
1
+ /* eslint-disable no-console */
2
+
3
+ 'use strict';
4
+
5
+ const { ArgError } = require('../errors.js');
6
+ const { sinon, expect } = require('../../test/utils/testUtils');
7
+ const testimNgrok = require('./testimNgrok');
8
+ const utils = require('../utils');
9
+ const npmWrapper = require('./npmWrapper');
10
+
11
+
12
+
13
+ describe('testimNgrok', () => {
14
+ let sandbox;
15
+ let authData;
16
+ let tunnelUrl;
17
+ let collectNgrokStatsStub;
18
+
19
+ const ngrokMock = {
20
+ connect: () => {},
21
+ disconnect: () => {},
22
+ getApi: () => {},
23
+ };
24
+
25
+ beforeEach(() => {
26
+ sandbox = sinon.createSandbox();
27
+ authData = { ngrokToken: utils.guid() };
28
+ tunnelUrl = utils.guid();
29
+ sandbox.stub(ngrokMock, 'connect').callsFake(() => tunnelUrl);
30
+ sandbox.stub(ngrokMock, 'disconnect');
31
+ sandbox.stub(npmWrapper, 'getPackageIfInstalledLocally').returns(ngrokMock);
32
+ collectNgrokStatsStub = sandbox.stub(testimNgrok, 'collectNgrokStats');
33
+ });
34
+ afterEach(() => {
35
+ sandbox.restore();
36
+ });
37
+
38
+ describe('disconnectTunnel', () => {
39
+ it('should do nothing when no tunnel', async () => {
40
+ await testimNgrok.disconnectTunnel({});
41
+ sinon.assert.neverCalledWith(ngrokMock.disconnect);
42
+ });
43
+
44
+ it('should disconnect the tunnel', async () => {
45
+ await testimNgrok.connectTunnel({}, authData);
46
+ await testimNgrok.disconnectTunnel({});
47
+ sinon.assert.calledOnce(ngrokMock.disconnect);
48
+ });
49
+
50
+ it('should collect stats on disconnect', async () => {
51
+ await testimNgrok.connectTunnel({}, authData);
52
+ await testimNgrok.disconnectTunnel({ tunnelDiagnostics: true });
53
+
54
+ sinon.assert.calledOnce(collectNgrokStatsStub);
55
+ });
56
+ });
57
+
58
+ describe('connectTunnel', () => {
59
+ it('should throw when no token', async () => {
60
+ await expect(testimNgrok.connectTunnel({ company: {} })).to.be.rejectedWith(ArgError, 'tunnel feature is not enabled, please contact support - info@testim.io.');
61
+ });
62
+
63
+ it('should spawn the tunnel', async () => {
64
+ const opts = {};
65
+ await testimNgrok.connectTunnel(opts, authData);
66
+
67
+ sinon.assert.calledOnce(ngrokMock.connect);
68
+ expect(ngrokMock.connect.args[0][0]).to.shallowDeepEqual({
69
+ proto: 'http',
70
+ addr: 80,
71
+ authtoken: authData.ngrokToken,
72
+ hostname: undefined,
73
+ });
74
+ expect(opts.baseUrl).to.equal(tunnelUrl);
75
+ });
76
+
77
+ it('should return whitlisted url when isNgrokWhitelisted', async () => {
78
+ authData.isNgrokWhitelisted = true;
79
+ await testimNgrok.connectTunnel({ projectData: { projectId: 'projectId' } }, authData);
80
+ expect(ngrokMock.connect.args[0][0].hostname).to.endWith('projectId.whitelisted-ngrok.testim.io');
81
+ });
82
+
83
+ it('should support tunnelHostHeader', async () => {
84
+ const tunnelHostHeader = utils.guid();
85
+ await testimNgrok.connectTunnel({ tunnelHostHeader }, authData);
86
+ // eslint-disable-next-line camelcase
87
+ expect(ngrokMock.connect.args[0][0]).to.shallowDeepEqual({ host_header: tunnelHostHeader });
88
+ });
89
+
90
+ it('should support tunnelRegion', async () => {
91
+ const tunnelRegion = utils.guid();
92
+ await testimNgrok.connectTunnel({ tunnelRegion }, authData);
93
+ expect(ngrokMock.connect.args[0][0]).to.shallowDeepEqual({ region: tunnelRegion });
94
+ });
95
+
96
+ it('should force using http when passing tunnelUseHttpAddress', async () => {
97
+ const opts = { tunnelUseHttpAddress: true };
98
+ const guid = utils.guid();
99
+ tunnelUrl = `https://${guid}`;
100
+ await testimNgrok.connectTunnel(opts, authData);
101
+
102
+ sinon.assert.calledOnce(ngrokMock.connect);
103
+ expect(opts.baseUrl).to.equal(`http://${guid}`);
104
+ });
105
+
106
+ it('should collect stats on connect', async () => {
107
+ await testimNgrok.connectTunnel({ tunnelDiagnostics: true }, authData);
108
+ sinon.assert.calledOnce(collectNgrokStatsStub);
109
+ });
110
+ });
111
+
112
+ describe('collectNgrokStats', () => {
113
+ let tunnels;
114
+ let getApiStub;
115
+ beforeEach(() => {
116
+ tunnels = [{ }];
117
+ getApiStub = sandbox.stub(ngrokMock, 'getApi').returns({ get: () => ({ tunnels }) });
118
+ collectNgrokStatsStub.callThrough();
119
+ });
120
+
121
+ it('should collect stats', async () => {
122
+ await testimNgrok.collectNgrokStats(false);
123
+ sinon.assert.calledOnce(ngrokMock.getApi);
124
+ });
125
+
126
+ it('should rerun itself using timeout', async () => {
127
+ const clock = sandbox.useFakeTimers();
128
+
129
+ await testimNgrok.collectNgrokStats();
130
+ clock.tick(15000);
131
+ sinon.assert.calledTwice(collectNgrokStatsStub);
132
+ });
133
+
134
+ it('should ignore errors while collecting stats', async () => {
135
+ getApiStub.throws(new Error('test'));
136
+ await testimNgrok.collectNgrokStats();
137
+ sinon.assert.calledOnce(ngrokMock.getApi);
138
+ });
139
+ });
140
+ });
@@ -3,7 +3,7 @@
3
3
  const ora = require('ora');
4
4
 
5
5
  const LambdatestService = require('../services/lambdatestService');
6
- const { registerExitHook } = require('../processHandler');
6
+ const processHandler = require('../processHandler');
7
7
  const testimCustomToken = require('./testimCustomToken');
8
8
  const { gridTypes } = require('./constants');
9
9
  const testimNgrok = require('./testimNgrok');
@@ -59,10 +59,10 @@ const disconnect = async (options) => {
59
59
  }
60
60
  };
61
61
 
62
- const serveTunneling = async (options) => {
63
- await connect(options);
64
- registerExitHook(() => disconnect(options));
65
- return await new Promise(() => { /* avoid exiting process */ });
62
+ const serveTunneling = async (options, waitFor = new Promise(() => { /* avoid exiting process */ })) => {
63
+ await module.exports.connect(options);
64
+ processHandler.registerExitHook(() => module.exports.disconnect(options));
65
+ return await waitFor;
66
66
  };
67
67
 
68
68
  module.exports = {
@@ -1,49 +1,67 @@
1
1
  const { expect, sinon } = require('../../test/utils/testUtils');
2
2
  const LambdatestService = require('../services/lambdatestService');
3
+ const processHandler = require('../processHandler');
3
4
  const testimNgrok = require('./testimNgrok');
5
+ const testimCloudflare = require('./testimCloudflare');
4
6
  const testimTunnel = require('./testimTunnel');
5
7
 
6
8
  describe('testimTunnel', () => {
7
9
  describe('connect', () => {
8
10
  let ltConnectStub;
9
11
  let ngrokConnectStub;
12
+ let cloudflareConnectStub;
13
+
10
14
  beforeEach(() => {
11
15
  ltConnectStub = sinon.stub(LambdatestService, 'connectTunnel').resolves();
12
16
  ngrokConnectStub = sinon.stub(testimNgrok, 'connectTunnel').resolves();
17
+ cloudflareConnectStub = sinon.stub(testimCloudflare, 'connectTunnel').resolves();
13
18
  });
14
19
  afterEach(() => {
15
20
  ltConnectStub.restore();
16
21
  ngrokConnectStub.restore();
22
+ cloudflareConnectStub.restore();
17
23
  });
18
24
 
19
25
  it('should not connect to tunnel if tunnel option off', async () => {
20
26
  await testimTunnel.connect({});
21
27
  sinon.assert.notCalled(ltConnectStub);
22
28
  sinon.assert.notCalled(ngrokConnectStub);
29
+ sinon.assert.notCalled(cloudflareConnectStub);
23
30
  });
24
31
 
25
32
  it('should choose ngrok if passed grid is not a lambdatest grid', async () => {
26
33
  await testimTunnel.connect({ tunnel: true, gridData: { } });
27
34
  sinon.assert.notCalled(ltConnectStub);
28
35
  sinon.assert.calledOnce(ngrokConnectStub);
36
+ sinon.assert.notCalled(cloudflareConnectStub);
37
+ });
38
+
39
+ it('should choose cloudflare if passed tunnelRoutes options', async () => {
40
+ await testimTunnel.connect({ tunnel: true, tunnelRoutes: [] });
41
+ sinon.assert.notCalled(ltConnectStub);
42
+ sinon.assert.notCalled(ngrokConnectStub);
43
+ sinon.assert.calledOnce(cloudflareConnectStub);
29
44
  });
30
45
 
31
46
  it('should choose lambdatest if passed grid is a lambdatest grid', async () => {
32
47
  await testimTunnel.connect({ tunnel: true, gridData: { type: 'testimLambdaTest' } });
33
48
  sinon.assert.calledOnce(ltConnectStub);
34
49
  sinon.assert.notCalled(ngrokConnectStub);
50
+ sinon.assert.notCalled(cloudflareConnectStub);
35
51
  });
36
52
 
37
53
  it('should choose lambdatest if passed grid is a hybrid grid', async () => {
38
54
  await testimTunnel.connect({ tunnel: true, gridData: { type: 'testimHybrid' } });
39
55
  sinon.assert.calledOnce(ltConnectStub);
40
56
  sinon.assert.notCalled(ngrokConnectStub);
57
+ sinon.assert.notCalled(cloudflareConnectStub);
41
58
  });
42
59
 
43
60
  it('should choose ngrok if passed grid is a hybrid grid and it is set to use ngrok tunnel', async () => {
44
61
  await testimTunnel.connect({ tunnel: true, gridData: { type: 'testimHybrid', tunnel: 'ngrok' } });
45
62
  sinon.assert.notCalled(ltConnectStub);
46
63
  sinon.assert.calledOnce(ngrokConnectStub);
64
+ sinon.assert.notCalled(cloudflareConnectStub);
47
65
  });
48
66
 
49
67
  it('should handle connect errors', async () => {
@@ -55,43 +73,59 @@ describe('testimTunnel', () => {
55
73
  describe('disconnect', () => {
56
74
  let ltDisconnectStub;
57
75
  let ngrokDisconnectStub;
76
+ let cloudflareDisconnectStub;
77
+
58
78
  beforeEach(() => {
59
79
  ltDisconnectStub = sinon.stub(LambdatestService, 'disconnectTunnel').resolves();
60
80
  ngrokDisconnectStub = sinon.stub(testimNgrok, 'disconnectTunnel').resolves();
81
+ cloudflareDisconnectStub = sinon.stub(testimCloudflare, 'disconnectTunnel').resolves();
61
82
  });
62
83
  afterEach(() => {
63
84
  ltDisconnectStub.restore();
64
85
  ngrokDisconnectStub.restore();
86
+ cloudflareDisconnectStub.restore();
65
87
  });
66
88
 
67
89
  it('should not disconnect from tunnel if tunnel option off', async () => {
68
90
  await testimTunnel.disconnect({});
69
91
  sinon.assert.notCalled(ltDisconnectStub);
70
92
  sinon.assert.notCalled(ngrokDisconnectStub);
93
+ sinon.assert.notCalled(cloudflareDisconnectStub);
71
94
  });
72
95
 
73
96
  it('should choose ngrok if passed grid is not a lambdatest grid', async () => {
74
97
  await testimTunnel.disconnect({ tunnel: true, gridData: { } });
75
98
  sinon.assert.notCalled(ltDisconnectStub);
76
99
  sinon.assert.calledOnce(ngrokDisconnectStub);
100
+ sinon.assert.notCalled(cloudflareDisconnectStub);
101
+ });
102
+
103
+ it('should choose cloudflare if passed tunnelRoutes options', async () => {
104
+ await testimTunnel.disconnect({ tunnel: true, tunnelRoutes: [] });
105
+ sinon.assert.notCalled(ltDisconnectStub);
106
+ sinon.assert.notCalled(ngrokDisconnectStub);
107
+ sinon.assert.calledOnce(cloudflareDisconnectStub);
77
108
  });
78
109
 
79
110
  it('should choose lambdatest if passed grid is a lambdatest grid', async () => {
80
111
  await testimTunnel.disconnect({ tunnel: true, gridData: { type: 'testimLambdaTest' } });
81
112
  sinon.assert.calledOnce(ltDisconnectStub);
82
113
  sinon.assert.notCalled(ngrokDisconnectStub);
114
+ sinon.assert.notCalled(cloudflareDisconnectStub);
83
115
  });
84
116
 
85
117
  it('should choose lambdatest if passed grid is a hybrid grid', async () => {
86
118
  await testimTunnel.disconnect({ tunnel: true, gridData: { type: 'testimHybrid' } });
87
119
  sinon.assert.calledOnce(ltDisconnectStub);
88
120
  sinon.assert.notCalled(ngrokDisconnectStub);
121
+ sinon.assert.notCalled(cloudflareDisconnectStub);
89
122
  });
90
123
 
91
124
  it('should choose ngrok if passed grid is a hybrid grid and it is set to use ngrok tunnel', async () => {
92
125
  await testimTunnel.disconnect({ tunnel: true, gridData: { type: 'testimHybrid', tunnel: 'ngrok' } });
93
126
  sinon.assert.notCalled(ltDisconnectStub);
94
127
  sinon.assert.calledOnce(ngrokDisconnectStub);
128
+ sinon.assert.notCalled(cloudflareDisconnectStub);
95
129
  });
96
130
 
97
131
  it('should handle connect errors', async () => {
@@ -99,4 +133,39 @@ describe('testimTunnel', () => {
99
133
  await expect(testimTunnel.disconnect({ tunnel: true, gridData: { type: 'testimLambdaTest' } })).to.be.rejectedWith('catch error - failed to close tunnel');
100
134
  });
101
135
  });
136
+
137
+ describe('serveTunneling', () => {
138
+ let connectStub;
139
+ let disconnectStub;
140
+ let registerExitHookStub;
141
+ let exitHooks;
142
+
143
+ beforeEach(() => {
144
+ exitHooks = [];
145
+ connectStub = sinon.stub(testimTunnel, 'connect').resolves();
146
+ disconnectStub = sinon.stub(testimTunnel, 'disconnect').resolves();
147
+ registerExitHookStub = sinon.stub(processHandler, 'registerExitHook').callsFake(cb => { exitHooks.push(cb); });
148
+ });
149
+ afterEach(() => {
150
+ connectStub.restore();
151
+ registerExitHookStub.restore();
152
+ disconnectStub.restore();
153
+ });
154
+
155
+ it('should connect to tunnel', async () => {
156
+ await testimTunnel.serveTunneling({}, Promise.resolve());
157
+ sinon.assert.calledOnce(connectStub);
158
+ });
159
+
160
+ it('should disconnect from tunnel on exit', async () => {
161
+ await testimTunnel.serveTunneling({}, Promise.resolve());
162
+ sinon.assert.calledOnce(registerExitHookStub);
163
+ exitHooks[0]();
164
+ sinon.assert.calledOnce(disconnectStub);
165
+ });
166
+
167
+ it('should return a default promise that never resolves', async () => {
168
+ expect(testimTunnel.serveTunneling({})).to.be.an.instanceof(Promise);
169
+ });
170
+ });
102
171
  });
package/executionQueue.js CHANGED
@@ -7,6 +7,10 @@ class ExecutionQueue {
7
7
  this._waitingTests = testList.map(testInfo => new TestRun(executionId, executionName, testInfo, options, branchToUse, testStatus));
8
8
  }
9
9
 
10
+ stop() {
11
+ this._waitingTests = [];
12
+ }
13
+
10
14
  getNext() {
11
15
  const nextTestRunHandler = this._waitingTests.shift();
12
16
  if (nextTestRunHandler) {