Skip to content

Commit 925b5a8

Browse files
authored
test(e2e): enhance resilience by adding retries (#547)
1 parent 824b051 commit 925b5a8

File tree

5 files changed

+112
-26
lines changed

5 files changed

+112
-26
lines changed

.github/workflows/e2e.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ jobs:
119119
E2E_CLI_VERSION: ${{ inputs.version }}
120120
E2E_CLI_CMD: ${{ runner.os != 'windows' && format('./{0}', matrix.executable) || format('.\{0}', matrix.executable) }}
121121
E2E_RUN_ID: ${{ format('{0}-{1}-{2}-{3}', github.run_number, github.run_attempt, github.job, strategy.job-index) }}
122-
E2E_CLUSTER: ${{ secrets[format('E2E_{0}_HOST', inputs.environment )] }}
122+
E2E_CLUSTER: ${{ vars[format('E2E_{0}_HOST', inputs.environment )] }}
123+
E2E_PROJECT_ID: ${{ vars[format('E2E_{0}_PROJECT_ID', inputs.environment )] }}
123124
E2E_CLUSTER_API_KEY: ${{ secrets[format('E2E_{0}_API_KEY', inputs.environment )] }}
124125
E2E_REPEATER_TARGET_URL: ${{ format('http://localhost:{0}', steps.target.outputs.port) }}
125126
E2E_REPEATER_TARGET_CMD: ${{ steps.target.outputs.cmd }}
@@ -168,7 +169,8 @@ jobs:
168169
E2E_CLI_VERSION: ${{ inputs.version }}
169170
E2E_CLI_CMD: ${{ 'bright-cli.exe' }}
170171
E2E_RUN_ID: ${{ format('{0}-{1}-{2}-{3}', github.run_number, github.run_attempt, github.job, strategy.job-index) }}
171-
E2E_CLUSTER: ${{ secrets[format('E2E_{0}_HOST', inputs.environment )] }}
172+
E2E_CLUSTER: ${{ vars[format('E2E_{0}_HOST', inputs.environment )] }}
173+
E2E_PROJECT_ID: ${{ vars[format('E2E_{0}_PROJECT_ID', inputs.environment )] }}
172174
E2E_CLUSTER_API_KEY: ${{ secrets[format('E2E_{0}_API_KEY', inputs.environment )] }}
173175
E2E_REPEATER_TARGET_URL: ${{ format('http://localhost:{0}', steps.target.outputs.port) }}
174176
E2E_REPEATER_TARGET_CMD: ${{ steps.target.outputs.cmd }}
@@ -209,7 +211,8 @@ jobs:
209211
E2E_CLI_VERSION: ${{ inputs.version }}
210212
E2E_CLI_CMD: docker run --add-host host.docker.internal:host-gateway --network host brightsec/cli:${{ inputs.version }}
211213
E2E_RUN_ID: ${{ format('{0}-{1}-{2}-{3}', github.run_number, github.run_attempt, github.job, strategy.job-index) }}
212-
E2E_CLUSTER: ${{ secrets[format('E2E_{0}_HOST', inputs.environment )] }}
214+
E2E_CLUSTER: ${{ vars[format('E2E_{0}_HOST', inputs.environment )] }}
215+
E2E_PROJECT_ID: ${{ vars[format('E2E_{0}_PROJECT_ID', inputs.environment )] }}
213216
E2E_CLUSTER_API_KEY: ${{ secrets[format('E2E_{0}_API_KEY', inputs.environment )] }}
214217
E2E_REPEATER_TARGET_URL: ${{ format('http://host.docker.internal:{0}', steps.target.outputs.port) }}
215218
E2E_REPEATER_TARGET_CMD: ${{ steps.target.outputs.cmd }}
@@ -276,7 +279,8 @@ jobs:
276279
E2E_CLI_VERSION: ${{ inputs.version }}
277280
E2E_CLI_CMD: bright-cli
278281
E2E_RUN_ID: ${{ format('{0}-{1}-{2}-{3}', github.run_number, github.run_attempt, github.job, strategy.job-index) }}
279-
E2E_CLUSTER: ${{ secrets[format('E2E_{0}_HOST', inputs.environment )] }}
282+
E2E_CLUSTER: ${{ vars[format('E2E_{0}_HOST', inputs.environment )] }}
283+
E2E_PROJECT_ID: ${{ vars[format('E2E_{0}_PROJECT_ID', inputs.environment )] }}
280284
E2E_CLUSTER_API_KEY: ${{ secrets[format('E2E_{0}_API_KEY', inputs.environment )] }}
281285
E2E_REPEATER_TARGET_URL: ${{ format('http://localhost:{0}', steps.target.outputs.port) }}
282286
E2E_REPEATER_TARGET_CMD: ${{ steps.target.outputs.cmd }}

package-lock.json

Lines changed: 40 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
@@ -73,6 +73,7 @@
7373
"@typescript-eslint/eslint-plugin": "^5.33.1",
7474
"@typescript-eslint/parser": "^5.33.1",
7575
"@yao-pkg/pkg": "^5.11.5",
76+
"axios-retry": "^4.3.0",
7677
"clean-webpack-plugin": "^4.0.0",
7778
"conventional-changelog-conventionalcommits": "^7.0.2",
7879
"cross-env": "^7.0.3",

tests/Commands/repeater.spec.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const config = {
99
cluster: process.env.E2E_CLUSTER,
1010
apiKey: process.env.E2E_CLUSTER_API_KEY,
1111
runId: process.env.E2E_RUN_ID,
12+
projectId: process.env.E2E_PROJECT_ID,
1213
targetUrl: process.env['E2E_REPEATER_TARGET_URL'],
1314
targetCmd: process.env['E2E_REPEATER_TARGET_CMD'],
1415
maxTestTimeout: parseInt(process.env.E2E_TEST_TIMEOUT, 10) * 1000
@@ -30,16 +31,12 @@ describe('Repeater Command', () => {
3031
targetHost = new URL(config.targetUrl).host;
3132
api = new Api({
3233
baseUrl: `https://${config.cluster}`,
33-
apiKey: config.apiKey
34+
apiKey: config.apiKey,
35+
timeout: 60_000,
36+
spoofIP: true
3437
});
3538
});
3639

37-
afterAll(() => {
38-
targetProcess.stderr.destroy();
39-
targetProcess.stdout.destroy();
40-
targetProcess.kill('SIGTERM');
41-
});
42-
4340
beforeEach(async () => {
4441
const [targetCmd, ...targetArgs]: string[] = config.targetCmd.split(' ');
4542
targetProcess = spawn(targetCmd, targetArgs, {
@@ -53,7 +50,7 @@ describe('Repeater Command', () => {
5350
targetProcess.stdout.pipe(process.stdout);
5451
targetProcess.stderr.pipe(process.stderr);
5552

56-
repeaterId = await api.createRepeater(name);
53+
repeaterId = await api.createRepeater(name, config.projectId);
5754
}, 10000);
5855

5956
afterEach(async () => {
@@ -95,7 +92,10 @@ describe('Repeater Command', () => {
9592
name,
9693
repeaters: [repeaterId],
9794
crawlerUrls: [config.targetUrl],
98-
smart: true
95+
slowEpTimeout: 5_000,
96+
targetTimeout: 3,
97+
poolSize: 50,
98+
projectId: config.projectId
9999
});
100100
const scan = await api.waitForScanToFinish(scanId);
101101
const connectivity = await api.getScanEntryPointsConnectivity(scanId);
@@ -119,7 +119,10 @@ describe('Repeater Command', () => {
119119
name,
120120
repeaters: [repeaterId],
121121
crawlerUrls: [config.targetUrl],
122-
smart: true
122+
slowEpTimeout: 5_000,
123+
targetTimeout: 3,
124+
poolSize: 50,
125+
projectId: config.projectId
123126
});
124127

125128
// assert
@@ -159,7 +162,10 @@ describe('Repeater Command', () => {
159162
name,
160163
repeaters: [repeaterId],
161164
crawlerUrls: [config.targetUrl],
162-
smart: true
165+
slowEpTimeout: 5_000,
166+
targetTimeout: 3,
167+
poolSize: 50,
168+
projectId: config.projectId
163169
});
164170
const scan = await api.waitForScanToFinish(scanId);
165171
const connectivity = await api.getScanEntryPointsConnectivity(scanId);

tests/Setup/api.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import axios, { Axios } from 'axios';
1+
import axios, { AxiosInstance } from 'axios';
2+
import axiosRetry, { exponentialDelay } from 'axios-retry';
23
import { setTimeout } from 'node:timers/promises';
34

45
export interface ApiOptions {
56
baseUrl: string;
67
apiKey: string;
8+
timeout?: number;
9+
spoofIP?: boolean;
710
}
811

912
export interface WaitOptions {
@@ -13,22 +16,33 @@ export interface WaitOptions {
1316

1417
export interface CreateScanProps {
1518
name: string;
16-
repeaters?: string[];
1719
crawlerUrls: string[];
18-
smart: boolean;
20+
repeaters?: string[];
21+
slowEpTimeout?: number;
22+
targetTimeout?: number;
23+
poolSize?: number;
24+
projectId?: string;
1925
}
2026

2127
export class Api {
22-
private readonly client: Axios;
28+
private readonly client: AxiosInstance;
2329

2430
constructor(options: ApiOptions) {
2531
this.client = axios.create({
2632
baseURL: options.baseUrl,
2733
responseType: 'json',
34+
timeout: options.timeout,
2835
transitional: {
2936
clarifyTimeoutError: true
3037
},
31-
headers: { authorization: `api-key ${options.apiKey}` }
38+
headers: {
39+
authorization: `api-key ${options.apiKey}`
40+
}
41+
});
42+
43+
axiosRetry(this.client, {
44+
retries: 10,
45+
retryDelay: exponentialDelay
3246
});
3347

3448
const isGithubRunnerDebugMode =
@@ -45,6 +59,14 @@ export class Api {
4559
body: request.data
4660
});
4761

62+
if (options.spoofIP) {
63+
const ip = this.getRandomIP();
64+
65+
request.headers['x-forwarded-for'] = ip;
66+
request.headers['x-real-ip'] = ip;
67+
request.headers['forwarded'] = `for=${ip}`;
68+
}
69+
4870
return request;
4971
});
5072

@@ -90,11 +112,15 @@ export class Api {
90112
return data;
91113
}
92114

93-
public async createRepeater(name: string): Promise<string> {
115+
public async createRepeater(
116+
name: string,
117+
projectId?: string
118+
): Promise<string> {
94119
const { data } = await this.client.post<{ id: string }>(
95120
'/api/v1/repeaters',
96121
{
97-
name
122+
name,
123+
...(projectId ? { projectIds: [projectId] } : {})
98124
}
99125
);
100126

@@ -118,8 +144,8 @@ export class Api {
118144
repeaterId: string,
119145
options?: WaitOptions
120146
) {
121-
const maxAttempts = options?.maxAttempts ?? 20;
122-
const timeout = options?.timeout ?? 10000;
147+
const maxAttempts = options?.maxAttempts ?? 25;
148+
const timeout = options?.timeout ?? 10_000;
123149

124150
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
125151
const { data } = await this.client.get<{ status: string }>(
@@ -141,8 +167,8 @@ export class Api {
141167
}
142168

143169
public async waitForScanToFinish(scanId: string, options?: WaitOptions) {
144-
const maxAttempts = options?.maxAttempts ?? 100;
145-
const timeout = options?.timeout ?? 60000;
170+
const maxAttempts = options?.maxAttempts ?? 50;
171+
const timeout = options?.timeout ?? 30_000;
146172

147173
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
148174
const { data } = await this.client.get<{
@@ -165,4 +191,13 @@ export class Api {
165191
}
166192
}
167193
}
194+
195+
private getRandomIP() {
196+
const octet1 = Math.floor(Math.random() * 256);
197+
const octet2 = Math.floor(Math.random() * 256);
198+
const octet3 = Math.floor(Math.random() * 256);
199+
const octet4 = Math.floor(Math.random() * 256);
200+
201+
return `${octet1}.${octet2}.${octet3}.${octet4}`;
202+
}
168203
}

0 commit comments

Comments
 (0)