@zohodesk/testinglibrary 0.0.64-n20-experimental → 0.0.66-n20-experimental

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.
Files changed (22) hide show
  1. package/build/common/data-generator/steps/DataGenerator.spec.js +8 -4
  2. package/build/common/data-generator/steps/DataGeneratorStepsHelper.js +2 -2
  3. package/build/core/dataGenerator/DataGenerator.js +9 -4
  4. package/build/core/dataGenerator/authenticator/Authenticator.js +35 -0
  5. package/build/core/dataGenerator/authenticator/CookieAuthenticator.js +41 -0
  6. package/build/core/dataGenerator/authenticator/JWTauthenticator.js +71 -0
  7. package/build/core/dataGenerator/authenticator/OAuthAuthenticator.js +31 -0
  8. package/build/core/dataGenerator/authenticator/OrgOAuthAuthenticator.js +29 -0
  9. package/build/core/playwright/builtInFixtures/context.js +3 -33
  10. package/build/core/playwright/readConfigFile.js +1 -3
  11. package/build/core/playwright/runner/SpawnRunner.js +1 -1
  12. package/build/core/playwright/setup/custom-reporter.js +1 -87
  13. package/build/test/core/dataGenerator/authenticator/__test__/Authenticator.test.js +89 -0
  14. package/build/test/core/dataGenerator/authenticator/__test__/JWTauthenticator.test.js +134 -0
  15. package/build/test/core/dataGenerator/authenticator/__test__/OAuthAuthenticator.test.js +46 -0
  16. package/build/test/core/dataGenerator/authenticator/__test__/OrgOAuthAuthenticator.test.js +46 -0
  17. package/build/test/core/dataGenerator/authenticator/__tests__/JWTauthenticator.test.js +165 -0
  18. package/jest.config.js +1 -1
  19. package/npm-shrinkwrap.json +2419 -1360
  20. package/package.json +1 -1
  21. package/unit_reports/unit-report.html +277 -0
  22. package/build/core/playwright/helpers/logCollector.js +0 -154
@@ -4,16 +4,20 @@ import { generateAndCacheTestData } from './DataGeneratorStepsHelper';
4
4
  const { Given } = createBdd();
5
5
 
6
6
  Given('generate a {string} entity {string} with generator {string}', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, generatorName, dataTable) => {
7
- await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName);
7
+ await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, null, page);
8
8
  });
9
9
 
10
10
  Given('generate a {string} entity {string} with API {string}', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, dataTable) => {
11
- await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName);
11
+ await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, null, page);
12
12
  });
13
13
  Given('generate a {string} entity {string} with generator {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, generatorName, profile, dataTable) => {
14
- await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, profile);
14
+ await generateAndCacheTestData(executionContext, "template", generatorName, dataTable, cacheLayer, entityName, profile, page);
15
15
  });
16
16
 
17
17
  Given('generate a {string} entity {string} with API {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, profile, dataTable) => {
18
- await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile);
18
+ await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile, page);
19
19
  });
20
+
21
+ Given('generate a {string} entity {string} with API {string} using {string} profile', async ({ page, context, i18N, cacheLayer, executionContext}, module, entityName, operationId, profile, dataTable) => {
22
+ await generateAndCacheTestData(executionContext, "API", operationId, dataTable, cacheLayer, entityName, profile, page);
23
+ });
@@ -4,7 +4,7 @@ import DataGenerator from '@zohodesk/testinglibrary/DataGenerator';
4
4
 
5
5
  const dataGenerator = new DataGenerator();
6
6
 
