testit-js-commons 4.0.5 → 4.1.0

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.
@@ -12,6 +12,7 @@ export interface CliOptions {
12
12
  tmsAutomaticUpdationLinksToTestCases: boolean;
13
13
  tmsSyncStorageEnabled: boolean;
14
14
  tmsSyncStoragePort: string;
15
+ tmsImportRealtime: boolean;
15
16
  }
16
17
  export interface EnvironmentOptions {
17
18
  TMS_URL: string;
@@ -27,6 +28,7 @@ export interface EnvironmentOptions {
27
28
  TMS_CERT_VALIDATION: string;
28
29
  TMS_SYNC_STORAGE_ENABLED: string;
29
30
  TMS_SYNC_STORAGE_PORT: string;
31
+ TMS_IMPORT_REALTIME: string;
30
32
  }
31
33
  export interface ProcessEnvOptions {
32
34
  TMS_URL?: string;
@@ -42,6 +44,7 @@ export interface ProcessEnvOptions {
42
44
  TMS_CERT_VALIDATION?: string;
43
45
  TMS_SYNC_STORAGE_ENABLED?: string;
44
46
  TMS_SYNC_STORAGE_PORT?: string;
47
+ TMS_IMPORT_REALTIME?: string;
45
48
  }
46
49
  export interface AdapterConfig {
47
50
  url: string;
@@ -56,4 +59,5 @@ export interface AdapterConfig {
56
59
  certValidation?: boolean;
57
60
  syncStorageEnabled?: boolean;
58
61
  syncStoragePort?: string;
62
+ importRealtime?: boolean;
59
63
  }
@@ -0,0 +1,29 @@
1
+ export type HttpErrorLike = {
2
+ code?: string;
3
+ errno?: number;
4
+ status?: number;
5
+ statusCode?: number;
6
+ response?: {
7
+ status?: number;
8
+ };
9
+ error?: unknown;
10
+ cause?: unknown;
11
+ };
12
+ export type HttpRetryOptions = {
13
+ maxAttempts?: number;
14
+ delayMs?: number;
15
+ /** When true, delay is `delayMs * attempt` (1-based). */
16
+ backoff?: boolean;
17
+ /** Shown in debug logs when a retry happens. */
18
+ label?: string;
19
+ };
20
+ /** Unwrap nested `error` / `cause` from API client wrappers. */
21
+ export declare function unwrapHttpError(err: unknown): HttpErrorLike | null;
22
+ export declare function getHttpStatus(err: HttpErrorLike): number | undefined;
23
+ export declare function getNetworkErrorCode(err: HttpErrorLike): string | undefined;
24
+ /**
25
+ * Retry only transient failures: HTTP 5xx and known network error codes.
26
+ * Does not retry HTTP 4xx (client/validation errors).
27
+ */
28
+ export declare function isRetryableHttpError(err: unknown): boolean;
29
+ export declare function withHttpRetry<T>(fn: () => Promise<T>, options?: HttpRetryOptions): Promise<T>;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.unwrapHttpError = unwrapHttpError;
16
+ exports.getHttpStatus = getHttpStatus;
17
+ exports.getNetworkErrorCode = getNetworkErrorCode;
18
+ exports.isRetryableHttpError = isRetryableHttpError;
19
+ exports.withHttpRetry = withHttpRetry;
20
+ const logger_1 = __importDefault(require("../../logger"));
21
+ /** Node/network error codes that are safe to retry. */
22
+ const TRANSIENT_NETWORK_CODES = new Set(["ECONNRESET",
23
+ "ETIMEDOUT",
24
+ "EPIPE",
25
+ "ECONNABORTED",
26
+ "ECONNREFUSED",
27
+ "EHOSTUNREACH",
28
+ "ENETUNREACH",
29
+ "EAI_AGAIN",
30
+ ]);
31
+ /** Windows errno for ECONNRESET. */
32
+ const ERRNO_ECONNRESET = -104;
33
+ function sleep(ms) {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ function isErrorLike(value) {
37
+ return value !== null && typeof value === "object";
38
+ }
39
+ /** Unwrap nested `error` / `cause` from API client wrappers. */
40
+ function unwrapHttpError(err) {
41
+ if (!isErrorLike(err)) {
42
+ return null;
43
+ }
44
+ const nested = err.error;
45
+ if (isErrorLike(nested)) {
46
+ return nested;
47
+ }
48
+ const cause = err.cause;
49
+ if (isErrorLike(cause)) {
50
+ return cause;
51
+ }
52
+ return err;
53
+ }
54
+ function getHttpStatus(err) {
55
+ var _a, _b, _c;
56
+ const status = (_b = (_a = err.status) !== null && _a !== void 0 ? _a : err.statusCode) !== null && _b !== void 0 ? _b : (_c = err.response) === null || _c === void 0 ? void 0 : _c.status;
57
+ return typeof status === "number" ? status : undefined;
58
+ }
59
+ function getNetworkErrorCode(err) {
60
+ return typeof err.code === "string" ? err.code : undefined;
61
+ }
62
+ /**
63
+ * Retry only transient failures: HTTP 5xx and known network error codes.
64
+ * Does not retry HTTP 4xx (client/validation errors).
65
+ */
66
+ function isRetryableHttpError(err) {
67
+ const e = unwrapHttpError(err);
68
+ if (!e) {
69
+ return false;
70
+ }
71
+ const status = getHttpStatus(e);
72
+ if (status !== undefined) {
73
+ if (status >= 400 && status < 500) {
74
+ return false;
75
+ }
76
+ if (status >= 500) {
77
+ return true;
78
+ }
79
+ }
80
+ const code = getNetworkErrorCode(e);
81
+ if (code !== undefined && TRANSIENT_NETWORK_CODES.has(code)) {
82
+ return true;
83
+ }
84
+ if (e.errno === ERRNO_ECONNRESET) {
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+ function withHttpRetry(fn_1) {
90
+ return __awaiter(this, arguments, void 0, function* (fn, options = {}) {
91
+ var _a, _b, _c, _d, _e, _f;
92
+ const maxAttempts = (_a = options.maxAttempts) !== null && _a !== void 0 ? _a : 3;
93
+ const delayMs = (_b = options.delayMs) !== null && _b !== void 0 ? _b : 1000;
94
+ const backoff = (_c = options.backoff) !== null && _c !== void 0 ? _c : false;
95
+ const label = (_d = options.label) !== null && _d !== void 0 ? _d : "http";
96
+ let last;
97
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
98
+ try {
99
+ const value = yield fn();
100
+ if (attempt > 1) {
101
+ logger_1.default.debug(`[http-retry] ${label} succeeded on attempt ${attempt}/${maxAttempts}`);
102
+ }
103
+ return value;
104
+ }
105
+ catch (e) {
106
+ last = e;
107
+ if (!isRetryableHttpError(e) || attempt === maxAttempts) {
108
+ if (attempt > 1) {
109
+ logger_1.default.debug(`[http-retry] ${label} failed after ${attempt} attempt(s)`, {
110
+ retryable: isRetryableHttpError(e),
111
+ });
112
+ }
113
+ throw e;
114
+ }
115
+ const waitMs = backoff ? delayMs * attempt : delayMs;
116
+ logger_1.default.debug(`[http-retry] ${label} attempt ${attempt}/${maxAttempts} failed, retry in ${waitMs}ms`, {
117
+ status: getHttpStatus((_e = unwrapHttpError(e)) !== null && _e !== void 0 ? _e : {}),
118
+ code: getNetworkErrorCode((_f = unwrapHttpError(e)) !== null && _f !== void 0 ? _f : {}),
119
+ });
120
+ yield sleep(waitMs);
121
+ }
122
+ }
123
+ throw last;
124
+ });
125
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const http_retry_util_1 = require("./http-retry.util");
4
+ describe("isRetryableHttpError", () => {
5
+ it("retries on network error code", () => {
6
+ expect((0, http_retry_util_1.isRetryableHttpError)({ code: "ECONNRESET" })).toBe(true);
7
+ expect((0, http_retry_util_1.getNetworkErrorCode)({ code: "ECONNRESET" })).toBe("ECONNRESET");
8
+ });
9
+ it("retries on HTTP 5xx", () => {
10
+ expect((0, http_retry_util_1.isRetryableHttpError)({ status: 503 })).toBe(true);
11
+ expect((0, http_retry_util_1.getHttpStatus)({ response: { status: 502 } })).toBe(502);
12
+ });
13
+ it("does not retry HTTP 4xx", () => {
14
+ expect((0, http_retry_util_1.isRetryableHttpError)({ status: 400 })).toBe(false);
15
+ expect((0, http_retry_util_1.isRetryableHttpError)({ statusCode: 404 })).toBe(false);
16
+ });
17
+ it("unwraps nested error", () => {
18
+ expect((0, http_retry_util_1.isRetryableHttpError)({ error: { code: "ETIMEDOUT" } })).toBe(true);
19
+ });
20
+ it("does not retry unknown errors without status/code", () => {
21
+ expect((0, http_retry_util_1.isRetryableHttpError)(new Error("something went wrong"))).toBe(false);
22
+ });
23
+ });
@@ -1,3 +1,4 @@
1
1
  export * from "./utils";
2
2
  export * from "./html-escape.util";
3
3
  export * from "./tms-load-test-run-debug";
4
+ export * from "./http-retry.util";
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./utils"), exports);
18
18
  __exportStar(require("./html-escape.util"), exports);
19
19
  __exportStar(require("./tms-load-test-run-debug"), exports);
20
+ __exportStar(require("./http-retry.util"), exports);
@@ -1,3 +1,2 @@
1
- /** Set `TMS_DEBUG_LOAD_TEST_RUN=1` (or `true`) for loadTestRun / TMS result POST tracing. */
2
1
  export declare function isTmsLoadTestRunDebug(): boolean;
3
2
  export declare function logTmsLoadTestRun(message: string, data?: Record<string, unknown>): void;
@@ -1,9 +1,16 @@
1
1
  "use strict";
2
- /** Set `TMS_DEBUG_LOAD_TEST_RUN=1` (or `true`) for loadTestRun / TMS result POST tracing. */
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
3
5
  Object.defineProperty(exports, "__esModule", { value: true });
4
6
  exports.isTmsLoadTestRunDebug = isTmsLoadTestRunDebug;
5
7
  exports.logTmsLoadTestRun = logTmsLoadTestRun;
8
+ /** Trace loadTestRun / result POST with `LOG_LEVEL=debug` or `TMS_DEBUG_LOAD_TEST_RUN=1`. */
9
+ const logger_1 = __importDefault(require("../../logger"));
6
10
  function isTmsLoadTestRunDebug() {
11
+ if (logger_1.default.isLevelEnabled("debug")) {
12
+ return true;
13
+ }
7
14
  const v = process.env.TMS_DEBUG_LOAD_TEST_RUN;
8
15
  return v === "1" || (v === null || v === void 0 ? void 0 : v.toLowerCase()) === "true";
9
16
  }
@@ -12,9 +19,9 @@ function logTmsLoadTestRun(message, data) {
12
19
  return;
13
20
  }
14
21
  if (data && Object.keys(data).length > 0) {
15
- console.log(`[testit-js-commons:loadTestRun] ${message}`, data);
22
+ logger_1.default.debug(`[loadTestRun] ${message}`, data);
16
23
  }
17
24
  else {
18
- console.log(`[testit-js-commons:loadTestRun] ${message}`);
25
+ logger_1.default.debug(`[loadTestRun] ${message}`);
19
26
  }
20
27
  }
@@ -32,10 +32,14 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.ConfigComposer = exports.DEFAULT_CONFIG_FILE = void 0;
37
40
  const dotenv = __importStar(require("dotenv"));
38
41
  const common_1 = require("../../common");
42
+ const logger_1 = __importDefault(require("../../logger"));
39
43
  exports.DEFAULT_CONFIG_FILE = "tms.config.json";
40
44
  class ConfigComposer {
41
45
  compose(base) {
@@ -48,7 +52,7 @@ class ConfigComposer {
48
52
  if (content !== "") {
49
53
  const fileConfig = JSON.parse(content);
50
54
  if (fileConfig.privateToken) {
51
- console.warn(`
55
+ logger_1.default.warn(`
52
56
  The configuration file specifies a private token. It is not safe.
53
57
  Use TMS_PRIVATE_TOKEN environment variable`);
54
58
  }
@@ -58,10 +62,17 @@ class ConfigComposer {
58
62
  config = this.merge(environment, base);
59
63
  }
60
64
  this.validateConfig(config);
65
+ logger_1.default.debug("[config] composed", {
66
+ adapterMode: config.adapterMode,
67
+ importRealtime: config.importRealtime,
68
+ syncStorageEnabled: config.syncStorageEnabled,
69
+ hasTestRunId: Boolean(config.testRunId),
70
+ projectId: config.projectId,
71
+ });
61
72
  return config;
62
73
  }
63
74
  mergeAll(file, env, base) {
64
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
75
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
65
76
  return {
66
77
  url: this.resolveAllProperties(file.url, env === null || env === void 0 ? void 0 : env.TMS_URL, base === null || base === void 0 ? void 0 : base.url),
67
78
  projectId: this.resolveAllProperties(file.projectId, env === null || env === void 0 ? void 0 : env.TMS_PROJECT_ID, base === null || base === void 0 ? void 0 : base.projectId),
@@ -75,10 +86,11 @@ class ConfigComposer {
75
86
  certValidation: (_m = (_l = (_k = file.certValidation) !== null && _k !== void 0 ? _k : stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_CERT_VALIDATION)) !== null && _l !== void 0 ? _l : base === null || base === void 0 ? void 0 : base.certValidation) !== null && _m !== void 0 ? _m : true,
76
87
  syncStorageEnabled: (_q = (_p = (_o = file.syncStorageEnabled) !== null && _o !== void 0 ? _o : stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_SYNC_STORAGE_ENABLED)) !== null && _p !== void 0 ? _p : base === null || base === void 0 ? void 0 : base.syncStorageEnabled) !== null && _q !== void 0 ? _q : true,
77
88
  syncStoragePort: this.resolveAllProperties(file.syncStoragePort, env === null || env === void 0 ? void 0 : env.TMS_SYNC_STORAGE_PORT, base === null || base === void 0 ? void 0 : base.syncStoragePort) || "49152",
89
+ importRealtime: (_t = (_s = (_r = file.importRealtime) !== null && _r !== void 0 ? _r : stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_IMPORT_REALTIME)) !== null && _s !== void 0 ? _s : base === null || base === void 0 ? void 0 : base.importRealtime) !== null && _t !== void 0 ? _t : false,
78
90
  };
79
91
  }
80
92
  merge(env, base) {
81
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
93
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
82
94
  return {
83
95
  url: this.resolveProperties(env === null || env === void 0 ? void 0 : env.TMS_URL, base === null || base === void 0 ? void 0 : base.url),
84
96
  projectId: this.resolveProperties(env === null || env === void 0 ? void 0 : env.TMS_PROJECT_ID, base === null || base === void 0 ? void 0 : base.projectId),
@@ -92,10 +104,11 @@ class ConfigComposer {
92
104
  certValidation: (_h = (_g = stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_CERT_VALIDATION)) !== null && _g !== void 0 ? _g : base === null || base === void 0 ? void 0 : base.certValidation) !== null && _h !== void 0 ? _h : true,
93
105
  syncStorageEnabled: (_k = (_j = stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_SYNC_STORAGE_ENABLED)) !== null && _j !== void 0 ? _j : base === null || base === void 0 ? void 0 : base.syncStorageEnabled) !== null && _k !== void 0 ? _k : true,
94
106
  syncStoragePort: this.resolveProperties(env === null || env === void 0 ? void 0 : env.TMS_SYNC_STORAGE_PORT, base === null || base === void 0 ? void 0 : base.syncStoragePort) || "49152",
107
+ importRealtime: (_m = (_l = stringToBoolean(env === null || env === void 0 ? void 0 : env.TMS_IMPORT_REALTIME)) !== null && _l !== void 0 ? _l : base === null || base === void 0 ? void 0 : base.importRealtime) !== null && _m !== void 0 ? _m : false,
95
108
  };
96
109
  }
97
110
  mergeEnv(dotEnv, processEnv) {
98
- var _a, _b, _c, _d, _e;
111
+ var _a, _b, _c, _d, _e, _f;
99
112
  return {
100
113
  TMS_URL: this.resolveProperties(dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_URL, processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_URL),
101
114
  TMS_PROJECT_ID: this.resolveProperties(dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_PROJECT_ID, processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_PROJECT_ID),
@@ -109,6 +122,7 @@ class ConfigComposer {
109
122
  TMS_CERT_VALIDATION: (_d = dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_CERT_VALIDATION) !== null && _d !== void 0 ? _d : processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_CERT_VALIDATION,
110
123
  TMS_SYNC_STORAGE_ENABLED: (_e = dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_SYNC_STORAGE_ENABLED) !== null && _e !== void 0 ? _e : processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_SYNC_STORAGE_ENABLED,
111
124
  TMS_SYNC_STORAGE_PORT: this.resolveProperties(dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_SYNC_STORAGE_PORT, processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_SYNC_STORAGE_PORT),
125
+ TMS_IMPORT_REALTIME: (_f = dotEnv === null || dotEnv === void 0 ? void 0 : dotEnv.TMS_IMPORT_REALTIME) !== null && _f !== void 0 ? _f : processEnv === null || processEnv === void 0 ? void 0 : processEnv.TMS_IMPORT_REALTIME,
112
126
  };
113
127
  }
114
128
  resolveAllProperties(file, env, base) {
@@ -141,30 +155,30 @@ class ConfigComposer {
141
155
  new URL(config.url);
142
156
  }
143
157
  catch (err) {
144
- console.error(`Url is invalid`);
158
+ logger_1.default.error(`Url is invalid`);
145
159
  }
146
160
  if (!config.privateToken) {
147
- console.error(`Private Token is invalid`);
161
+ logger_1.default.error(`Private Token is invalid`);
148
162
  }
149
163
  if (config.projectId.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null) {
150
- console.error(`Project ID is invalid`);
164
+ logger_1.default.error(`Project ID is invalid`);
151
165
  }
152
166
  if (config.configurationId.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null) {
153
- console.error(`Configuration ID is invalid`);
167
+ logger_1.default.error(`Configuration ID is invalid`);
154
168
  }
155
169
  if (config.adapterMode == 2) {
156
170
  if (config.testRunId.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') !== null) {
157
- console.error(`Adapter works in mode 2. Config should not contains test run id.`);
171
+ logger_1.default.error(`Adapter works in mode 2. Config should not contains test run id.`);
158
172
  }
159
173
  }
160
174
  else if (config.adapterMode == 1) {
161
175
  if (config.testRunId.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null) {
162
- console.error(`Adapter works in mode 1. Config should contains valid test run id.`);
176
+ logger_1.default.error(`Adapter works in mode 1. Config should contains valid test run id.`);
163
177
  }
164
178
  }
165
179
  else {
166
180
  if (config.testRunId.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null) {
167
- console.error(`Adapter works in mode 0. Config should contains valid test run id.`);
181
+ logger_1.default.error(`Adapter works in mode 0. Config should contains valid test run id.`);
168
182
  }
169
183
  }
170
184
  }
@@ -188,6 +202,7 @@ function parseProcessEnvConfig() {
188
202
  TMS_CONFIG_FILE: process.env.TMS_PRIVATE_TOKEN,
189
203
  TMS_SYNC_STORAGE_ENABLED: process.env.TMS_SYNC_STORAGE_ENABLED,
190
204
  TMS_SYNC_STORAGE_PORT: process.env.TMS_SYNC_STORAGE_PORT,
205
+ TMS_IMPORT_REALTIME: process.env.TMS_IMPORT_REALTIME,
191
206
  };
192
207
  }
193
208
  function stringToAdapterMode(str) {
@@ -8,6 +8,7 @@ describe("ConfigComposer sync-storage defaults", () => {
8
8
  process.env = Object.assign({}, env);
9
9
  delete process.env.TMS_SYNC_STORAGE_ENABLED;
10
10
  delete process.env.TMS_SYNC_STORAGE_PORT;
11
+ delete process.env.TMS_IMPORT_REALTIME;
11
12
  });
12
13
  afterAll(() => {
13
14
  process.env = env;
@@ -22,6 +23,7 @@ describe("ConfigComposer sync-storage defaults", () => {
22
23
  });
23
24
  expect(config.syncStorageEnabled).toBe(true);
24
25
  expect(config.syncStoragePort).toBe("49152");
26
+ expect(config.importRealtime).toBe(false);
25
27
  });
26
28
  it("should allow disabling sync-storage via env", () => {
27
29
  const config = new config_helper_1.ConfigComposer().merge({
@@ -37,4 +39,26 @@ describe("ConfigComposer sync-storage defaults", () => {
37
39
  expect(config.syncStorageEnabled).toBe(false);
38
40
  expect(config.syncStoragePort).toBe("59999");
39
41
  });
42
+ it("should keep importRealtime false by default", () => {
43
+ const config = new config_helper_1.ConfigComposer().merge(undefined, {
44
+ url: "http://localhost:8080",
45
+ privateToken: "token",
46
+ projectId: "11111111-1111-1111-1111-111111111111",
47
+ configurationId: "22222222-2222-2222-2222-222222222222",
48
+ testRunId: "33333333-3333-3333-3333-333333333333",
49
+ });
50
+ expect(config.importRealtime).toBe(false);
51
+ });
52
+ it("should allow enabling importRealtime via env", () => {
53
+ const config = new config_helper_1.ConfigComposer().merge({
54
+ TMS_IMPORT_REALTIME: "true",
55
+ }, {
56
+ url: "http://localhost:8080",
57
+ privateToken: "token",
58
+ projectId: "11111111-1111-1111-1111-111111111111",
59
+ configurationId: "22222222-2222-2222-2222-222222222222",
60
+ testRunId: "33333333-3333-3333-3333-333333333333",
61
+ });
62
+ expect(config.importRealtime).toBe(true);
63
+ });
40
64
  });
package/lib/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./helpers";
4
4
  export * from "./services";
5
5
  export * from "./storage";
6
6
  export * from "./strategy";
7
+ export { default as logger } from "./logger";
package/lib/index.js CHANGED
@@ -13,10 +13,16 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
13
13
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
16
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.logger = void 0;
17
21
  __exportStar(require("./common"), exports);
18
22
  __exportStar(require("./client"), exports);
19
23
  __exportStar(require("./helpers"), exports);
20
24
  __exportStar(require("./services"), exports);
21
25
  __exportStar(require("./storage"), exports);
22
26
  __exportStar(require("./strategy"), exports);
27
+ var logger_1 = require("./logger");
28
+ Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return __importDefault(logger_1).default; } });
@@ -0,0 +1,17 @@
1
+ type LogLevel = 'error' | 'warn' | 'info' | 'debug';
2
+ declare class Logger {
3
+ private readonly levels;
4
+ private currentLevel;
5
+ constructor();
6
+ private log_;
7
+ error(message: string, ...args: unknown[]): void;
8
+ warn(message: string, ...args: unknown[]): void;
9
+ info(message: string, ...args: unknown[]): void;
10
+ log(message: string, ...args: unknown[]): void;
11
+ debug(message: string, ...args: unknown[]): void;
12
+ isLevelEnabled(level: LogLevel): boolean;
13
+ getCurrentLevel(): LogLevel;
14
+ setLevel(level: LogLevel): void;
15
+ }
16
+ declare const logger: Logger;
17
+ export default logger;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class Logger {
4
+ constructor() {
5
+ this.levels = {
6
+ error: 0,
7
+ warn: 1,
8
+ info: 2,
9
+ debug: 3
10
+ };
11
+ // Уровень из переменной окружения или по умолчанию 'warn'
12
+ const envLevel = (process.env.LOG_LEVEL || 'warn').toLowerCase();
13
+ this.currentLevel = this.levels[envLevel] !== undefined
14
+ ? this.levels[envLevel]
15
+ : this.levels.info;
16
+ }
17
+ log_(level, message, ...args) {
18
+ if (this.levels[level] <= this.currentLevel) {
19
+ const timestamp = new Date().toISOString();
20
+ const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
21
+ if (level === 'error') {
22
+ console.error(prefix, message, ...args);
23
+ }
24
+ else if (level === 'warn') {
25
+ console.warn(prefix, message, ...args);
26
+ }
27
+ else {
28
+ console.log(prefix, message, ...args);
29
+ }
30
+ }
31
+ }
32
+ error(message, ...args) {
33
+ this.log_('error', message, ...args);
34
+ }
35
+ warn(message, ...args) {
36
+ this.log_('warn', message, ...args);
37
+ }
38
+ info(message, ...args) {
39
+ this.log_('info', message, ...args);
40
+ }
41
+ log(message, ...args) {
42
+ this.log_('info', message, ...args);
43
+ }
44
+ debug(message, ...args) {
45
+ this.log_('debug', message, ...args);
46
+ }
47
+ // Проверка, включен ли уровень
48
+ isLevelEnabled(level) {
49
+ return this.levels[level] <= this.currentLevel;
50
+ }
51
+ // Получить текущий уровень логирования
52
+ getCurrentLevel() {
53
+ const level = Object.keys(this.levels).find((key) => this.levels[key] === this.currentLevel);
54
+ return level || 'info';
55
+ }
56
+ // Установить уровень логирования динамически
57
+ setLevel(level) {
58
+ if (this.levels[level] !== undefined) {
59
+ this.currentLevel = this.levels[level];
60
+ }
61
+ }
62
+ }
63
+ // Синглтон
64
+ const logger = new Logger();
65
+ exports.default = logger;
@@ -41,6 +41,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  exports.AttachmentsService = void 0;
46
49
  // @ts-ignore
@@ -48,64 +51,10 @@ const TestitApiClient = __importStar(require("testit-api-client"));
48
51
  const common_1 = require("../../common");
49
52
  const buffer_1 = require("buffer");
50
53
  const fs = __importStar(require("fs"));
51
- const UPLOAD_MAX_ATTEMPTS = 5;
52
- const UPLOAD_RETRY_BASE_MS = 500;
54
+ const logger_1 = __importDefault(require("../../logger"));
55
+ const UPLOAD_RETRY_OPTIONS = { maxAttempts: 5, delayMs: 500, backoff: true };
53
56
  /** Minimum HTTP timeout for attachment POST (ms); reduces false timeouts on slow TLS. */
54
57
  const UPLOAD_CLIENT_TIMEOUT_MS = 120000;
55
- function sleep(ms) {
56
- return new Promise((resolve) => setTimeout(resolve, ms));
57
- }
58
- function unwrapAttachmentError(err) {
59
- if (!err || typeof err !== "object") {
60
- return err;
61
- }
62
- const nested = err.error;
63
- if (nested instanceof Error || (nested && typeof nested === "object")) {
64
- return nested;
65
- }
66
- const cause = err.cause;
67
- if (cause instanceof Error || (cause && typeof cause === "object")) {
68
- return cause;
69
- }
70
- return err;
71
- }
72
- function isTransientAttachmentError(err) {
73
- var _a, _b, _c, _d, _e;
74
- const e = unwrapAttachmentError(err);
75
- const status = (_b = (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : (_c = err === null || err === void 0 ? void 0 : err.response) === null || _c === void 0 ? void 0 : _c.status;
76
- if (typeof status === "number" && status >= 400 && status < 500)
77
- return false;
78
- if (typeof status === "number" && status >= 500)
79
- return true;
80
- const code = (_d = e === null || e === void 0 ? void 0 : e.code) !== null && _d !== void 0 ? _d : err === null || err === void 0 ? void 0 : err.code;
81
- if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "EPIPE" || code === "ECONNABORTED")
82
- return true;
83
- if ((e === null || e === void 0 ? void 0 : e.errno) === -104 || (err === null || err === void 0 ? void 0 : err.errno) === -104)
84
- return true;
85
- const msg = (_e = e === null || e === void 0 ? void 0 : e.message) !== null && _e !== void 0 ? _e : err === null || err === void 0 ? void 0 : err.message;
86
- if (typeof msg === "string" && /socket hang up|ECONNRESET|ETIMEDOUT|ECONNREFUSED|read ECONNRESET/i.test(msg)) {
87
- return true;
88
- }
89
- return false;
90
- }
91
- function withUploadRetry(fn) {
92
- return __awaiter(this, void 0, void 0, function* () {
93
- let last;
94
- for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
95
- try {
96
- return yield fn();
97
- }
98
- catch (e) {
99
- last = e;
100
- if (!isTransientAttachmentError(e) || attempt === UPLOAD_MAX_ATTEMPTS) {
101
- throw e;
102
- }
103
- yield sleep(UPLOAD_RETRY_BASE_MS * attempt);
104
- }
105
- }
106
- throw last;
107
- });
108
- }
109
58
  class AttachmentsService extends common_1.BaseService {
110
59
  constructor(config) {
111
60
  super(config);
@@ -126,7 +75,8 @@ class AttachmentsService extends common_1.BaseService {
126
75
  const tempFilePath = `${tempDir}/${fileName}`;
127
76
  try {
128
77
  fs.writeFileSync(tempFilePath, bufferContent);
129
- const id = yield withUploadRetry(() => __awaiter(this, void 0, void 0, function* () {
78
+ logger_1.default.debug("[attachments] upload text", { fileName, bytes: bufferContent.length });
79
+ const id = yield (0, common_1.withHttpRetry)(() => __awaiter(this, void 0, void 0, function* () {
130
80
  const fileStream = fs.createReadStream(tempFilePath);
131
81
  try {
132
82
  // @ts-ignore
@@ -137,7 +87,8 @@ class AttachmentsService extends common_1.BaseService {
137
87
  finally {
138
88
  fileStream.destroy();
139
89
  }
140
- }));
90
+ }), Object.assign(Object.assign({}, UPLOAD_RETRY_OPTIONS), { label: `uploadText:${fileName}` }));
91
+ logger_1.default.debug("[attachments] upload text ok", { fileName, id });
141
92
  return [{ id }];
142
93
  }
143
94
  finally {
@@ -150,14 +101,14 @@ class AttachmentsService extends common_1.BaseService {
150
101
  }
151
102
  }
152
103
  catch (cleanupError) {
153
- console.warn("Failed to cleanup temporary files:", cleanupError);
104
+ logger_1.default.warn("Failed to cleanup temporary files:", cleanupError);
154
105
  }
155
106
  }
156
107
  }
157
108
  catch (error) {
158
- console.error("Error uploading text attachment:", error);
109
+ logger_1.default.error("Error uploading text attachment:", error);
159
110
  if (error.response) {
160
- console.error("Response details:", {
111
+ logger_1.default.error("Response details:", {
161
112
  status: error.response.status,
162
113
  text: error.response.text,
163
114
  });
@@ -174,7 +125,8 @@ class AttachmentsService extends common_1.BaseService {
174
125
  if (!fs.existsSync(path)) {
175
126
  throw new Error(`File not found: ${path}`);
176
127
  }
177
- const id = yield withUploadRetry(() => __awaiter(this, void 0, void 0, function* () {
128
+ logger_1.default.debug("[attachments] upload file", { path });
129
+ const id = yield (0, common_1.withHttpRetry)(() => __awaiter(this, void 0, void 0, function* () {
178
130
  const fileStream = common_1.Utils.readStream(path);
179
131
  try {
180
132
  // @ts-ignore
@@ -185,13 +137,14 @@ class AttachmentsService extends common_1.BaseService {
185
137
  finally {
186
138
  fileStream.destroy();
187
139
  }
188
- }));
140
+ }), Object.assign(Object.assign({}, UPLOAD_RETRY_OPTIONS), { label: `uploadFile:${path}` }));
141
+ logger_1.default.debug("[attachments] upload file ok", { path, id });
189
142
  attachmentIds.push(id);
190
143
  }
191
144
  catch (error) {
192
- console.error(`Error uploading attachment ${path}:`, error);
145
+ logger_1.default.error(`Error uploading attachment ${path}:`, error);
193
146
  if (error.response) {
194
- console.error("Response details:", {
147
+ logger_1.default.error("Response details:", {
195
148
  status: error.response.status,
196
149
  text: error.response.text,
197
150
  });
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.handleHttpError = handleHttpError;
7
+ const logger_1 = __importDefault(require("../../logger"));
4
8
  function handleHttpError(err, message = "") {
5
- console.error(`HttpError ${err.statusCode}: ${message}. Error body: \n`, err.body);
9
+ logger_1.default.error(`HttpError ${err.statusCode}: ${message}. Error body: \n`, err.body);
6
10
  }
@@ -6,8 +6,6 @@ export declare class AutotestsService extends BaseService implements IAutotestSe
6
6
  protected readonly config: AdapterConfig;
7
7
  protected _client: TestitApiClient.AutoTestsApi;
8
8
  protected _converter: IAutotestConverter;
9
- private MAX_TRIES;
10
- private WAITING_TIME;
11
9
  constructor(config: AdapterConfig);
12
10
  createAutotest(autotest: AutotestPost): Promise<void>;
13
11
  updateAutotest(autotest: AutotestPost): Promise<void>;
@@ -41,6 +41,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  exports.AutotestsService = void 0;
46
49
  // @ts-ignore
@@ -48,12 +51,11 @@ const TestitApiClient = __importStar(require("testit-api-client"));
48
51
  const common_1 = require("../../common");
49
52
  const autotests_converter_1 = require("./autotests.converter");
50
53
  const autotests_handler_1 = require("./autotests.handler");
54
+ const logger_1 = __importDefault(require("../../logger"));
51
55
  class AutotestsService extends common_1.BaseService {
52
56
  constructor(config) {
53
57
  super(config);
54
58
  this.config = config;
55
- this.MAX_TRIES = 10;
56
- this.WAITING_TIME = 100;
57
59
  this._client = new TestitApiClient.AutoTestsApi();
58
60
  this._converter = new autotests_converter_1.AutotestConverter(config);
59
61
  }
@@ -61,9 +63,10 @@ class AutotestsService extends common_1.BaseService {
61
63
  return __awaiter(this, void 0, void 0, function* () {
62
64
  const autotestPost = this._converter.toOriginAutotest(autotest);
63
65
  (0, common_1.escapeHtmlInObject)(autotestPost);
64
- return yield this._client
65
- .createAutoTest({ autoTestCreateApiModel: autotestPost })
66
- .then(() => console.log(`Create autotest "${autotest.name}".`))
66
+ logger_1.default.debug("[autotests] createAutoTest", { externalId: autotest.externalId, name: autotest.name });
67
+ return yield (0, common_1.withHttpRetry)(() => this._client
68
+ .createAutoTest({ autoTestCreateApiModel: autotestPost }), { label: "createAutoTest" })
69
+ .then(() => logger_1.default.log(`Create autotest "${autotest.name}".`))
67
70
  // @ts-ignore
68
71
  .catch((err) => (0, autotests_handler_1.handleHttpError)(err, `Failed create autotest "${autotestPost.name}"`));
69
72
  });
@@ -72,9 +75,10 @@ class AutotestsService extends common_1.BaseService {
72
75
  return __awaiter(this, void 0, void 0, function* () {
73
76
  const autotestPost = this._converter.toOriginAutotest(autotest);
74
77
  (0, common_1.escapeHtmlInObject)(autotestPost);
75
- yield this._client
76
- .updateAutoTest({ autoTestUpdateApiModel: autotestPost })
77
- .then(() => console.log(`Update autotest "${autotest.name}".`))
78
+ logger_1.default.debug("[autotests] updateAutoTest", { externalId: autotest.externalId, name: autotest.name });
79
+ yield (0, common_1.withHttpRetry)(() => this._client
80
+ .updateAutoTest({ autoTestUpdateApiModel: autotestPost }), { label: "updateAutoTest" })
81
+ .then(() => logger_1.default.log(`Update autotest "${autotest.name}".`))
78
82
  // @ts-ignore
79
83
  .catch((err) => (0, autotests_handler_1.handleHttpError)(err, `Failed update autotest "${autotestPost.name}"`));
80
84
  });
@@ -85,9 +89,11 @@ class AutotestsService extends common_1.BaseService {
85
89
  var _a, _b;
86
90
  const originAutotest = yield this.getAutotestByExternalId(autotest.externalId);
87
91
  if (!originAutotest) {
92
+ logger_1.default.debug("[autotests] loadAutotest → create", { externalId: autotest.externalId, status });
88
93
  yield this.createAutotest(autotest);
89
94
  return;
90
95
  }
96
+ logger_1.default.debug("[autotests] loadAutotest → update path", { externalId: autotest.externalId, status });
91
97
  const mergedAutotest = Object.assign(Object.assign({}, autotest), { namespace: (_a = autotest.namespace) !== null && _a !== void 0 ? _a : originAutotest.namespace, classname: (_b = autotest.classname) !== null && _b !== void 0 ? _b : originAutotest.classname });
92
98
  // fix the issue with lowercase status
93
99
  const currentStatus = status.toLowerCase();
@@ -103,10 +109,10 @@ class AutotestsService extends common_1.BaseService {
103
109
  yield this.updateAutotestFromFailed(originAutotest, mergedAutotest);
104
110
  return;
105
111
  }
106
- console.log(`Cannot update skipped autotest ${autotest.name} without name or externalId`);
112
+ logger_1.default.log(`Cannot update skipped autotest ${autotest.name} without name or externalId`);
107
113
  return;
108
114
  default:
109
- console.log(`Cannot update autotest ${autotest.name} with unknown status ${status}`);
115
+ logger_1.default.log(`Cannot update autotest ${autotest.name} with unknown status ${status}`);
110
116
  }
111
117
  });
112
118
  }
@@ -119,35 +125,26 @@ class AutotestsService extends common_1.BaseService {
119
125
  linkToWorkItems(internalId, workItemIds) {
120
126
  return __awaiter(this, void 0, void 0, function* () {
121
127
  const promises = workItemIds.map((workItemId) => __awaiter(this, void 0, void 0, function* () {
122
- for (let attempts = 0; attempts < this.MAX_TRIES; attempts++) {
123
- try {
124
- yield this._client.linkAutoTestToWorkItem(internalId, { workItemIdApiModel: { id: workItemId } });
125
- console.log(`Link autotest ${internalId} to workitem ${workItemId} is successfully`);
126
- return;
127
- // @ts-ignore
128
- }
129
- catch (e) {
130
- console.error(`Cannot link autotest ${internalId} to work item ${workItemId}`);
131
- // console.error(e);
132
- yield new Promise((f) => setTimeout(f, this.WAITING_TIME));
133
- }
128
+ var _a, _b;
129
+ try {
130
+ yield this._client.linkAutoTestToWorkItem(internalId, { workItemIdApiModel: { id: workItemId } });
131
+ logger_1.default.log(`Link autotest ${internalId} to workitem ${workItemId} is successfully`);
132
+ }
133
+ catch (e) {
134
+ logger_1.default.error(`Cannot link autotest ${internalId} to work item ${workItemId}`, (_b = (_a = e === null || e === void 0 ? void 0 : e.body) !== null && _a !== void 0 ? _a : e === null || e === void 0 ? void 0 : e.error) !== null && _b !== void 0 ? _b : e);
134
135
  }
135
136
  }));
136
- yield Promise.all(promises).catch((err) => (0, autotests_handler_1.handleHttpError)(err, "Failed link work item"));
137
+ yield Promise.all(promises);
137
138
  });
138
139
  }
139
140
  unlinkToWorkItem(internalId, workItemId) {
140
141
  return __awaiter(this, void 0, void 0, function* () {
141
- for (let attempts = 0; attempts < this.MAX_TRIES; attempts++) {
142
- try {
143
- yield this._client.deleteAutoTestLinkFromWorkItem(internalId, { workItemId: workItemId });
144
- console.log(`Unlink autotest ${internalId} from workitem ${workItemId} is successfully`);
145
- return;
146
- }
147
- catch (e) {
148
- console.log(`Cannot unlink autotest ${internalId} to work item ${workItemId}: ${e}`);
149
- yield new Promise((f) => setTimeout(f, this.WAITING_TIME));
150
- }
142
+ try {
143
+ yield this._client.deleteAutoTestLinkFromWorkItem(internalId, { workItemId: workItemId });
144
+ logger_1.default.log(`Unlink autotest ${internalId} from workitem ${workItemId} is successfully`);
145
+ }
146
+ catch (e) {
147
+ logger_1.default.log(`Cannot unlink autotest ${internalId} from work item ${workItemId}: ${e}`);
151
148
  }
152
149
  });
153
150
  }
@@ -159,7 +156,7 @@ class AutotestsService extends common_1.BaseService {
159
156
  .then((res) => res.body)
160
157
  // @ts-ignore
161
158
  .catch((e) => {
162
- console.log(`Cannot get linked workitems to autotest ${internalId}: ${e}`);
159
+ logger_1.default.log(`Cannot get linked workitems to autotest ${internalId}: ${e}`);
163
160
  return [];
164
161
  });
165
162
  });
@@ -191,7 +188,7 @@ class AutotestsService extends common_1.BaseService {
191
188
  return autotest ? this._converter.toLocalAutotest(autotest) : null;
192
189
  })
193
190
  .catch((reason) => {
194
- console.error(reason);
191
+ logger_1.default.error(reason);
195
192
  return null;
196
193
  });
197
194
  });
@@ -8,6 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.SyncStorageRunner = void 0;
13
16
  const fs_1 = require("fs");
@@ -17,6 +20,7 @@ const path_1 = require("path");
17
20
  const process_1 = require("process");
18
21
  const child_process_1 = require("child_process");
19
22
  const utils_1 = require("../../common/utils");
23
+ const logger_1 = __importDefault(require("../../logger"));
20
24
  // Generated sync-storage client is bundled into lib/sync-storage/dist during build.
21
25
  // eslint-disable-next-line @typescript-eslint/no-var-requires
22
26
  const SyncStorageClient = require("../../sync-storage/dist/index");
@@ -65,7 +69,7 @@ class SyncStorageRunner {
65
69
  return true;
66
70
  }
67
71
  catch (error) {
68
- console.warn(`Sync storage start failed: ${error}`);
72
+ logger_1.default.warn(`Sync storage start failed: ${error}`);
69
73
  return false;
70
74
  }
71
75
  });
@@ -108,7 +112,7 @@ class SyncStorageRunner {
108
112
  return __awaiter(this, void 0, void 0, function* () {
109
113
  if (!this.running || !this.isMaster || this.alreadyInProgress || this.inProgressPublishing) {
110
114
  if ((0, utils_1.isTmsLoadTestRunDebug)()) {
111
- console.debug("[syncstorage] skip in-progress cut publish", {
115
+ logger_1.default.debug("[syncstorage] skip in-progress cut publish", {
112
116
  reason: {
113
117
  notRunning: !this.running,
114
118
  notMaster: !this.isMaster,
@@ -121,9 +125,9 @@ class SyncStorageRunner {
121
125
  return false;
122
126
  }
123
127
  if (!model.projectId || !model.autoTestExternalId || !model.statusCode || !model.statusType) {
124
- console.warn("Sync storage in-progress payload is incomplete; skipping publish.");
128
+ logger_1.default.warn("Sync storage in-progress payload is incomplete; skipping publish.");
125
129
  if ((0, utils_1.isTmsLoadTestRunDebug)()) {
126
- console.debug("[syncstorage] incomplete in-progress cut payload", {
130
+ logger_1.default.debug("[syncstorage] incomplete in-progress cut payload", {
127
131
  hasProjectId: Boolean(model.projectId),
128
132
  hasAutoTestExternalId: Boolean(model.autoTestExternalId),
129
133
  hasStatusCode: Boolean(model.statusCode),
@@ -144,11 +148,11 @@ class SyncStorageRunner {
144
148
  yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () { return this.testResultsApi.inProgressTestResultPost(this.testRunId, request); }), SyncStorageRunner.RETRY_COUNT);
145
149
  this.alreadyInProgress = true;
146
150
  if ((0, utils_1.isTmsLoadTestRunDebug)()) {
147
- console.debug("[syncstorage] alreadyInProgress set", {
151
+ logger_1.default.debug("[syncstorage] alreadyInProgress set", {
148
152
  workerPid: this.workerPid,
149
153
  autoTestExternalId: model.autoTestExternalId,
150
154
  });
151
- console.debug("[syncstorage] in-progress cut published", {
155
+ logger_1.default.debug("[syncstorage] in-progress cut published", {
152
156
  workerPid: this.workerPid,
153
157
  autoTestExternalId: model.autoTestExternalId,
154
158
  });
@@ -156,9 +160,9 @@ class SyncStorageRunner {
156
160
  return true;
157
161
  }
158
162
  catch (error) {
159
- console.warn(`Sync storage in-progress publish failed: ${error}`);
163
+ logger_1.default.warn(`Sync storage in-progress publish failed: ${error}`);
160
164
  if ((0, utils_1.isTmsLoadTestRunDebug)()) {
161
- console.debug("[syncstorage] in-progress cut publish failed", {
165
+ logger_1.default.debug("[syncstorage] in-progress cut publish failed", {
162
166
  workerPid: this.workerPid,
163
167
  autoTestExternalId: model.autoTestExternalId,
164
168
  });
@@ -184,7 +188,7 @@ class SyncStorageRunner {
184
188
  yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () { return this.workersApi.setWorkerStatusPost(request); }), SyncStorageRunner.RETRY_COUNT);
185
189
  }
186
190
  catch (error) {
187
- console.warn(`Sync storage set worker status failed: ${error}`);
191
+ logger_1.default.warn(`Sync storage set worker status failed: ${error}`);
188
192
  }
189
193
  });
190
194
  }
@@ -204,7 +208,7 @@ class SyncStorageRunner {
204
208
  yield this.withRetry(() => __awaiter(this, void 0, void 0, function* () { return this.completionApi.forceCompletionGet(this.testRunId); }), SyncStorageRunner.RETRY_COUNT);
205
209
  }
206
210
  catch (error) {
207
- console.warn(`Sync storage completion failed: ${error}`);
211
+ logger_1.default.warn(`Sync storage completion failed: ${error}`);
208
212
  }
209
213
  });
210
214
  }
@@ -241,7 +245,7 @@ class SyncStorageRunner {
241
245
  return started;
242
246
  }
243
247
  catch (error) {
244
- console.warn(`Sync storage local process start failed: ${error}`);
248
+ logger_1.default.warn(`Sync storage local process start failed: ${error}`);
245
249
  return false;
246
250
  }
247
251
  });
@@ -349,7 +353,7 @@ class SyncStorageRunner {
349
353
  }
350
354
  }
351
355
  exports.SyncStorageRunner = SyncStorageRunner;
352
- SyncStorageRunner.VERSION = "v0.3.2";
356
+ SyncStorageRunner.VERSION = "v0.3.5";
353
357
  SyncStorageRunner.STARTUP_TIMEOUT_MS = 30000;
354
358
  SyncStorageRunner.STARTUP_POLL_MS = 1000;
355
359
  SyncStorageRunner.PROCESS_WARMUP_MS = 2000;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.handleHttpError = handleHttpError;
7
+ const logger_1 = __importDefault(require("../../logger"));
4
8
  function handleHttpError(err, message = "") {
5
- console.error(`HttpError ${err.statusCode}: ${message}. Error body: \n`, err.body);
9
+ logger_1.default.error(`HttpError ${err.statusCode}: ${message}. Error body: \n`, err.body);
6
10
  }
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.TestRunErrorHandler = void 0;
7
+ const logger_1 = __importDefault(require("../../logger"));
4
8
  class TestRunErrorHandler {
5
9
  static handleErrorStartTestRun(err, message = "") {
6
- console.error(`HttpError ${err.statusCode}. Failed start test run in system. Message: ${message}. Error body:\n`, err.body);
10
+ logger_1.default.error(`HttpError ${err.statusCode}. Failed start test run in system. Message: ${message}. Error body:\n`, err.body);
7
11
  }
8
12
  static handleErrorCompletedTestRun(err, message = "") {
9
- console.error(`HttpError ${err.statusCode}. Failed completed test run in system. Message: ${message}. Error body:\n`, err.body);
13
+ logger_1.default.error(`HttpError ${err.statusCode}. Failed completed test run in system. Message: ${message}. Error body:\n`, err.body);
10
14
  }
11
15
  }
12
16
  exports.TestRunErrorHandler = TestRunErrorHandler;
@@ -41,6 +41,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  exports.TestRunsService = void 0;
46
49
  // @ts-ignore
@@ -49,6 +52,7 @@ const common_1 = require("../../common");
49
52
  const utils_1 = require("../../common/utils");
50
53
  const testruns_converter_1 = require("./testruns.converter");
51
54
  const testruns_handler_1 = require("./testruns.handler");
55
+ const logger_1 = __importDefault(require("../../logger"));
52
56
  class TestRunsService extends common_1.BaseService {
53
57
  constructor(config) {
54
58
  super(config);
@@ -66,7 +70,7 @@ class TestRunsService extends common_1.BaseService {
66
70
  .createEmpty({ createEmptyTestRunApiModel: (0, utils_1.escapeHtmlInObject)(createRequest) })
67
71
  // @ts-ignore
68
72
  .then((response) => {
69
- //console.debug("Full response from createEmpty:", response);
73
+ //logger.debug("Full response from createEmpty:", response);
70
74
  const data = response.body || response;
71
75
  if (!data) {
72
76
  throw new Error("API returned undefined response");
@@ -77,7 +81,7 @@ class TestRunsService extends common_1.BaseService {
77
81
  return data.id;
78
82
  })
79
83
  .catch((err) => {
80
- console.error("Error in createTestRun:", err);
84
+ logger_1.default.error("Error in createTestRun:", err);
81
85
  throw err;
82
86
  });
83
87
  });
@@ -101,7 +105,7 @@ class TestRunsService extends common_1.BaseService {
101
105
  .updateEmpty({ updateEmptyTestRunApiModel: testRun })
102
106
  // @ts-ignore
103
107
  .then((response) => {
104
- console.log("Full response from updateEmpty:", response);
108
+ logger_1.default.log("Full response from updateEmpty:", response);
105
109
  const data = response.body || response;
106
110
  if (!data) {
107
111
  throw new Error("API returned undefined response");
@@ -149,7 +153,7 @@ class TestRunsService extends common_1.BaseService {
149
153
  statusCode: model.statusCode,
150
154
  hasStartedOn: Boolean(model.startedOn),
151
155
  });
152
- yield this._client.setAutoTestResultsForTestRun(testRunId, { autoTestResultsForTestRunModel: [model] });
156
+ yield this.sendAutotestResultWithRetry(testRunId, model);
153
157
  (0, utils_1.logTmsLoadTestRun)("POST setAutoTestResults (InProgress stub) done", {
154
158
  autoTestExternalId: model.autoTestExternalId,
155
159
  });
@@ -171,7 +175,7 @@ class TestRunsService extends common_1.BaseService {
171
175
  yield this.sendAutotestResultWithRetry(testRunId, autotestResult).catch((err) => {
172
176
  var _a, _b;
173
177
  const normalized = (_b = (_a = err === null || err === void 0 ? void 0 : err.body) !== null && _a !== void 0 ? _a : err === null || err === void 0 ? void 0 : err.error) !== null && _b !== void 0 ? _b : err;
174
- console.error("[testit-js-commons:loadTestRun] FAILED to post final result", {
178
+ logger_1.default.error("[testit-js-commons:loadTestRun] FAILED to post final result", {
175
179
  testRunId,
176
180
  autoTestExternalId: autotestResult.autoTestExternalId,
177
181
  error: normalized,
@@ -182,30 +186,18 @@ class TestRunsService extends common_1.BaseService {
182
186
  }
183
187
  sendAutotestResultWithRetry(testRunId, autotestResult) {
184
188
  return __awaiter(this, void 0, void 0, function* () {
185
- var _a, _b, _c;
186
- const maxAttempts = 5;
187
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
188
- try {
189
- yield this._client.setAutoTestResultsForTestRun(testRunId, { autoTestResultsForTestRunModel: [autotestResult] });
190
- return;
191
- }
192
- catch (err) {
193
- const code = err === null || err === void 0 ? void 0 : err.code;
194
- const status = (_a = err === null || err === void 0 ? void 0 : err.status) !== null && _a !== void 0 ? _a : err === null || err === void 0 ? void 0 : err.statusCode;
195
- const msg = String((_c = (_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err) !== null && _c !== void 0 ? _c : "");
196
- const transient = code === "ECONNRESET" ||
197
- code === "ETIMEDOUT" ||
198
- code === "EPIPE" ||
199
- code === "ECONNABORTED" ||
200
- msg.includes("socket hang up") ||
201
- msg.includes("read ECONNRESET") ||
202
- (typeof status === "number" && status >= 500);
203
- if (!transient || attempt === maxAttempts) {
204
- throw err;
205
- }
206
- yield new Promise((resolve) => setTimeout(resolve, 1000));
207
- }
208
- }
189
+ var _a;
190
+ yield (0, utils_1.withHttpRetry)(() => this._client.setAutoTestResultsForTestRun(testRunId, {
191
+ autoTestResultsForTestRunModel: [autotestResult],
192
+ }), {
193
+ label: `setAutoTestResults:${autotestResult.autoTestExternalId}:${(_a = autotestResult.statusCode) !== null && _a !== void 0 ? _a : autotestResult.statusType}`,
194
+ });
195
+ logger_1.default.debug("[testruns] setAutoTestResults ok", {
196
+ testRunId,
197
+ autoTestExternalId: autotestResult.autoTestExternalId,
198
+ statusCode: autotestResult.statusCode,
199
+ statusType: autotestResult.statusType,
200
+ });
209
201
  });
210
202
  }
211
203
  }
@@ -8,11 +8,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.BaseStrategy = void 0;
13
16
  const client_1 = require("../client");
14
17
  const utils_1 = require("../common/utils");
15
18
  const syncstorage_1 = require("../services/syncstorage");
19
+ const logger_1 = __importDefault(require("../logger"));
16
20
  class BaseStrategy {
17
21
  constructor(config) {
18
22
  this.config = config;
@@ -45,6 +49,14 @@ class BaseStrategy {
45
49
  }
46
50
  loadAutotest(autotest, status) {
47
51
  return __awaiter(this, void 0, void 0, function* () {
52
+ var _a, _b, _c, _d, _e, _f;
53
+ logger_1.default.debug("[strategy] loadAutotest", {
54
+ externalId: autotest.externalId,
55
+ status,
56
+ setupSteps: (_b = (_a = autotest.setup) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
57
+ testSteps: (_d = (_c = autotest.steps) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
58
+ teardownSteps: (_f = (_e = autotest.teardown) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0,
59
+ });
48
60
  yield this.client.autoTests.loadAutotest(autotest, status);
49
61
  if (Array.isArray(autotest.workItemIds)) {
50
62
  yield this.updateTestLinkToWorkItems(autotest.externalId, autotest.workItemIds);
@@ -75,7 +87,7 @@ class BaseStrategy {
75
87
  }
76
88
  }
77
89
  yield this.client.autoTests.linkToWorkItems(existingAutotest, workItemIds).catch((err) => {
78
- console.log("Failed link work items. \n", err);
90
+ logger_1.default.log("Failed link work items. \n", err);
79
91
  });
80
92
  });
81
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testit-js-commons",
3
- "version": "4.0.5",
3
+ "version": "4.1.0",
4
4
  "description": "JavaScript commons for Test IT",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",