@squiz/component-cli-lib 1.39.0 → 1.39.1-alpha.1
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.
- package/.env.example +1 -0
- package/.npm/_logs/{2023-06-21T06_20_44_251Z-debug-0.log → 2023-06-22T05_24_46_739Z-debug-0.log} +16 -16
- package/lib/index.d.ts +1 -0
- package/lib/index.js +139 -139
- package/lib/index.js.map +4 -4
- package/lib/integration-tests/helper.d.ts +5 -0
- package/lib/upload-job.d.ts +4 -0
- package/lib/utils.d.ts +7 -0
- package/lib/utils.spec.d.ts +1 -0
- package/package.json +9 -9
- package/src/index.ts +1 -0
- package/src/integration-tests/__jobs__/invalid-manifest/main.js +3 -0
- package/src/integration-tests/__jobs__/invalid-manifest/manifest.json +28 -0
- package/src/integration-tests/__jobs__/invalid-upload/main.js +3 -0
- package/src/integration-tests/__jobs__/simple-job/main.js +3 -0
- package/src/integration-tests/__jobs__/simple-job/manifest.json +25 -0
- package/src/integration-tests/helper.ts +33 -0
- package/src/upload-component-folder.ts +26 -103
- package/src/upload-job.ts +101 -0
- package/src/utils.spec.ts +199 -0
- package/src/utils.ts +97 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
watchAndWaitForUploadAndScanComplete,
|
|
3
|
+
checkIfVersionExists,
|
|
4
|
+
handleResponse,
|
|
5
|
+
handleError,
|
|
6
|
+
isAxiosError,
|
|
7
|
+
isAxiosResponse,
|
|
8
|
+
} from './utils';
|
|
9
|
+
import axios, { AxiosResponse, AxiosError } from 'axios';
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.resetAllMocks();
|
|
13
|
+
|
|
14
|
+
// Reset our process.env.NODE_ENV variable
|
|
15
|
+
process.env.NODE_ENV = 'test';
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const jobService = axios.create({
|
|
19
|
+
baseURL: 'some_url',
|
|
20
|
+
headers: {
|
|
21
|
+
authorization: 'someToken',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
|
|
26
|
+
it('should pass if virus scan is successful', async () => {
|
|
27
|
+
jest
|
|
28
|
+
.spyOn(jobService as any, `get`)
|
|
29
|
+
.mockImplementation(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));
|
|
30
|
+
|
|
31
|
+
const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
|
|
32
|
+
expect(res).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw specific error if virus scan is flagged', async () => {
|
|
36
|
+
jest
|
|
37
|
+
.spyOn(jobService as any, `get`)
|
|
38
|
+
.mockImplementation(() => Promise.resolve({ data: { status: 'Flagged' } } as AxiosResponse));
|
|
39
|
+
|
|
40
|
+
await expect(async () => {
|
|
41
|
+
await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
|
|
42
|
+
}).rejects.toThrow('upload has been flagged as a virus');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw generic error if virus scan is Error', async () => {
|
|
46
|
+
jest
|
|
47
|
+
.spyOn(jobService as any, `get`)
|
|
48
|
+
.mockImplementation(() => Promise.resolve({ data: { status: 'Error' } } as AxiosResponse));
|
|
49
|
+
|
|
50
|
+
await expect(async () => {
|
|
51
|
+
await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
|
|
52
|
+
}).rejects.toThrow('there has been an error');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should poll until a result is received', async () => {
|
|
56
|
+
jest
|
|
57
|
+
.spyOn(jobService as any, `get`)
|
|
58
|
+
.mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
|
|
59
|
+
.mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
|
|
60
|
+
.mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
|
|
61
|
+
.mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
|
|
62
|
+
.mockImplementationOnce(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));
|
|
63
|
+
|
|
64
|
+
const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
|
|
65
|
+
expect(res).toBeUndefined();
|
|
66
|
+
expect((jobService as any).get.mock.calls).toHaveLength(5);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('cli lib utils checkIfVersionExists', () => {
|
|
71
|
+
it('should return true if version exists', async () => {
|
|
72
|
+
jest.spyOn(jobService as any, `get`).mockImplementation(() => Promise.resolve({ status: 200 } as AxiosResponse));
|
|
73
|
+
|
|
74
|
+
const res = await checkIfVersionExists(jobService, '/job/testJob/1.0.0');
|
|
75
|
+
expect(res).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return false if version does not exist', async () => {
|
|
79
|
+
jest.spyOn(jobService as any, `get`).mockImplementation(() => Promise.resolve({ status: 404 } as AxiosResponse));
|
|
80
|
+
|
|
81
|
+
const res = await checkIfVersionExists(jobService, '/job/testJob/5.0.0');
|
|
82
|
+
expect(res).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw an error if status is not 404 or 200', async () => {
|
|
86
|
+
jest
|
|
87
|
+
.spyOn(jobService as any, `get`)
|
|
88
|
+
.mockImplementation(() =>
|
|
89
|
+
Promise.resolve({ status: 500, data: { message: 'something bad happened' } } as AxiosResponse),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
await expect(async () => {
|
|
93
|
+
await checkIfVersionExists(jobService, '/job/testJob/5.0.0');
|
|
94
|
+
}).rejects.toThrow('Unexpected response code 500. something bad happened');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('cli lib utils handleResponse', () => {
|
|
99
|
+
it('should return response data for a successful request', async () => {
|
|
100
|
+
jest.spyOn(jobService as any, `get`).mockImplementation(
|
|
101
|
+
() =>
|
|
102
|
+
new Promise((resolve) => {
|
|
103
|
+
resolve({ status: 200, data: { status: 'Successful' } } as AxiosResponse);
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const response = jobService.get('/upload-job/status/123');
|
|
108
|
+
const res = await handleResponse(response);
|
|
109
|
+
expect(res).toStrictEqual({ status: 'Successful' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should throw an error if error occurs in the request', async () => {
|
|
113
|
+
jest.spyOn(jobService as any, `get`).mockImplementation(
|
|
114
|
+
() =>
|
|
115
|
+
new Promise((resolve, reject) => {
|
|
116
|
+
reject(new Error('something really bad happened'));
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const response = jobService.get('/upload-job/status/123');
|
|
121
|
+
await expect(async () => {
|
|
122
|
+
await handleResponse(response);
|
|
123
|
+
}).rejects.toThrow('something really bad happened');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('cli lib utils handleError', () => {
|
|
128
|
+
it('should throw an error with message if not an AxiosError', async () => {
|
|
129
|
+
const error = new Error('oh no');
|
|
130
|
+
const res = handleError(error);
|
|
131
|
+
|
|
132
|
+
expect(res.message).toBe('oh no');
|
|
133
|
+
});
|
|
134
|
+
it('should throw an error with generic message if not an AxiosError', async () => {
|
|
135
|
+
const error = new Error();
|
|
136
|
+
const res = handleError(error);
|
|
137
|
+
|
|
138
|
+
expect(res.message).toBe('An error has occurred');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should throw an error with additional fields if an AxiosError without message', async () => {
|
|
142
|
+
const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
|
|
143
|
+
status: 500,
|
|
144
|
+
data: { status: 'fail' },
|
|
145
|
+
} as AxiosResponse);
|
|
146
|
+
const res = handleError(error);
|
|
147
|
+
|
|
148
|
+
expect(res.message).toBe('i am an axios error (code: 500)');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should throw an error with additional fields if an AxiosError with message', async () => {
|
|
152
|
+
const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
|
|
153
|
+
status: 500,
|
|
154
|
+
data: { message: 'i have failed' },
|
|
155
|
+
} as AxiosResponse);
|
|
156
|
+
const res = handleError(error);
|
|
157
|
+
|
|
158
|
+
expect(res.message).toBe('Unexpected response code 500. i have failed');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('cli lib utils isAxiosError', () => {
|
|
163
|
+
it('should return false if not an AxiosError', async () => {
|
|
164
|
+
const error = new Error('oh no');
|
|
165
|
+
const res = isAxiosError(error);
|
|
166
|
+
|
|
167
|
+
expect(res).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
it('should return true if an AxiosError', async () => {
|
|
170
|
+
const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
|
|
171
|
+
status: 500,
|
|
172
|
+
data: { message: 'i have failed' },
|
|
173
|
+
} as AxiosResponse);
|
|
174
|
+
const res = isAxiosError(error);
|
|
175
|
+
|
|
176
|
+
expect(res).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('cli lib utils isAxiosResponse', () => {
|
|
181
|
+
it('should return true if an AxiosResponse', async () => {
|
|
182
|
+
const resp = {
|
|
183
|
+
status: 200,
|
|
184
|
+
data: { message: 'i have succeeded' },
|
|
185
|
+
} as AxiosResponse;
|
|
186
|
+
const res = isAxiosResponse(resp);
|
|
187
|
+
|
|
188
|
+
expect(res).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
it('should return false if not an AxiosResponse', async () => {
|
|
191
|
+
const resp = {
|
|
192
|
+
code: 200,
|
|
193
|
+
data: { message: 'i am not an axios response' },
|
|
194
|
+
};
|
|
195
|
+
const res = isAxiosResponse(resp);
|
|
196
|
+
|
|
197
|
+
expect(res).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
|
|
2
|
+
import { ScanStatus } from '@squiz/virus-scanner-lib';
|
|
3
|
+
|
|
4
|
+
export async function watchAndWaitForUploadAndScanComplete(
|
|
5
|
+
apiClient: AxiosInstance,
|
|
6
|
+
endpoint: string,
|
|
7
|
+
id: string,
|
|
8
|
+
managementURL?: string,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
let poll: () => Promise<any>;
|
|
11
|
+
if (managementURL) {
|
|
12
|
+
poll = () => handleResponse<ScanStatus>(apiClient.get(endpoint + id, { baseURL: managementURL }));
|
|
13
|
+
} else {
|
|
14
|
+
poll = () => handleResponse<ScanStatus>(apiClient.get(endpoint + id));
|
|
15
|
+
}
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const recurse = () =>
|
|
18
|
+
poll().then(async (req) => {
|
|
19
|
+
if (req.status == 'Success') {
|
|
20
|
+
resolve();
|
|
21
|
+
} else if (req.status == 'Flagged') {
|
|
22
|
+
reject(new Error('upload has been flagged as a virus'));
|
|
23
|
+
} else if (req.status == 'Error') {
|
|
24
|
+
reject(new Error('there has been an error'));
|
|
25
|
+
} else {
|
|
26
|
+
setTimeout(async () => {
|
|
27
|
+
await recurse();
|
|
28
|
+
}, 1000);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
recurse();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function checkIfVersionExists(apiClient: AxiosInstance, endpoint: string, managementURL?: string) {
|
|
37
|
+
try {
|
|
38
|
+
const response = await apiClient.get(endpoint, {
|
|
39
|
+
validateStatus: null,
|
|
40
|
+
baseURL: managementURL,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (response.status === 200) {
|
|
44
|
+
return true;
|
|
45
|
+
} else if (response.status === 404) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const error = new AxiosError(
|
|
50
|
+
response?.data?.message || response.statusText,
|
|
51
|
+
response.status.toString(),
|
|
52
|
+
undefined,
|
|
53
|
+
undefined,
|
|
54
|
+
response,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
throw handleError(error);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw handleError(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function handleResponse<T>(axiosInstance: Promise<AxiosResponse<T>>): Promise<T> {
|
|
64
|
+
try {
|
|
65
|
+
const response = await axiosInstance;
|
|
66
|
+
return response.data;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw handleError(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function handleError(error: any): Error {
|
|
73
|
+
const { response } = error;
|
|
74
|
+
const errorMessage = error.message;
|
|
75
|
+
const newError = new Error(errorMessage || 'An error has occurred');
|
|
76
|
+
|
|
77
|
+
if (isAxiosError(error)) {
|
|
78
|
+
const errorCode = response.status ?? 'unknown';
|
|
79
|
+
if (response?.data?.message) {
|
|
80
|
+
newError.message = `Unexpected response code ${errorCode}. ${response.data.message}`.trim();
|
|
81
|
+
} else {
|
|
82
|
+
newError.message = `${errorMessage} (code: ${errorCode})`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
newError.stack = error.stack;
|
|
87
|
+
|
|
88
|
+
return newError;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isAxiosError(error: any): error is AxiosError {
|
|
92
|
+
return error && error.isAxiosError === true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isAxiosResponse(response: unknown): response is AxiosResponse {
|
|
96
|
+
return response instanceof Object && Object.prototype.hasOwnProperty.call(response, 'status');
|
|
97
|
+
}
|