7
- export async function generateAndCacheTestData(executionContext, type, identifier, dataTable, cacheLayer, entityName, profile = null) {
7
+ export async function generateAndCacheTestData(executionContext, type, identifier, dataTable, cacheLayer, entityName, profile = null, page = null) {
8
8
  let actorInfo;
9
9
  const testInfo = test.info();
10
10
  const scenarioName = testInfo.title.split('/').pop() || 'Unknown Scenario';
@@ -16,6 +16,6 @@ export async function generateAndCacheTestData(executionContext, type, identifie
16
16
  actorInfo = executionContext.actorInfo;
17
17
  }
18
18
 
19
- const generatedData = await dataGenerator.generate(testInfo, actorInfo, type, identifier, scenarioName, dataTable ? dataTable.hashes() : []);
19
+ const generatedData = await dataGenerator.generate(testInfo, actorInfo, type, identifier, scenarioName, dataTable ? dataTable.hashes() : [], page);
20
20
  await cacheLayer.set(entityName, generatedData.data);
21
21
  }
@@ -9,6 +9,7 @@ var _path = _interopRequireDefault(require("path"));
9
9
  var _fs = _interopRequireDefault(require("fs"));
10
10
  var _logger = require("../../utils/logger");
11
11
  var _DataGeneratorHelper = require("./DataGeneratorHelper");
12
+ var _Authenticator = _interopRequireDefault(require("./authenticator/Authenticator.js"));
12
13
  var _helpers = require("@zohodesk/testinglibrary/helpers");
13
14
  var _DataGeneratorError = require("./DataGeneratorError");
14
15
  function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
@@ -19,7 +20,7 @@ class DataGenerator {
19
20
  constructor() {
20
21
  _classPrivateMethodInitSpec(this, _DataGenerator_brand);
21
22
  }
22
- async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable) {
23
+ async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable, page = null) {
23
24
  try {
24
25
  let generators;
25
26
  if (generatorType === 'API') {
@@ -28,7 +29,7 @@ class DataGenerator {
28
29
  generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this, testInfo, generatorName);
29
30
  }
30
31
  const processedGenerators = await (0, _DataGeneratorHelper.processGenerator)(generators, dataTable);
31
- const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo);
32
+ const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo, page);
32
33
  const response = await (0, _DataGeneratorHelper.makeRequest)(process.env.DG_SERVICE_DOMAIN + process.env.DG_SERVICE_API_PATH, apiPayload);
33
34
  _logger.Logger.log(_logger.Logger.INFO_TYPE, `Generated response for the generator: ${generatorName} for scenario: ${scenarioName}, Response: ${JSON.stringify(response)}`);
34
35
  return response;
@@ -72,12 +73,12 @@ async function _generateAPIGenerator(operationId) {
72
73
  name: operationId
73
74
  }];
74
75
  }
