@allurereport/plugin-testops 3.15.0 → 3.16.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 +2 -0
- package/dist/client.d.ts +7 -3
- package/dist/client.js +182 -109
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +134 -0
- package/dist/gitFlow/options.d.ts +1 -0
- package/dist/gitFlow/options.js +1 -1
- package/dist/logger.d.ts +3 -1
- package/dist/logger.js +6 -2
- package/dist/model.d.ts +26 -1
- package/dist/plugin.js +175 -160
- package/dist/uploadPacer.d.ts +12 -0
- package/dist/uploadPacer.js +76 -0
- package/dist/uploadQueue.d.ts +15 -0
- package/dist/uploadQueue.js +61 -0
- package/dist/utils/ciOverrides.d.ts +2 -0
- package/dist/utils/ciOverrides.js +20 -0
- package/dist/utils/httpRetry.d.ts +4 -0
- package/dist/utils/httpRetry.js +62 -0
- package/dist/utils/jobParameters.d.ts +15 -0
- package/dist/utils/jobParameters.js +39 -0
- package/dist/utils/launchCategories.d.ts +7 -0
- package/dist/utils/launchCategories.js +97 -0
- package/dist/utils/options.d.ts +1 -0
- package/dist/utils/options.js +14 -2
- package/dist/utils/resolvers.js +1 -0
- package/dist/utils/testResults.js +0 -1
- package/package.json +8 -8
package/dist/errors.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { KnownError, UnknownError } from "@allurereport/service";
|
|
3
|
+
import { isAxiosError } from "axios";
|
|
4
|
+
import { retryAfterMs } from "./utils/httpRetry.js";
|
|
5
|
+
export var ErrorKind;
|
|
6
|
+
(function (ErrorKind) {
|
|
7
|
+
ErrorKind["None"] = "none";
|
|
8
|
+
ErrorKind["ServiceTransient"] = "service_transient";
|
|
9
|
+
ErrorKind["ResourceRecoverable"] = "resource_recoverable";
|
|
10
|
+
ErrorKind["AuthTerminal"] = "auth_terminal";
|
|
11
|
+
ErrorKind["NotFoundTerminal"] = "not_found_terminal";
|
|
12
|
+
ErrorKind["PayloadTerminal"] = "payload_terminal";
|
|
13
|
+
ErrorKind["ValidationTerminal"] = "validation_terminal";
|
|
14
|
+
ErrorKind["ConflictTerminal"] = "conflict_terminal";
|
|
15
|
+
ErrorKind["Unknown"] = "unknown";
|
|
16
|
+
})(ErrorKind || (ErrorKind = {}));
|
|
17
|
+
const isLaunchClosedMessage = (message) => {
|
|
18
|
+
if (!message) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const lower = message.toLowerCase();
|
|
22
|
+
return lower.includes("launch is closed") || lower.includes("closed launch");
|
|
23
|
+
};
|
|
24
|
+
const responseStatus = (error) => isAxiosError(error) ? error.response?.status : undefined;
|
|
25
|
+
const responseMessage = (error) => {
|
|
26
|
+
if (!isAxiosError(error)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const data = error.response?.data;
|
|
30
|
+
return data?.message;
|
|
31
|
+
};
|
|
32
|
+
const classifyHttpStatus = (status) => {
|
|
33
|
+
if (status === 408 || status === 429 || status >= 500) {
|
|
34
|
+
return ErrorKind.ServiceTransient;
|
|
35
|
+
}
|
|
36
|
+
if (status === 423) {
|
|
37
|
+
return ErrorKind.ResourceRecoverable;
|
|
38
|
+
}
|
|
39
|
+
if (status === 401 || status === 403) {
|
|
40
|
+
return ErrorKind.AuthTerminal;
|
|
41
|
+
}
|
|
42
|
+
if (status === 404) {
|
|
43
|
+
return ErrorKind.NotFoundTerminal;
|
|
44
|
+
}
|
|
45
|
+
if (status === 413 || status === 415) {
|
|
46
|
+
return ErrorKind.PayloadTerminal;
|
|
47
|
+
}
|
|
48
|
+
if (status === 409) {
|
|
49
|
+
return ErrorKind.ConflictTerminal;
|
|
50
|
+
}
|
|
51
|
+
if (status >= 400 && status < 500) {
|
|
52
|
+
return ErrorKind.ValidationTerminal;
|
|
53
|
+
}
|
|
54
|
+
return ErrorKind.Unknown;
|
|
55
|
+
};
|
|
56
|
+
export const isClosedLaunchError = (error) => {
|
|
57
|
+
if (isAxiosError(error)) {
|
|
58
|
+
return isLaunchClosedMessage(responseMessage(error));
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof KnownError || error instanceof UnknownError) {
|
|
61
|
+
return isLaunchClosedMessage(error.message);
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
};
|
|
65
|
+
const isCanceledError = (error) => isAxiosError(error) && (error.code === "ERR_CANCELED" || error.name === "CanceledError");
|
|
66
|
+
export const classifyError = (error) => {
|
|
67
|
+
if (!error) {
|
|
68
|
+
return ErrorKind.None;
|
|
69
|
+
}
|
|
70
|
+
if (isCanceledError(error)) {
|
|
71
|
+
return ErrorKind.Unknown;
|
|
72
|
+
}
|
|
73
|
+
if (isAxiosError(error)) {
|
|
74
|
+
if (isLaunchClosedMessage(responseMessage(error))) {
|
|
75
|
+
return ErrorKind.ResourceRecoverable;
|
|
76
|
+
}
|
|
77
|
+
const status = responseStatus(error);
|
|
78
|
+
return status === undefined ? ErrorKind.ServiceTransient : classifyHttpStatus(status);
|
|
79
|
+
}
|
|
80
|
+
if (error instanceof KnownError) {
|
|
81
|
+
if (isLaunchClosedMessage(error.message)) {
|
|
82
|
+
return ErrorKind.ResourceRecoverable;
|
|
83
|
+
}
|
|
84
|
+
return typeof error.status === "number" ? classifyHttpStatus(error.status) : ErrorKind.Unknown;
|
|
85
|
+
}
|
|
86
|
+
if (error instanceof UnknownError) {
|
|
87
|
+
if (isLaunchClosedMessage(error.message)) {
|
|
88
|
+
return ErrorKind.ResourceRecoverable;
|
|
89
|
+
}
|
|
90
|
+
return ErrorKind.ServiceTransient;
|
|
91
|
+
}
|
|
92
|
+
if (error instanceof Error && (error.name === "AbortError" || /timeout/i.test(error.message))) {
|
|
93
|
+
return ErrorKind.ServiceTransient;
|
|
94
|
+
}
|
|
95
|
+
return ErrorKind.Unknown;
|
|
96
|
+
};
|
|
97
|
+
export const shouldRetryUpload = (error) => {
|
|
98
|
+
const kind = classifyError(error);
|
|
99
|
+
return kind === ErrorKind.ServiceTransient || kind === ErrorKind.ResourceRecoverable;
|
|
100
|
+
};
|
|
101
|
+
export const isPermanentUploadError = (error) => {
|
|
102
|
+
switch (classifyError(error)) {
|
|
103
|
+
case ErrorKind.AuthTerminal:
|
|
104
|
+
case ErrorKind.NotFoundTerminal:
|
|
105
|
+
case ErrorKind.PayloadTerminal:
|
|
106
|
+
case ErrorKind.ValidationTerminal:
|
|
107
|
+
case ErrorKind.ConflictTerminal:
|
|
108
|
+
return true;
|
|
109
|
+
default:
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
export const isTerminalUploadError = (error, retries, maxRetries) => isPermanentUploadError(error) || retries >= maxRetries;
|
|
114
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
115
|
+
const DEFAULT_BASE_DELAY_MS = 500;
|
|
116
|
+
const DEFAULT_MAX_DELAY_MS = 5000;
|
|
117
|
+
export const withUploadRetry = async (operation, options = {}) => {
|
|
118
|
+
const { maxRetries = DEFAULT_MAX_RETRIES, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, onRetry, } = options;
|
|
119
|
+
let attempt = 0;
|
|
120
|
+
for (;;) {
|
|
121
|
+
try {
|
|
122
|
+
return await operation();
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (!shouldRetryUpload(error) || attempt >= maxRetries) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
attempt += 1;
|
|
129
|
+
await onRetry?.(error, attempt);
|
|
130
|
+
const backoffMs = retryAfterMs(error) ?? Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
|
|
131
|
+
await delay(backoffMs);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { TestOpsPluginOptions } from "../model.js";
|
|
2
2
|
import type { ResolvedGitFlowOptions } from "./types.js";
|
|
3
|
+
export declare const parseEnvBool: (value?: string) => boolean | undefined;
|
|
3
4
|
export declare const resolveGitFlowOptions: (options: TestOpsPluginOptions) => ResolvedGitFlowOptions;
|
package/dist/gitFlow/options.js
CHANGED
package/dist/logger.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
type LogLevel = "silent" | "verbose" | "debug" | "info" | "warn" | "error";
|
|
1
2
|
type JSONMessage = Record<string, unknown> | Array<unknown>;
|
|
2
3
|
export declare class Logger {
|
|
3
4
|
#private;
|
|
4
5
|
private readonly loggerName;
|
|
5
|
-
constructor(loggerName: string);
|
|
6
|
+
constructor(loggerName: string, logLevel?: LogLevel);
|
|
7
|
+
setLogLevel(logLevel: LogLevel): void;
|
|
6
8
|
verbose(message: string | JSONMessage): void;
|
|
7
9
|
debug(message: string | JSONMessage): void;
|
|
8
10
|
newLine(): void;
|
package/dist/logger.js
CHANGED
|
@@ -35,13 +35,17 @@ function getLogLevelFromEnv() {
|
|
|
35
35
|
return "info";
|
|
36
36
|
}
|
|
37
37
|
export class Logger {
|
|
38
|
-
constructor(loggerName) {
|
|
38
|
+
constructor(loggerName, logLevel) {
|
|
39
39
|
_Logger_instances.add(this);
|
|
40
40
|
this.loggerName = loggerName;
|
|
41
|
-
_Logger_level.set(this,
|
|
41
|
+
_Logger_level.set(this, void 0);
|
|
42
42
|
_Logger_prefix.set(this, void 0);
|
|
43
|
+
__classPrivateFieldSet(this, _Logger_level, logLevel ?? getLogLevelFromEnv(), "f");
|
|
43
44
|
__classPrivateFieldSet(this, _Logger_prefix, cyan(bold(`[${this.loggerName}]:`)), "f");
|
|
44
45
|
}
|
|
46
|
+
setLogLevel(logLevel) {
|
|
47
|
+
__classPrivateFieldSet(this, _Logger_level, logLevel, "f");
|
|
48
|
+
}
|
|
45
49
|
verbose(message) {
|
|
46
50
|
if (!__classPrivateFieldGet(this, _Logger_instances, "m", _Logger_isLogLevel).call(this, "verbose")) {
|
|
47
51
|
return;
|
package/dist/model.d.ts
CHANGED
|
@@ -107,7 +107,6 @@ export type UploadTestResultDto = {
|
|
|
107
107
|
trace?: string;
|
|
108
108
|
hostId?: string;
|
|
109
109
|
threadId?: string;
|
|
110
|
-
environment?: string;
|
|
111
110
|
category?: UploadTestResultCategoryDto;
|
|
112
111
|
namedEnv?: UploadTestResultNamedEnvDto;
|
|
113
112
|
steps?: UploadTestResultStepDto[];
|
|
@@ -173,16 +172,24 @@ export type LaunchCategoryBulkResult = {
|
|
|
173
172
|
id: number;
|
|
174
173
|
externalId: string;
|
|
175
174
|
};
|
|
175
|
+
export type UploadRateLimit = {
|
|
176
|
+
windowMs: number;
|
|
177
|
+
maxRequestsPerWindow?: number;
|
|
178
|
+
maxFilesPerWindow?: number;
|
|
179
|
+
maxBytesPerWindow?: number;
|
|
180
|
+
};
|
|
176
181
|
export type TestOpsClientParams = {
|
|
177
182
|
baseUrl: string;
|
|
178
183
|
projectId: string;
|
|
179
184
|
accessToken: string;
|
|
180
185
|
limit?: number;
|
|
186
|
+
uploadRateLimit?: UploadRateLimit | false;
|
|
181
187
|
};
|
|
182
188
|
export type AttachmentForUpload = {
|
|
183
189
|
originalFileName: string;
|
|
184
190
|
contentType: string;
|
|
185
191
|
content: Buffer | Blob | ReadableStream | Readable;
|
|
192
|
+
contentLength?: number;
|
|
186
193
|
};
|
|
187
194
|
export type FixtureResolver = (tr: TestResult) => Promise<TestOpsFixtureResult[]>;
|
|
188
195
|
export type AttachmentsResolver = (tr: TestResult) => Promise<AttachmentForUpload[] | AttachmentForUpload>;
|
|
@@ -193,10 +200,13 @@ export type TestOpsUploaderOptions = {
|
|
|
193
200
|
launchName: string;
|
|
194
201
|
launchTags: string[];
|
|
195
202
|
autocloseLaunch?: boolean;
|
|
203
|
+
reopenClosedLaunch?: boolean;
|
|
204
|
+
launchId?: number;
|
|
196
205
|
gitFlow?: boolean;
|
|
197
206
|
ancestorLimit?: number;
|
|
198
207
|
filter?: (testResult: TestResult) => boolean;
|
|
199
208
|
limit?: number;
|
|
209
|
+
uploadRateLimit?: UploadRateLimit | false;
|
|
200
210
|
};
|
|
201
211
|
export interface TestOpsFixtureResult extends Omit<TestFixtureResult, "type"> {
|
|
202
212
|
type: "BEFORE" | "AFTER";
|
|
@@ -210,6 +220,21 @@ export type TestOpsSession = {
|
|
|
210
220
|
launchId: number;
|
|
211
221
|
projectId: number;
|
|
212
222
|
};
|
|
223
|
+
export type ExternalRunStartResponse = {
|
|
224
|
+
projectId: number;
|
|
225
|
+
launchId: number;
|
|
226
|
+
jobId?: number;
|
|
227
|
+
jobRunId?: number;
|
|
228
|
+
};
|
|
229
|
+
export type TestOpsJobParameter = {
|
|
230
|
+
name: string;
|
|
231
|
+
defaultValue: string;
|
|
232
|
+
};
|
|
233
|
+
export type TestOpsJob = {
|
|
234
|
+
id: number;
|
|
235
|
+
externalId: string;
|
|
236
|
+
parameters: TestOpsJobParameter[];
|
|
237
|
+
};
|
|
213
238
|
export type TestOpsLaunch = {
|
|
214
239
|
id: number;
|
|
215
240
|
name: string;
|