Skip to content

Commit 4d45edd

Browse files
authored
Re-introduce support for node.js 16 (#284)
* Use built-in http client in order to support earlier nodejs version * Provide more information in case of healthcheck failure * Test using node 16.x
1 parent 57184f8 commit 4d45edd

File tree

5 files changed

+74
-58
lines changed

5 files changed

+74
-58
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
node-version: [18.x, 20.x, 22.x]
14+
node-version: [16.x, 18.x, 20.x, 22.x]
1515
steps:
1616
- name: Clone Repository
1717
uses: actions/checkout@v4

src/sauceConnectHealthcheck.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
import http from 'http';
12
import {SC_HEALTHCHECK_TIMEOUT} from './constants';
23

34
export default class SauceConnectHealthCheck {
45
async perform(apiAddress) {
5-
const response = await fetch(`${apiAddress}/readyz`, {
6-
signal: AbortSignal.timeout(SC_HEALTHCHECK_TIMEOUT),
6+
return new Promise((resolve, reject) => {
7+
const request = http.get(
8+
`${apiAddress}/readyz`,
9+
{timeout: SC_HEALTHCHECK_TIMEOUT},
10+
(response) => {
11+
if (response.statusCode !== 200) {
12+
reject(
13+
new Error(`response status code ${response.statusCode} != 200`)
14+
);
15+
} else {
16+
resolve();
17+
}
18+
}
19+
);
20+
21+
request.on('error', reject);
22+
request.on('timeout', () => {
23+
request.destroy();
24+
reject(new Error('Request timed out'));
25+
});
726
});
8-
if (response.status !== 200) {
9-
throw new TypeError(`response status code ${response.status} != 200`);
10-
}
1127
}
1228
}

src/sauceConnectManager.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export class SauceConnectManager {
1515
}
1616

1717
waitForReady(apiAddress) {
18+
let lastHealthcheckErr = null;
1819
apiAddress = this._parseApiAddress(apiAddress);
1920

2021
return new Promise((resolve, reject) => {
@@ -47,17 +48,19 @@ export class SauceConnectManager {
4748
resolve();
4849
})
4950
.catch((err) => {
50-
if (err.name !== 'TypeError') {
51-
clearInterval(this._healthcheckInterval);
52-
clearTimeout(this._readyTimeout);
53-
reject(err);
54-
}
51+
lastHealthcheckErr = err;
5552
});
5653
}, SC_HEALTHCHECK_TIMEOUT);
5754

5855
this._readyTimeout = setTimeout(() => {
5956
clearInterval(this._healthcheckInterval);
60-
reject(new Error('Timeout waiting for healthcheck endpoint'));
57+
console.error(
58+
`Timeout waiting for healthcheck endpoint, err=${lastHealthcheckErr?.message}, code=${lastHealthcheckErr?.code}`
59+
);
60+
reject(
61+
lastHealthcheckErr ||
62+
new Error('Timeout waiting for healthcheck endpoint')
63+
);
6164
}, SC_READY_TIMEOUT);
6265
});
6366
}

tests/sauceConnectHealthcheck.test.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
import SauceConnectHealthCheck from '../src/sauceConnectHealthcheck';
2-
3-
const fetchMock = jest.fn();
4-
global.fetch = fetchMock;
2+
import http from 'http';
53

64
describe('SauceConnectHealthcheck', () => {
5+
let mockHttpGet;
76
beforeEach(() => {
8-
fetchMock.mockClear();
7+
mockHttpGet = jest.spyOn(http, 'get');
98
});
109

1110
describe('perform', () => {
1211
test('raises error if response code != 200', async () => {
13-
fetchMock.mockImplementation(async () => ({
14-
status: 503,
15-
}));
12+
mockHttpGet.mockImplementation((url, options, callback) => {
13+
callback({statusCode: 503});
14+
return {on: jest.fn()};
15+
});
1616
const healthcheck = new SauceConnectHealthCheck();
17-
const error = await healthcheck.perform(':8042').catch((err) => err);
17+
const error = await healthcheck
18+
.perform('http://localhost:8042')
19+
.catch((err) => err);
1820
expect(error.message).toBe('response status code 503 != 200');
1921
});
2022

2123
test('all good when response code == 200', async () => {
22-
fetchMock.mockImplementation(async () => ({
23-
status: 200,
24-
}));
24+
mockHttpGet.mockImplementation((url, options, callback) => {
25+
callback({statusCode: 200});
26+
return {on: jest.fn()};
27+
});
2528
const healthcheck = new SauceConnectHealthCheck();
26-
const result = await healthcheck.perform(':8042');
29+
const result = await healthcheck.perform('http://localhost:8042');
2730
expect(result).toBe(undefined);
2831
});
2932
});

tests/sauceConnectManager.test.js

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,51 +19,45 @@ describe('SauceConnectManager', () => {
1919
);
2020
});
2121

22-
test('error when requesting healthcheck', async () => {
23-
const manager = new SauceConnectManager(
24-
{
25-
stderr: {
26-
on: jest.fn(),
27-
},
28-
stdout: {
29-
on: jest.fn(),
30-
},
31-
},
32-
undefined,
33-
{
34-
async perform() {
35-
throw new Error('custom error');
36-
},
37-
}
38-
);
39-
const error = await manager.waitForReady(':8042').catch((err) => err);
40-
expect(error.message).toBe('custom error');
41-
});
42-
43-
describe('waiting too long for healthcheck', () => {
44-
let origTimeout = constants.SC_READY_TIMEOUT;
22+
describe('short ready timeout', () => {
23+
let origHealthcheckTimeout = constants.SC_HEALTHCHECK_TIMEOUT;
24+
let origReadyTimeout = constants.SC_READY_TIMEOUT;
4525
beforeEach(() => {
4626
// eslint-disable-next-line no-import-assign
47-
Object.defineProperty(constants, 'SC_READY_TIMEOUT', {value: 10});
27+
Object.defineProperty(constants, 'SC_HEALTHCHECK_TIMEOUT', {value: 10});
28+
// eslint-disable-next-line no-import-assign
29+
Object.defineProperty(constants, 'SC_READY_TIMEOUT', {value: 100});
4830
});
4931

50-
test('waiting too long for healthcheck', async () => {
51-
const manager = new SauceConnectManager({
52-
stderr: {
53-
on: jest.fn(),
54-
},
55-
stdout: {
56-
on: jest.fn(),
32+
test('error when requesting healthcheck', async () => {
33+
const manager = new SauceConnectManager(
34+
{
35+
stderr: {
36+
on: jest.fn(),
37+
},
38+
stdout: {
39+
on: jest.fn(),
40+
},
5741
},
58-
});
42+
undefined,
43+
{
44+
async perform() {
45+
throw new Error('custom error');
46+
},
47+
}
48+
);
5949
const error = await manager.waitForReady(':8042').catch((err) => err);
60-
expect(error.message).toBe('Timeout waiting for healthcheck endpoint');
50+
expect(error.message).toBe('custom error');
6151
});
6252

6353
afterEach(() => {
54+
// eslint-disable-next-line no-import-assign
55+
Object.defineProperty(constants, 'SC_HEALTHCHECK_TIMEOUT', {
56+
value: origHealthcheckTimeout,
57+
});
6458
// eslint-disable-next-line no-import-assign
6559
Object.defineProperty(constants, 'SC_READY_TIMEOUT', {
66-
value: origTimeout,
60+
value: origReadyTimeout,
6761
});
6862
});
6963
});

0 commit comments

Comments
 (0)