75
- async function _constructApiPayload(scenarioName, processedGenerators, actorInfo) {
76
+ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo, page = null) {
76
77
  const dataGeneratorObj = actorInfo['data-generator'];
77
78
  if (!dataGeneratorObj) {
78
79
  throw new _DataGeneratorError.DataGeneratorConfigurationError(`Data Generator configuration is missing for the profile: ${actorInfo['profile']}`);
79
80
  }
80
- const apiPayload = {
81
+ let apiPayload = {
81
82
  scenario_name: scenarioName,
82
83
  data_generation_templates: processedGenerators,
83
84
  ...dataGeneratorObj
@@ -94,6 +95,10 @@ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo
94
95
  environmentDetails.host = domainUrl.origin;
95
96
  }
96
97
  apiPayload.environmentDetails = environmentDetails;
98
+
99
+ // Only perform auth augmentation via Authenticator
100
+ apiPayload = await new _Authenticator.default().constructPayload(apiPayload, actorInfo, page);
101
+ console.log("Final API Payload after authentication augmentation: ", JSON.stringify(apiPayload));
97
102
  return apiPayload;
98
103
  }
99
104
  var _default = exports.default = DataGenerator;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
8
+ class Authenticator {
9
+ // Only route authentication augmentations; do not modify environment or base payload here.
10
+ async constructPayload(apiPayload, actorInfo, page = null) {
11
+ var _actorInfo$dataGener;
12
+ const authenticationLoaders = {
13
+ 'jwt-auth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./JWTauthenticator.js'))),
14
+ oauth2: () => Promise.resolve().then(() => _interopRequireWildcard(require('./OAuthAuthenticator.js'))),
15
+ 'org-oauth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./OrgOAuthAuthenticator.js'))),
16
+ 'cookie-auth': () => Promise.resolve().then(() => _interopRequireWildcard(require('./CookieAuthenticator.js')))
17
+ };
18
+ const authentications = (actorInfo === null || actorInfo === void 0 || (_actorInfo$dataGener = actorInfo['data-generator']) === null || _actorInfo$dataGener === void 0 || (_actorInfo$dataGener = _actorInfo$dataGener.account) === null || _actorInfo$dataGener === void 0 ? void 0 : _actorInfo$dataGener.authentications) || [];
19
+ const authenticationTypes = [...new Set(authentications.map(auth => String(auth).toLowerCase()).filter(Boolean))];
20
+ for (const authenticationType of authenticationTypes) {
21
+ const authenticatorLoader = authenticationLoaders[authenticationType];
22
+ if (!authenticatorLoader) {
23
+ continue;
24
+ }
25
+ const {
26
+ default: AuthenticatorClass
27
+ } = await authenticatorLoader();
28
+ const authenticatorInstance = new AuthenticatorClass();
29
+ apiPayload = await authenticatorInstance.constructPayload(apiPayload, actorInfo, page);
30
+ break;
31
+ }
32
+ return apiPayload;
33
+ }
34
+ }
35
+ exports.default = Authenticator;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
9
+ /**
10
+ * CookieAuthenticator prepares the payload for cookie-based authentication.
11
+ * It extracts cookies from the browser page context and attaches them
12
+ * as cookieAuthHeaders in the account block for the data generator service.
13
+ */
14
+ class CookieAuthenticator extends _Authenticator.default {
15
+ async constructPayload(apiPayload, actorInfo, page = null) {
16
+ apiPayload.account = apiPayload.account || {};
17
+ if (page) {
18
+ const {
19
+ crmcsrToken,
20
+ cookieString
21
+ } = await _getCookies.call(CookieAuthenticator, page);
22
+ apiPayload.account.authHeaders = {
23
+ Cookie: cookieString,
24
+ 'X-ZCSRF-TOKEN': crmcsrToken
25
+ };
26
+ }
27
+ return apiPayload;
28
+ }
29
+ }
30
+ async function _getCookies(page) {
31
+ const context = page.context();
32
+ const pageUrl = new URL(page.url());
33
+ const currentDomain = pageUrl.hostname;
34
+ const cookies = await context.cookies([page.url()]);
35
+ const crmcsrCookie = cookies.filter(cookie => cookie.domain.includes(currentDomain) && cookie.name === 'crmcsr');
36
+ return {
37
+ crmcsrToken: crmcsrCookie.map(c => `crmcsrfparam=${c.value}`).join('; '),
38
+ cookieString: cookies.map(c => `${c.name}=${c.value}`).join('; ')
39
+ };
40
+ }
41
+ var _default = exports.default = CookieAuthenticator;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
9
+ /**
10
+ * JWTauthenticator constructs payload by fetching a JWT access token
11
+ * from the authenticator app and attaching it to the account.jwt config.
12
+ */
13
+ class JWTauthenticator extends _Authenticator.default {
14
+ async constructPayload(apiPayload, actorInfo) {
15
+ const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
16
+ const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
17
+ const jwtConf = account === null || account === void 0 ? void 0 : account['jwt-oauth'];
18
+
19
+ // If jwt config is not present, nothing to do
20
+ if (!jwtConf) {
21
+ return apiPayload;
22
+ }
23
+
24
+ // Build payload for authenticator service
25
+ const userToken = encodeURIComponent((actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo.email) || '');
26
+ const jwtAuthUrl = process.env.DG_JWT_AUTH_URL;
27
+ const responseType = jwtConf.response_type;
28
+ const clientId = jwtConf.client_id;
29
+
30
+ // Resolve token_url from config or settings template
31
+ let tokenUrl = jwtConf.token_url;
32
+ if (!tokenUrl && process.env.DG_JWT_TOKEN_URL && clientId && clientId.includes('.')) {
33
+ const zsoid = clientId.split('.')[0];
34
+ tokenUrl = process.env.DG_JWT_TOKEN_URL.replace('{ZSOID}', zsoid);
35
+ }
36
+ const domainUrl = jwtConf.domain_url || process.env.DG_DOMAIN_URL;
37
+ const authPayload = {
38
+ user_token: userToken,
39
+ jwt_secret: jwtConf.jwt_secret,
40
+ jwt_auth_url: jwtAuthUrl,
41
+ response_type: responseType,
42
+ client_id: clientId,
43
+ token_url: tokenUrl,
44
+ domain_url: domainUrl
45
+ };
46
+ const authenticatorApi = process.env.DG_JWT_AUTHENTICATOR_API;
47
+ const resp = await fetch(authenticatorApi, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json'
51
+ },
52
+ body: JSON.stringify(authPayload)
53
+ });
54
+ if (!resp.ok) {
55
+ const errorBody = await resp.text();
56
+ throw new Error(`JWT authenticator error! status: ${resp.status}, body: ${errorBody}`);
57
+ }
58
+ const json = await resp.json();
59
+ const accessToken = json === null || json === void 0 ? void 0 : json.access_token;
60
+ if (!accessToken) {
61
+ throw new Error('JWT authenticator did not return access_token');
62
+ }
63
+
64
+ // Attach access token to authHeaders so downstream API calls can use it directly
65
+ apiPayload.account = apiPayload.account || {};
66
+ apiPayload.account.authHeaders = apiPayload.account.authHeaders || {};
67
+ apiPayload.account.authHeaders.Authorization = `Zoho-oauthtoken ${accessToken}`;
68
+ return apiPayload;
69
+ }
70
+ }
71
+ var _default = exports.default = JWTauthenticator;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
9
+ /**
10
+ * OAuthAuthenticator prepares the payload for OAuth2-based authentication.
11
+ * It ensures oauth2 config is present in the account block so the
12
+ * data generator service can use it.
13
+ */
14
+ class OAuthAuthenticator extends _Authenticator.default {
15
+ async constructPayload(apiPayload, actorInfo) {
16
+ const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
17
+ const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
18
+
19
+ // If oauth2 details exist in actor info, merge them into payload
20
+ const oauth2 = account === null || account === void 0 ? void 0 : account.oauth2;
21
+ apiPayload.account = apiPayload.account || {};
22
+ if (oauth2) {
23
+ apiPayload.account.oauth2 = {
24
+ ...(apiPayload.account.oauth2 || {}),
25
+ ...oauth2
26
+ };
27
+ }
28
+ return apiPayload;
29
+ }
30
+ }
31
+ var _default = exports.default = OAuthAuthenticator;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _Authenticator = _interopRequireDefault(require("./Authenticator.js"));
9
+ /**
10
+ * OrgOAuthAuthenticator prepares the payload for org_oauth authentication.
11
+ * It ensures org_oauth config is present in the account block so the
12
+ * data generator service can use it.
13
+ */
14
+ class OrgOAuthAuthenticator extends _Authenticator.default {
15
+ async constructPayload(apiPayload, actorInfo) {
16
+ const dg = (actorInfo === null || actorInfo === void 0 ? void 0 : actorInfo['data-generator']) || {};
17
+ const account = (dg === null || dg === void 0 ? void 0 : dg.account) || (apiPayload === null || apiPayload === void 0 ? void 0 : apiPayload.account) || {};
18
+ const orgOauth = account === null || account === void 0 ? void 0 : account.org_oauth;
19
+ apiPayload.account = apiPayload.account || {};
20
+ if (orgOauth) {
21
+ apiPayload.account.org_oauth = {
22
+ ...(apiPayload.account.org_oauth || {}),
23
+ ...orgOauth
24
+ };
25
+ }
26
+ return apiPayload;
27
+ }
28
+ }
29
+ var _default = exports.default = OrgOAuthAuthenticator;
@@ -1,19 +1,13 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.default = void 0;
8
- var _path = _interopRequireDefault(require("path"));
9
7
  var _readConfigFile = require("../readConfigFile");
