@allurereport/plugin-testops 3.3.1 → 3.4.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/README.md +8 -7
- package/dist/client.d.ts +38 -11
- package/dist/client.js +318 -67
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/logger.d.ts +24 -0
- package/dist/logger.js +129 -0
- package/dist/model.d.ts +80 -4
- package/dist/plugin.d.ts +9 -9
- package/dist/plugin.js +329 -101
- package/dist/uploadCategory.d.ts +3 -0
- package/dist/uploadCategory.js +20 -0
- package/dist/utils/categories.d.ts +4 -0
- package/dist/utils/categories.js +82 -0
- package/dist/utils.d.ts +5 -2
- package/dist/utils.js +34 -1
- package/package.json +22 -33
package/README.md
CHANGED
|
@@ -51,13 +51,14 @@ export default defineConfig({
|
|
|
51
51
|
|
|
52
52
|
The plugin accepts the following options:
|
|
53
53
|
|
|
54
|
-
| Option
|
|
55
|
-
|
|
56
|
-
| `launchName`
|
|
57
|
-
| `launchTags`
|
|
58
|
-
| `accessToken`
|
|
59
|
-
| `endpoint`
|
|
60
|
-
| `projectId`
|
|
54
|
+
| Option | Description | Type | Default |
|
|
55
|
+
|--------------------|-----------------------------------------------------------------------------|-----------|-----------------|
|
|
56
|
+
| `launchName` | Name of the report which will be assigned to the new launch | `string` | `Allure Report` |
|
|
57
|
+
| `launchTags` | Tags to be assigned to the new launch | `string[]`| `[]` |
|
|
58
|
+
| `accessToken` | Access token for TestOps API | `string` | `undefined` |
|
|
59
|
+
| `endpoint` | TestOps API endpoint | `string` | `undefined` |
|
|
60
|
+
| `projectId` | TestOps project ID | `string` | `undefined` |
|
|
61
|
+
| `autocloseLaunch` | When `true` (default), the launch is closed automatically when the plugin finishes; set to `false` to keep the launch open | `boolean` | `true` |
|
|
61
62
|
|
|
62
63
|
### Using options from environment variables
|
|
63
64
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,22 +1,49 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientRequest } from "http";
|
|
2
|
+
import type { AttachmentLink, CiDescriptor, EnvironmentIdentity, TestError, TestResult, TestStatus } from "@allurereport/core-api";
|
|
3
|
+
import type { QualityGateValidationResult } from "@allurereport/plugin-api";
|
|
4
|
+
import { AxiosError, type AxiosResponse } from "axios";
|
|
5
|
+
import type { AttachmentForUpload, AttachmentsResolver, FixtureResolver, LaunchCategoryBulkItem, LaunchCategoryBulkResult, TestOpsClientParams, TestOpsNamedEnv, TestOpsPluginTestResult } from "./model.js";
|
|
6
|
+
declare class TestOpsClientError extends AxiosError<{
|
|
7
|
+
message: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
status: number;
|
|
10
|
+
}> {
|
|
11
|
+
response: AxiosResponse<{
|
|
12
|
+
message: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
status: number;
|
|
15
|
+
}>;
|
|
16
|
+
request: ClientRequest;
|
|
17
|
+
}
|
|
2
18
|
export declare class TestOpsClient {
|
|
3
19
|
#private;
|
|
4
|
-
constructor(params:
|
|
5
|
-
|
|
6
|
-
projectId: string;
|
|
7
|
-
accessToken: string;
|
|
8
|
-
limit?: number;
|
|
9
|
-
});
|
|
20
|
+
constructor(params: TestOpsClientParams);
|
|
21
|
+
isTestOpsClientError(error: unknown): error is TestOpsClientError;
|
|
10
22
|
get launchUrl(): string | undefined;
|
|
23
|
+
get launchId(): number | undefined;
|
|
24
|
+
closeLaunch(launchId: number): Promise<void>;
|
|
25
|
+
createLaunchCategoriesBulk(launchId: number, items: LaunchCategoryBulkItem[]): Promise<LaunchCategoryBulkResult[]>;
|
|
11
26
|
issueOauthToken(): Promise<void>;
|
|
12
27
|
startUpload(ci: CiDescriptor): Promise<void>;
|
|
13
28
|
stopUpload(ci: CiDescriptor, status: TestStatus): Promise<void>;
|
|
14
29
|
createLaunch(launchName: string, launchTags: string[]): Promise<void>;
|
|
15
30
|
createSession(environment?: Record<string, any>): Promise<void>;
|
|
31
|
+
get namedEnvs(): MapIterator<TestOpsNamedEnv>;
|
|
32
|
+
getNamedEnvFor(id: string): TestOpsNamedEnv | undefined;
|
|
33
|
+
createNamedEnvs(environments: EnvironmentIdentity[], onProgress?: (percent: number, total: number) => void): Promise<void>;
|
|
34
|
+
uploadGlobalAttachments(params: {
|
|
35
|
+
attachments: AttachmentLink[];
|
|
36
|
+
attachmentsResolver: (attachment: AttachmentLink) => Promise<AttachmentForUpload | undefined>;
|
|
37
|
+
onProgress?: (percent: number, total: number) => void;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
uploadGlobalErrors(errors: TestError[], onProgress?: (percent: number, total: number) => void): Promise<void>;
|
|
16
40
|
uploadTestResults(params: {
|
|
17
|
-
trs:
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
trs: TestOpsPluginTestResult[];
|
|
42
|
+
environments: EnvironmentIdentity[];
|
|
43
|
+
attachmentsResolver: AttachmentsResolver;
|
|
44
|
+
fixturesResolver: FixtureResolver;
|
|
20
45
|
onProgress?: () => void;
|
|
21
|
-
}): Promise<
|
|
46
|
+
}): Promise<TestResult[]>;
|
|
47
|
+
uploadQualityGateResults(results: QualityGateValidationResult[], onProgress?: (percent: number, total: number) => void): Promise<void>;
|
|
22
48
|
}
|
|
49
|
+
export {};
|
package/dist/client.js
CHANGED
|
@@ -9,13 +9,21 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _TestOpsClient_accessToken, _TestOpsClient_projectId, _TestOpsClient_oauthToken, _TestOpsClient_client, _TestOpsClient_launch, _TestOpsClient_session, _TestOpsClient_uploadInProgress, _TestOpsClient_uploadLimit;
|
|
13
|
-
import axios from "axios";
|
|
12
|
+
var _TestOpsClient_instances, _TestOpsClient_logger, _TestOpsClient_accessToken, _TestOpsClient_projectId, _TestOpsClient_oauthToken, _TestOpsClient_client, _TestOpsClient_launch, _TestOpsClient_session, _TestOpsClient_uploadInProgress, _TestOpsClient_uploadLimit, _TestOpsClient_namedEnvsIdsByEnv, _TestOpsClient_postTestResultsChunk, _TestOpsClient_uploadChunkAttachmentsAndFixtures, _TestOpsClient_uploadAttachmentsForResult, _TestOpsClient_uploadFixturesForResult;
|
|
13
|
+
import axios, { AxiosError, isAxiosError } from "axios";
|
|
14
14
|
import FormData from "form-data";
|
|
15
|
-
import chunk from "lodash
|
|
15
|
+
import { chunk } from "lodash-es";
|
|
16
16
|
import pLimit from "p-limit";
|
|
17
|
+
import { bold } from "yoctocolors";
|
|
18
|
+
import { Logger } from "./logger.js";
|
|
19
|
+
class TestOpsClientError extends AxiosError {
|
|
20
|
+
}
|
|
21
|
+
const CHUNK_SIZE = 100;
|
|
22
|
+
const BULK_UPLOAD_CHUNK_SIZE = 1000;
|
|
17
23
|
export class TestOpsClient {
|
|
18
24
|
constructor(params) {
|
|
25
|
+
_TestOpsClient_instances.add(this);
|
|
26
|
+
_TestOpsClient_logger.set(this, new Logger("TestOpsClient"));
|
|
19
27
|
_TestOpsClient_accessToken.set(this, void 0);
|
|
20
28
|
_TestOpsClient_projectId.set(this, void 0);
|
|
21
29
|
_TestOpsClient_oauthToken.set(this, "");
|
|
@@ -24,6 +32,7 @@ export class TestOpsClient {
|
|
|
24
32
|
_TestOpsClient_session.set(this, void 0);
|
|
25
33
|
_TestOpsClient_uploadInProgress.set(this, false);
|
|
26
34
|
_TestOpsClient_uploadLimit.set(this, 1);
|
|
35
|
+
_TestOpsClient_namedEnvsIdsByEnv.set(this, new Map());
|
|
27
36
|
if (!params.accessToken) {
|
|
28
37
|
throw new Error("accessToken is required");
|
|
29
38
|
}
|
|
@@ -42,28 +51,86 @@ export class TestOpsClient {
|
|
|
42
51
|
baseURL: params.baseUrl,
|
|
43
52
|
validateStatus: (status) => status >= 200 && status < 400,
|
|
44
53
|
}), "f");
|
|
54
|
+
__classPrivateFieldGet(this, _TestOpsClient_client, "f").interceptors.request.use((config) => {
|
|
55
|
+
if (__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")) {
|
|
56
|
+
config.headers.set("Authorization", `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`);
|
|
57
|
+
}
|
|
58
|
+
return config;
|
|
59
|
+
});
|
|
45
60
|
if (params.limit) {
|
|
46
61
|
__classPrivateFieldSet(this, _TestOpsClient_uploadLimit, params.limit, "f");
|
|
47
62
|
}
|
|
48
63
|
}
|
|
64
|
+
isTestOpsClientError(error) {
|
|
65
|
+
return (isAxiosError(error) &&
|
|
66
|
+
typeof error.response?.data.status === "number" &&
|
|
67
|
+
typeof error.response?.data.message === "string");
|
|
68
|
+
}
|
|
49
69
|
get launchUrl() {
|
|
50
70
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
51
71
|
return undefined;
|
|
52
72
|
}
|
|
53
73
|
return new URL(`launch/${__classPrivateFieldGet(this, _TestOpsClient_launch, "f").id}`, __classPrivateFieldGet(this, _TestOpsClient_client, "f").defaults.baseURL).toString();
|
|
54
74
|
}
|
|
75
|
+
get launchId() {
|
|
76
|
+
return __classPrivateFieldGet(this, _TestOpsClient_launch, "f")?.id;
|
|
77
|
+
}
|
|
78
|
+
async closeLaunch(launchId) {
|
|
79
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Closing launch…");
|
|
80
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/launch/${launchId}/close`);
|
|
81
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Launch closed");
|
|
82
|
+
}
|
|
83
|
+
async createLaunchCategoriesBulk(launchId, items) {
|
|
84
|
+
if (items.length === 0) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const results = [];
|
|
88
|
+
const uploadChunk = async (chunk, currentRequestIndex, totalChunks) => {
|
|
89
|
+
const body = { launchId, items: chunk };
|
|
90
|
+
if (totalChunks === 1) {
|
|
91
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(`POST /api/launch/category/bulk request (items: ${chunk.length})`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(`POST /api/launch/category/bulk request (${currentRequestIndex + 1}/${totalChunks}, items: ${chunk.length})`);
|
|
95
|
+
}
|
|
96
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(body);
|
|
97
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/category/bulk", body, {
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
});
|
|
100
|
+
if (Array.isArray(data)) {
|
|
101
|
+
results.push(...data);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
if (items.length <= BULK_UPLOAD_CHUNK_SIZE) {
|
|
105
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose(`Creating ${bold(items.length.toString())} launch categories…`);
|
|
106
|
+
await uploadChunk(items, 0, 1);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const chunks = chunk(items, BULK_UPLOAD_CHUNK_SIZE);
|
|
110
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose(`Creating ${bold(items.length.toString())} launch categories in ${bold(chunks.length.toString())} request(s)…`);
|
|
111
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
112
|
+
await uploadChunk(chunks[i], i, chunks.length);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
55
117
|
async issueOauthToken() {
|
|
118
|
+
const base = __classPrivateFieldGet(this, _TestOpsClient_client, "f").defaults.baseURL;
|
|
119
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(`Endpoint: ${base}`);
|
|
120
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Issuing OAuth token…");
|
|
56
121
|
const formData = new FormData();
|
|
57
122
|
formData.append("grant_type", "apitoken");
|
|
58
123
|
formData.append("scope", "openid");
|
|
59
124
|
formData.append("token", __classPrivateFieldGet(this, _TestOpsClient_accessToken, "f"));
|
|
60
125
|
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/uaa/oauth/token", formData);
|
|
61
126
|
__classPrivateFieldSet(this, _TestOpsClient_oauthToken, data.access_token, "f");
|
|
127
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("OAuth token received");
|
|
62
128
|
}
|
|
63
129
|
async startUpload(ci) {
|
|
64
130
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
65
131
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
66
132
|
}
|
|
133
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose(`Starting CI upload (${ci.type})…`);
|
|
67
134
|
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/start", {
|
|
68
135
|
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
69
136
|
ci: {
|
|
@@ -79,12 +146,9 @@ export class TestOpsClient {
|
|
|
79
146
|
launch: {
|
|
80
147
|
id: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
81
148
|
},
|
|
82
|
-
}, {
|
|
83
|
-
headers: {
|
|
84
|
-
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
85
|
-
},
|
|
86
149
|
});
|
|
87
150
|
__classPrivateFieldSet(this, _TestOpsClient_uploadInProgress, true, "f");
|
|
151
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("CI upload started");
|
|
88
152
|
}
|
|
89
153
|
async stopUpload(ci, status) {
|
|
90
154
|
if (!__classPrivateFieldGet(this, _TestOpsClient_uploadInProgress, "f")) {
|
|
@@ -95,96 +159,283 @@ export class TestOpsClient {
|
|
|
95
159
|
jobUid: ci.jobUid,
|
|
96
160
|
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
97
161
|
status,
|
|
98
|
-
}, {
|
|
99
|
-
headers: {
|
|
100
|
-
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
101
|
-
},
|
|
102
162
|
});
|
|
103
163
|
__classPrivateFieldSet(this, _TestOpsClient_uploadInProgress, false, "f");
|
|
164
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose(`CI upload stopped (status: ${status})`);
|
|
104
165
|
}
|
|
105
166
|
async createLaunch(launchName, launchTags) {
|
|
167
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Creating launch…");
|
|
106
168
|
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch", {
|
|
107
169
|
name: launchName,
|
|
108
170
|
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
109
171
|
autoclose: true,
|
|
110
172
|
external: true,
|
|
111
173
|
tags: launchTags.map((tag) => ({ name: tag })),
|
|
112
|
-
}, {
|
|
113
|
-
headers: {
|
|
114
|
-
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
115
|
-
},
|
|
116
174
|
});
|
|
117
175
|
__classPrivateFieldSet(this, _TestOpsClient_launch, data, "f");
|
|
176
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(`Launch created: id=${bold(data.id.toString())}`);
|
|
118
177
|
}
|
|
119
178
|
async createSession(environment = {}) {
|
|
120
179
|
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
121
180
|
throw new Error("Launch isn't created! Call createLaunch first");
|
|
122
181
|
}
|
|
123
|
-
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/session
|
|
182
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/session", {
|
|
124
183
|
launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
125
|
-
environment: Object.entries(environment).map(([key, value]) => ({
|
|
184
|
+
environment: Object.entries(environment).map(([key, value]) => ({
|
|
185
|
+
key,
|
|
186
|
+
value: String(value),
|
|
187
|
+
})),
|
|
126
188
|
}, {
|
|
127
|
-
|
|
128
|
-
|
|
189
|
+
params: {
|
|
190
|
+
manual: "true",
|
|
129
191
|
},
|
|
130
192
|
});
|
|
131
193
|
__classPrivateFieldSet(this, _TestOpsClient_session, data, "f");
|
|
132
194
|
}
|
|
195
|
+
get namedEnvs() {
|
|
196
|
+
return __classPrivateFieldGet(this, _TestOpsClient_namedEnvsIdsByEnv, "f").values();
|
|
197
|
+
}
|
|
198
|
+
getNamedEnvFor(id) {
|
|
199
|
+
for (const [, env] of __classPrivateFieldGet(this, _TestOpsClient_namedEnvsIdsByEnv, "f")) {
|
|
200
|
+
if (env.externalId === id) {
|
|
201
|
+
return env;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
async createNamedEnvs(environments, onProgress) {
|
|
207
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
208
|
+
throw new Error("Session isn't created! Call createSession first");
|
|
209
|
+
}
|
|
210
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
211
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
212
|
+
}
|
|
213
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/named-env/bulk", {
|
|
214
|
+
launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
215
|
+
items: environments.map(({ id, name }) => ({
|
|
216
|
+
externalId: id,
|
|
217
|
+
name,
|
|
218
|
+
})),
|
|
219
|
+
}, {
|
|
220
|
+
headers: {
|
|
221
|
+
"Content-Type": "application/json",
|
|
222
|
+
},
|
|
223
|
+
onUploadProgress(progressEvent) {
|
|
224
|
+
const total = progressEvent.total ?? 100;
|
|
225
|
+
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
226
|
+
onProgress?.(percent, total);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
const namesById = new Map(environments.map(({ id, name }) => [id, name]));
|
|
230
|
+
data.forEach((env) => {
|
|
231
|
+
__classPrivateFieldGet(this, _TestOpsClient_namedEnvsIdsByEnv, "f").set(env.externalId, {
|
|
232
|
+
...env,
|
|
233
|
+
name: namesById.get(env.externalId) ?? env.externalId,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async uploadGlobalAttachments(params) {
|
|
238
|
+
const { attachments, attachmentsResolver, onProgress } = params;
|
|
239
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
240
|
+
throw new Error("Session isn't created! Call createSession first");
|
|
241
|
+
}
|
|
242
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
243
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
244
|
+
}
|
|
245
|
+
const formData = new FormData();
|
|
246
|
+
for (const attachmentLink of attachments) {
|
|
247
|
+
const attachment = await attachmentsResolver(attachmentLink);
|
|
248
|
+
if (!attachment) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
formData.append("file", attachment.content, {
|
|
252
|
+
filename: attachment.originalFileName,
|
|
253
|
+
contentType: attachment.contentType,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/attachment", formData, {
|
|
257
|
+
onUploadProgress(progressEvent) {
|
|
258
|
+
const total = progressEvent.total ?? 100;
|
|
259
|
+
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
260
|
+
onProgress?.(percent, total);
|
|
261
|
+
},
|
|
262
|
+
params: { launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id },
|
|
263
|
+
headers: formData.getHeaders(),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async uploadGlobalErrors(errors, onProgress) {
|
|
267
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
268
|
+
throw new Error("Session isn't created! Call createSession first");
|
|
269
|
+
}
|
|
270
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
271
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
272
|
+
}
|
|
273
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/error/bulk", {
|
|
274
|
+
launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
275
|
+
items: errors,
|
|
276
|
+
}, {
|
|
277
|
+
onUploadProgress(progressEvent) {
|
|
278
|
+
const total = progressEvent.total ?? 100;
|
|
279
|
+
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
280
|
+
onProgress?.(percent, total);
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
133
284
|
async uploadTestResults(params) {
|
|
134
285
|
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
135
286
|
throw new Error("Session isn't created! Call createSession first");
|
|
136
287
|
}
|
|
137
|
-
const { trs, attachmentsResolver, fixturesResolver, onProgress } = params;
|
|
138
|
-
const trsChunks = chunk(trs,
|
|
288
|
+
const { trs, environments, attachmentsResolver, fixturesResolver, onProgress } = params;
|
|
289
|
+
const trsChunks = chunk(trs, CHUNK_SIZE);
|
|
139
290
|
const uploadLimitFn = pLimit(__classPrivateFieldGet(this, _TestOpsClient_uploadLimit, "f"));
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
const trsTestOpsIdsByUuid = data.results.reduce((acc, { id, uuid }) => ({ ...acc, [uuid]: id }), {});
|
|
154
|
-
await Promise.all(trsChunk.map((tr) => uploadLimitFn(async () => {
|
|
155
|
-
const trTestOpsId = trsTestOpsIdsByUuid[tr.id];
|
|
156
|
-
const attachments = await attachmentsResolver(tr);
|
|
157
|
-
const fixtures = await fixturesResolver(tr);
|
|
158
|
-
if (attachments.length > 0) {
|
|
159
|
-
const attachmentsChunks = chunk(attachments, 100);
|
|
160
|
-
await Promise.all(attachmentsChunks.map(async (attachmentsChunk) => {
|
|
161
|
-
const formData = new FormData();
|
|
162
|
-
attachmentsChunk.forEach((attachment) => {
|
|
163
|
-
formData.append("file", attachment.content, {
|
|
164
|
-
filename: attachment.originalFileName,
|
|
165
|
-
contentType: attachment.contentType,
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${trTestOpsId}/attachment`, formData, {
|
|
169
|
-
headers: {
|
|
170
|
-
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
171
|
-
...formData.getHeaders(),
|
|
172
|
-
},
|
|
291
|
+
const uploadedTrs = [];
|
|
292
|
+
const envNamesById = new Map(environments.map(({ id, name }) => [id, name]));
|
|
293
|
+
try {
|
|
294
|
+
for (const trsChunk of trsChunks) {
|
|
295
|
+
const chunkEnvs = new Map();
|
|
296
|
+
for (const tr of trsChunk) {
|
|
297
|
+
const environmentId = tr.environment;
|
|
298
|
+
if (environmentId && !__classPrivateFieldGet(this, _TestOpsClient_namedEnvsIdsByEnv, "f").has(environmentId)) {
|
|
299
|
+
chunkEnvs.set(environmentId, {
|
|
300
|
+
id: environmentId,
|
|
301
|
+
name: envNamesById.get(environmentId) ?? environmentId,
|
|
173
302
|
});
|
|
174
|
-
}
|
|
303
|
+
}
|
|
175
304
|
}
|
|
176
|
-
if (
|
|
177
|
-
await
|
|
178
|
-
fixtures,
|
|
179
|
-
}, {
|
|
180
|
-
headers: {
|
|
181
|
-
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
182
|
-
},
|
|
183
|
-
});
|
|
305
|
+
if (chunkEnvs.size > 0) {
|
|
306
|
+
await this.createNamedEnvs(Array.from(chunkEnvs.values()));
|
|
184
307
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
308
|
+
const reportIdsToTestOpsIds = await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_postTestResultsChunk).call(this, trsChunk);
|
|
309
|
+
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadChunkAttachmentsAndFixtures).call(this, trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress);
|
|
310
|
+
uploadedTrs.push(...trsChunk);
|
|
311
|
+
}
|
|
312
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").verbose("Test results upload completed");
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
if (this.isTestOpsClientError(error)) {
|
|
316
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error(`Failed to upload test results: ${error.response?.data.message}`);
|
|
317
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").debug(error.response.data);
|
|
318
|
+
}
|
|
319
|
+
else if (error instanceof Error) {
|
|
320
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error(`Failed to upload test results: ${error.message}`);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error("Failed to upload test results");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return uploadedTrs;
|
|
327
|
+
}
|
|
328
|
+
async uploadQualityGateResults(results, onProgress) {
|
|
329
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
330
|
+
throw new Error("Session isn't created! Call createSession first");
|
|
331
|
+
}
|
|
332
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
333
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
334
|
+
}
|
|
335
|
+
const items = results.map((result) => {
|
|
336
|
+
const item = {
|
|
337
|
+
name: result.rule,
|
|
338
|
+
message: result.message,
|
|
339
|
+
};
|
|
340
|
+
const namedEnvId = !!result.environment && this.getNamedEnvFor(result.environment)?.id;
|
|
341
|
+
if (typeof namedEnvId === "number") {
|
|
342
|
+
item.namedEnvId = namedEnvId;
|
|
343
|
+
}
|
|
344
|
+
return item;
|
|
345
|
+
});
|
|
346
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch/quality-gate/bulk", {
|
|
347
|
+
launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
348
|
+
items,
|
|
349
|
+
}, {
|
|
350
|
+
onUploadProgress(progressEvent) {
|
|
351
|
+
const total = progressEvent.total ?? 100;
|
|
352
|
+
const percent = total > 0 ? Math.min(100, Math.max(0, (progressEvent.loaded / total) * 100)) : 0;
|
|
353
|
+
onProgress?.(percent, total);
|
|
354
|
+
},
|
|
355
|
+
});
|
|
188
356
|
}
|
|
189
357
|
}
|
|
190
|
-
_TestOpsClient_accessToken = new WeakMap(), _TestOpsClient_projectId = new WeakMap(), _TestOpsClient_oauthToken = new WeakMap(), _TestOpsClient_client = new WeakMap(), _TestOpsClient_launch = new WeakMap(), _TestOpsClient_session = new WeakMap(), _TestOpsClient_uploadInProgress = new WeakMap(), _TestOpsClient_uploadLimit = new WeakMap()
|
|
358
|
+
_TestOpsClient_logger = new WeakMap(), _TestOpsClient_accessToken = new WeakMap(), _TestOpsClient_projectId = new WeakMap(), _TestOpsClient_oauthToken = new WeakMap(), _TestOpsClient_client = new WeakMap(), _TestOpsClient_launch = new WeakMap(), _TestOpsClient_session = new WeakMap(), _TestOpsClient_uploadInProgress = new WeakMap(), _TestOpsClient_uploadLimit = new WeakMap(), _TestOpsClient_namedEnvsIdsByEnv = new WeakMap(), _TestOpsClient_instances = new WeakSet(), _TestOpsClient_postTestResultsChunk = async function _TestOpsClient_postTestResultsChunk(trsChunk) {
|
|
359
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/test-result", {
|
|
360
|
+
testSessionId: __classPrivateFieldGet(this, _TestOpsClient_session, "f").id,
|
|
361
|
+
results: trsChunk.map((testResult) => {
|
|
362
|
+
const extendedTestResult = {
|
|
363
|
+
...testResult,
|
|
364
|
+
uuid: testResult.id,
|
|
365
|
+
};
|
|
366
|
+
const namedEnvironment = !!testResult.environment && this.getNamedEnvFor(testResult.environment);
|
|
367
|
+
if (namedEnvironment) {
|
|
368
|
+
extendedTestResult.namedEnv = { id: namedEnvironment.id };
|
|
369
|
+
}
|
|
370
|
+
if (typeof extendedTestResult.category?.externalId === "string" ||
|
|
371
|
+
typeof extendedTestResult.category?.externalId === "number") {
|
|
372
|
+
const category = extendedTestResult.category;
|
|
373
|
+
extendedTestResult.category = {
|
|
374
|
+
externalId: category.externalId,
|
|
375
|
+
};
|
|
376
|
+
if (category.grouping && category.grouping.length > 0) {
|
|
377
|
+
extendedTestResult.category.grouping = category.grouping;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const error = extendedTestResult.error;
|
|
381
|
+
if (typeof error?.message === "string") {
|
|
382
|
+
extendedTestResult.message = error.message;
|
|
383
|
+
}
|
|
384
|
+
if (typeof error?.trace === "string") {
|
|
385
|
+
extendedTestResult.trace = error.trace;
|
|
386
|
+
}
|
|
387
|
+
return extendedTestResult;
|
|
388
|
+
}),
|
|
389
|
+
}, { headers: { "Content-Type": "application/json" } });
|
|
390
|
+
const reportIdsToTestOpsIds = {};
|
|
391
|
+
for (const { id, uuid } of data.results ?? []) {
|
|
392
|
+
reportIdsToTestOpsIds[uuid] = id;
|
|
393
|
+
}
|
|
394
|
+
return reportIdsToTestOpsIds;
|
|
395
|
+
}, _TestOpsClient_uploadChunkAttachmentsAndFixtures = async function _TestOpsClient_uploadChunkAttachmentsAndFixtures(trsChunk, reportIdsToTestOpsIds, attachmentsResolver, fixturesResolver, uploadLimitFn, onProgress) {
|
|
396
|
+
await Promise.all(trsChunk.map((tr) => uploadLimitFn(async () => {
|
|
397
|
+
const testOpsId = reportIdsToTestOpsIds[tr.id];
|
|
398
|
+
const attachments = await attachmentsResolver(tr);
|
|
399
|
+
const fixtures = await fixturesResolver(tr);
|
|
400
|
+
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadAttachmentsForResult).call(this, testOpsId, attachments);
|
|
401
|
+
await __classPrivateFieldGet(this, _TestOpsClient_instances, "m", _TestOpsClient_uploadFixturesForResult).call(this, testOpsId, fixtures);
|
|
402
|
+
onProgress?.();
|
|
403
|
+
})));
|
|
404
|
+
}, _TestOpsClient_uploadAttachmentsForResult = async function _TestOpsClient_uploadAttachmentsForResult(testOpsResultId, attachments) {
|
|
405
|
+
if (attachments.length === 0) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const attachmentsChunks = chunk(attachments, 100);
|
|
409
|
+
for (const attachmentsChunk of attachmentsChunks) {
|
|
410
|
+
const formData = new FormData();
|
|
411
|
+
for (const att of attachmentsChunk) {
|
|
412
|
+
formData.append("file", att.content, {
|
|
413
|
+
filename: att.originalFileName,
|
|
414
|
+
contentType: att.contentType,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${testOpsResultId}/attachment`, formData, {
|
|
419
|
+
headers: formData.getHeaders(),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
if (this.isTestOpsClientError(error)) {
|
|
424
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error(`Failed to upload attachments for result ${testOpsResultId}: ${error.response?.data.message}`);
|
|
425
|
+
}
|
|
426
|
+
else if (error instanceof Error) {
|
|
427
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error(`Failed to upload attachments for result ${testOpsResultId}: ${error.message}`);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").error(`Failed to upload attachments for result ${testOpsResultId}`);
|
|
431
|
+
}
|
|
432
|
+
__classPrivateFieldGet(this, _TestOpsClient_logger, "f").inspect(formData);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}, _TestOpsClient_uploadFixturesForResult = async function _TestOpsClient_uploadFixturesForResult(testOpsResultId, fixtures) {
|
|
436
|
+
if (fixtures.length === 0)
|
|
437
|
+
return;
|
|
438
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${testOpsResultId}/test-fixture-result`, {
|
|
439
|
+
fixtures,
|
|
440
|
+
});
|
|
441
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export {
|
|
1
|
+
export type { TestOpsPluginOptions as TestOpsUploaderPluginOptions } from "./model.js";
|
|
2
|
+
export { TestOpsPlugin as default } from "./plugin.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { TestOpsPlugin as default } from "./plugin.js";
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import ProgressBar from "progress";
|
|
2
|
+
type JSONMessage = Record<string, unknown> | Array<unknown>;
|
|
3
|
+
export declare class Logger {
|
|
4
|
+
#private;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
constructor(name: string);
|
|
7
|
+
progressBarCounter(message: string, total: number): ProgressBar | {
|
|
8
|
+
tick: () => void;
|
|
9
|
+
update: () => void;
|
|
10
|
+
terminate: () => void;
|
|
11
|
+
};
|
|
12
|
+
progressBar(message: string): ProgressBar | {
|
|
13
|
+
update: () => void;
|
|
14
|
+
terminate: () => void;
|
|
15
|
+
};
|
|
16
|
+
verbose(message: string | JSONMessage): void;
|
|
17
|
+
debug(message: string | JSONMessage): void;
|
|
18
|
+
newLine(): void;
|
|
19
|
+
info(message: string | JSONMessage): void;
|
|
20
|
+
warn(message: string | JSONMessage): void;
|
|
21
|
+
error(message: string | JSONMessage): void;
|
|
22
|
+
inspect(value: unknown): void;
|
|
23
|
+
}
|
|
24
|
+
export {};
|