10
- var _logCollector = require("../helpers/logCollector");
11
8
  const {
12
- testSetup,
13
- reportPath,
14
- runtimeLogs = {}
9
+ testSetup
15
10
  } = (0, _readConfigFile.generateConfigFromFile)();
16
- const LOGS_DIR = _path.default.join(reportPath, 'runtime-logs');
17
11
  async function performDefaultContextSteps({
18
12
  context
19
13
  }) {
@@ -26,37 +20,13 @@ async function performDefaultContextSteps({
26
20
  var _default = exports.default = {
27
21
  context: async ({
28
22
  context
29
- }, use, testInfo) => {
23
+ }, use) => {
30
24
  await context.addInitScript(() =>
31
25
  // eslint-disable-next-line no-undef
32
26
  window.localStorage.setItem('isDnBannerHide', true));
33
27
  await performDefaultContextSteps({
34
28
  context
35
29
  });
36
- const collector = new _logCollector.LogCollector(runtimeLogs.limits);
37
- collector.attachToContext(context);
38
- testInfo.runtimeLogs = collector;
39
- try {
40
- await use(context);
41
- } finally {
42
- collector.detach();
43
- const failureOnly = runtimeLogs.mode === 'failure-only';
44
- const isFailure = testInfo.status && testInfo.status !== testInfo.expectedStatus;
45
- const shouldFlush = !failureOnly || isFailure;
46
- const summary = collector.toSummary();
47
- let filePath = null;
48
- if (shouldFlush) {
49
- const base = `w${testInfo.workerIndex}-${testInfo.testId}-${testInfo.retry}`;
50
- filePath = collector.flushToFile(LOGS_DIR, base);
51
- }
52
- await testInfo.attach('runtime-log', {
53
- contentType: 'application/json',
54
- body: Buffer.from(JSON.stringify({
55
- schemaVersion: 1,
56
- summary,
57
- file: filePath ? _path.default.relative(reportPath, filePath) : null
58
- }))
59
- });
60
- }
30
+ await use(context);
61
31
  }
62
32
  };
@@ -55,8 +55,7 @@ function getDefaultConfig() {
55
55
  featureFilesFolder: 'feature-files',
56
56
  stepDefinitionsFolder: 'steps',
57
57
  testSetup: {},
58
- editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
59
- showCaseTimings: true
58
+ editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
60
59
  };
61
60
  }
62
61
  function combineDefaultConfigWithUserConfig(userConfiguration) {
@@ -114,7 +113,6 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
114
113
  * @property {string} testIdAttribute: Change the default data-testid attribute. configure what attribute to search while calling getByTestId
115
114
  * @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
116
115
  * @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
117
- * @property {boolean} showCaseTimings: When true, the console reporter prints per-case start/end wall-clock times (h:mm:ss AM/PM) and a case-timings.json sidecar is written to reportPath for the HTML report addon. Default: true.
118
116
  */
119
117
 
120
118
  /**
@@ -28,7 +28,7 @@ class SpawnRunner extends _Runner.default {
28
28
  }
29
29
  Promise.all(promises).then(() => this.runPlaywright()).catch(err => {
30
30
  _logger.Logger.error(err);
31
- process.exit();
31
+ process.exit(1);
32
32
  });
33
33
  }
34
34
 
@@ -12,22 +12,6 @@ var _readConfigFile = require("../readConfigFile");
12
12
  var _logger = require("../../../utils/logger");
13
13
  var _configFileNameProvider = require("../helpers/configFileNameProvider");
14
14
  var _mergeAbortedTests = _interopRequireDefault(require("../reporter/helpers/mergeAbortedTests"));
15
- function formatTime(date) {
16
- return date.toLocaleTimeString('en-US', {
17
- hour: 'numeric',
18
- minute: '2-digit',
19
- second: '2-digit',
20
- hour12: true
21
- });
22
- }
23
- function formatDuration(ms) {
24
- if (ms < 1000) return `${ms}ms`;
25
- const totalSeconds = ms / 1000;
26
- if (totalSeconds < 60) return `${totalSeconds.toFixed(2)}s`;
27
- const minutes = Math.floor(totalSeconds / 60);
28
- const seconds = (totalSeconds - minutes * 60).toFixed(2);
29
- return `${minutes}m ${seconds}s`;
30
- }
31
15
  class JSONSummaryReporter {
32
16
  constructor() {
33
17
  this.durationInMS = -1;
@@ -42,22 +26,11 @@ class JSONSummaryReporter {
42
26
  this.failedSteps = [];
43
27
  this.status = 'unknown';
44
28
  this.startedAt = 0;
45
- const config = (0, _readConfigFile.generateConfigFromFile)();
46
- this._open = config.openReportOn;
47
- this._showCaseTimings = config.showCaseTimings !== false;
48
- this._caseTimings = [];
29
+ this._open = (0, _readConfigFile.generateConfigFromFile)().openReportOn;
49
30
  }
50
31
  onBegin() {
51
32
  this.startedAt = Date.now();
52
33
  }
53
- onTestBegin(test) {
54
- if (!this._showCaseTimings) return;
55
- const {
56
- fullTitle
57
- } = this.getTitle(test);
58
- const startedAtLabel = formatTime(new Date());
59
- _logger.Logger.log(_logger.Logger.INFO_TYPE, `▶ ${fullTitle} — started at ${startedAtLabel}`);
60
- }
61
34
  getTitle(test) {
62
35
  const title = [];
63
36
  const fileName = [];
@@ -103,55 +76,6 @@ class JSONSummaryReporter {
103
76
  this[status].push(fileName);
104
77
  }
105
78
  this[status].push(fileName);
106
- if (this._showCaseTimings && result.startTime) {
107
- const startDate = new Date(result.startTime);
108
- const endDate = new Date(startDate.getTime() + (result.duration || 0));
109
- const startLabel = formatTime(startDate);
110
- const endLabel = formatTime(endDate);
111
- const durationLabel = formatDuration(result.duration || 0);
112
- const statusGlyph = result.status === 'passed' ? '✓' : result.status === 'skipped' ? '○' : '✗';
113
- const logType = result.status === 'passed' ? _logger.Logger.SUCCESS_TYPE : result.status === 'skipped' ? _logger.Logger.INFO_TYPE : _logger.Logger.FAILURE_TYPE;
114
- _logger.Logger.log(logType, `${statusGlyph} ${fullTitle} — ended at ${endLabel} (started ${startLabel}, took ${durationLabel})`);
115
- const isFailure = result.status !== 'passed' && result.status !== 'skipped';
116
- if (isFailure) {
117
- var _result$error;
118
- const runtimeAttachment = (result.attachments || []).find(a => a.name === 'runtime-log' && a.body);
119
- let runtime = null;
120
- if (runtimeAttachment) {
121
- try {
122
- runtime = JSON.parse(runtimeAttachment.body.toString('utf8'));
123
- } catch {
124
- // malformed attachment; skip
125
- }
126
- }
127
- this._caseTimings.push({
128
- title: fullTitle,
129
- fileName,
130
- status: result.status,
131
- retry: result.retry,
132
- startTime: startDate.toISOString(),
133
- endTime: endDate.toISOString(),
134
- startTimeFormatted: startLabel,
135
- endTimeFormatted: endLabel,
136
- duration: result.duration || 0,
137
- durationFormatted: durationLabel,
138
- errorMessage: (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.message,
139
- failedSteps: (result.steps || []).filter(step => step.error).map(step => {
140
- var _step$error;
141
- return {
142
- title: step.title,
143
- error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
144
- };
145
- }),
146
- ...(runtime ? {
147
- runtime: {
148
- summary: runtime.summary,
149
- logFile: runtime.file
150
- }
151
- } : {})
152
- });
153
- }
154
- }
155
79
  }
156
80
  onError(error) {
157
81
  this.errored.push({
@@ -198,16 +122,6 @@ class JSONSummaryReporter {
198
122
  reportPath
199
123
  } = (0, _readConfigFile.generateConfigFromFile)();
200
124
  (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, './', (0, _configFileNameProvider.getReportFileName)()), JSON.stringify(this, null, ' '));
201
- if (this._showCaseTimings && this._caseTimings.length > 0) {
202
- const timingsPayload = {
203
- suiteStartedAt: new Date(this.startedAt).toISOString(),
204
- suiteEndedAt: new Date(this.startedAt + this.durationInMS).toISOString(),
205
- suiteDurationMs: this.durationInMS,
206
- failedCount: this._caseTimings.length,
207
- cases: this._caseTimings
208
- };
209
- (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, 'case-timings.json'), JSON.stringify(timingsPayload, null, ' '));
210
- }
211
125
  }
212
126
  onExit() {
213
127
  // Update .last-run.json with aborted tests due to timing out or interruption
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _Authenticator = _interopRequireDefault(require("../../../../../../src/core/dataGenerator/authenticator/Authenticator"));
5
+ describe('Authenticator', () => {
6
+ it('should return unchanged payload when no supported authentication type exists', async () => {
7
+ const authenticator = new _Authenticator.default();
8
+ const apiPayload = {
9
+ account: {
10
+ name: 'sample'
11
+ }
12
+ };
13
+ const actorInfo = {
14
+ 'data-generator': {
15
+ account: {
16
+ authentications: ['apikey', 'basic']
17
+ }
18
+ }
19
+ };
20
+ const result = await authenticator.constructPayload(apiPayload, actorInfo);
21
+ expect(result).toEqual(apiPayload);
22
+ });
23
+ it('should route oauth2 authentication and merge oauth2 config into payload', async () => {
24
+ const authenticator = new _Authenticator.default();
25
+ const apiPayload = {
26
+ account: {}
27
+ };
28
+ const actorInfo = {
29
+ 'data-generator': {
30
+ account: {
31
+ authentications: ['oauth2'],
32
+ oauth2: {
33
+ client_id: 'client_1',
34
+ client_secret: 'secret_1'
35
+ }
36
+ }
37
+ }
38
+ };
39
+ const result = await authenticator.constructPayload(apiPayload, actorInfo);
40
+ expect(result.account.oauth2).toEqual({
41
+ client_id: 'client_1',
42
+ client_secret: 'secret_1'
43
+ });
44
+ });
45
+ it('should treat authentication values case-insensitively and deduplicate them', async () => {
46
+ const authenticator = new _Authenticator.default();
47
+ const apiPayload = {
48
+ account: {}
49
+ };
50
+ const actorInfo = {
51
+ 'data-generator': {
52
+ account: {
53
+ authentications: ['OAUTH2', 'oauth2', 'OAuth2'],
54
+ oauth2: {
55
+ scope: 'read'
56
+ }
57
+ }
58
+ }
59
+ };
60
+ const result = await authenticator.constructPayload(apiPayload, actorInfo);
61
+ expect(result.account.oauth2).toEqual({
62
+ scope: 'read'
63
+ });
64
+ });
65
+ it('should apply only the first supported authentication type', async () => {
66
+ const authenticator = new _Authenticator.default();
67
+ const apiPayload = {
68
+ account: {}
69
+ };
70
+ const actorInfo = {
71
+ 'data-generator': {
72
+ account: {
73
+ authentications: ['oauth2', 'org-oauth'],
74
+ oauth2: {
75
+ client_id: 'oauth-client'
76
+ },
77
+ org_oauth: {
78
+ org_id: 'org-101'
79
+ }
80
+ }
81
+ }
82
+ };
83
+ const result = await authenticator.constructPayload(apiPayload, actorInfo);
84
+ expect(result.account.oauth2).toEqual({
85
+ client_id: 'oauth-client'
86
+ });
87
+ expect(result.account.org_oauth).toBeUndefined();
88
+ });
89
+ });