@elisra-devops/docgen-data-provider 1.140.0 → 1.141.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.
Files changed (44) hide show
  1. package/.github/workflows/ci.yml +2 -0
  2. package/bin/helpers/tfs.js +21 -14
  3. package/bin/helpers/tfs.js.map +1 -1
  4. package/bin/modules/JfrogDataProvider.js +1 -1
  5. package/bin/modules/JfrogDataProvider.js.map +1 -1
  6. package/bin/modules/PipelinesDataProvider.js +1 -1
  7. package/bin/modules/PipelinesDataProvider.js.map +1 -1
  8. package/bin/modules/ResultDataProvider.js +21 -15
  9. package/bin/modules/ResultDataProvider.js.map +1 -1
  10. package/bin/modules/TicketsDataProvider.js +13 -10
  11. package/bin/modules/TicketsDataProvider.js.map +1 -1
  12. package/bin/tests/modules/JfrogDataProvider.test.js +3 -1
  13. package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -1
  14. package/bin/tests/modules/ResultDataProvider.test.js +6 -2
  15. package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
  16. package/bin/tests/utils/logger.test.d.ts +1 -0
  17. package/bin/tests/utils/logger.test.js +238 -0
  18. package/bin/tests/utils/logger.test.js.map +1 -0
  19. package/bin/tests/utils/runContext.test.d.ts +1 -0
  20. package/bin/tests/utils/runContext.test.js +51 -0
  21. package/bin/tests/utils/runContext.test.js.map +1 -0
  22. package/bin/utils/logSink.d.ts +25 -0
  23. package/bin/utils/logSink.js +16 -0
  24. package/bin/utils/logSink.js.map +1 -0
  25. package/bin/utils/logger.d.ts +6 -0
  26. package/bin/utils/logger.js +219 -27
  27. package/bin/utils/logger.js.map +1 -1
  28. package/bin/utils/runContext.d.ts +8 -0
  29. package/bin/utils/runContext.js +12 -0
  30. package/bin/utils/runContext.js.map +1 -0
  31. package/eslint.config.mjs +40 -0
  32. package/package.json +8 -3
  33. package/src/helpers/tfs.ts +19 -15
  34. package/src/modules/JfrogDataProvider.ts +1 -1
  35. package/src/modules/PipelinesDataProvider.ts +1 -1
  36. package/src/modules/ResultDataProvider.ts +20 -15
  37. package/src/modules/TicketsDataProvider.ts +15 -14
  38. package/src/tests/modules/JfrogDataProvider.test.ts +3 -1
  39. package/src/tests/modules/ResultDataProvider.test.ts +6 -2
  40. package/src/tests/utils/logger.test.ts +270 -0
  41. package/src/tests/utils/runContext.test.ts +48 -0
  42. package/src/utils/logSink.ts +44 -0
  43. package/src/utils/logger.ts +229 -29
  44. package/src/utils/runContext.ts +25 -0
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,mCAAmC;AAEnC,IAAI,MAAsB,CAAC;AAE3B,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,MAAM,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AACxG,uCAAuC;AACvC,uDAAuD;AAEvD,oCAAoC;AACpC,0CAA0C;AAC1C,sBAAsB;AACtB,oBAAoB;AACpB,sCAAsC;AACtC,4DAA4D;AAC5D,0BAA0B;AAC1B,6BAA6B;AAC7B,YAAY;AACZ,sCAAsC;AACtC,yDAAyD;AACzD,6BAA6B;AAC7B,YAAY;AACZ,+EAA+E;AAC/E,SAAS;AACT,QAAQ;AACR,WAAW;AACX,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE;IAClC,KAAK,EAAE,OAAO;IACd,UAAU,EAAE;QACV,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QACrE,6DAA6D;KAC9D;CACF,CAAC,CAAC;AACH,IAAI;AAEJ,kBAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ export interface RunContext {
3
+ runId: string;
4
+ captureMode?: 'verbose' | 'retain-on-failure';
5
+ docType?: string;
6
+ project?: string;
7
+ }
8
+ export declare const runContextStore: AsyncLocalStorage<RunContext>;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+ var _a;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.runContextStore = void 0;
5
+ const async_hooks_1 = require("async_hooks");
6
+ // Symbol.for uses the global symbol registry, so every duplicated copy of this file across
7
+ // the DocGen packages — hoisted or nested at any depth by npm — converges on the same
8
+ // AsyncLocalStorage instance. Keying by module identity instead would silently split into
9
+ // two stores and runId would go missing with no visible error.
10
+ const KEY = Symbol.for('elisradevops.docgen.runContext');
11
+ exports.runContextStore = (_a = globalThis[KEY]) !== null && _a !== void 0 ? _a : (globalThis[KEY] = new async_hooks_1.AsyncLocalStorage());
12
+ //# sourceMappingURL=runContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runContext.js","sourceRoot":"","sources":["../../src/utils/runContext.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;AACb,6CAAgD;AAehD,2FAA2F;AAC3F,sFAAsF;AACtF,0FAA0F;AAC1F,+DAA+D;AAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAE5C,QAAA,eAAe,GAC1B,MAAE,UAAsC,CAAC,GAAG,CAA+C,mCAC3F,CAAE,UAAsC,CAAC,GAAG,CAAC,GAAG,IAAI,+BAAiB,EAAc,CAAC,CAAC"}
@@ -0,0 +1,40 @@
1
+ import tseslint from 'typescript-eslint';
2
+ import globals from 'globals';
3
+
4
+ // Phase 4 regression guard: bans the two log-record-corrupting patterns the observability plan
5
+ // found and fixed by hand across all four backend repos. Deliberately does NOT extend
6
+ // js.configs.recommended/tseslint's recommended rule sets — this repo has never had a linter,
7
+ // and pulling in a general rule set now would surface a large, unrelated pre-existing backlog
8
+ // (unused vars, etc.) as new lint failures. Scope is exactly the one regression this guards
9
+ // against. Keep this file identical in shape across docgen-api-gate / docgen-content-control /
10
+ // docgen-data-provider-package / docgen-dg-skins-package — see each repo's CLAUDE.md "keep
11
+ // these files in sync" note.
12
+ export default tseslint.config(
13
+ {
14
+ ignores: ['bin/**', 'coverage/**', '**/test/**', '**/tests/**'],
15
+ },
16
+ {
17
+ files: ['src/**/*.ts'],
18
+ extends: [tseslint.configs.base],
19
+ languageOptions: {
20
+ globals: globals.node,
21
+ },
22
+ rules: {
23
+ 'no-restricted-syntax': [
24
+ 'error',
25
+ {
26
+ selector:
27
+ "CallExpression[callee.object.name='logger'] > :matches(TemplateLiteral, BinaryExpression[operator='+']).arguments:first-child MemberExpression[property.name='stack']",
28
+ message:
29
+ 'Do not embed .stack in the log message string — errors({stack:true}) attaches it automatically when the Error is passed as an argument. Pass the Error itself as a positional/meta argument instead.',
30
+ },
31
+ {
32
+ selector:
33
+ "CallExpression[callee.object.name='logger'] CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] > Identifier.arguments:first-child[name=/^(e|err|error|ex)$/]",
34
+ message:
35
+ "Don't JSON.stringify an Error — message/stack are non-enumerable and it serializes to {}. Pass the Error object directly as a log argument.",
36
+ },
37
+ ],
38
+ },
39
+ }
40
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.140.0",
3
+ "version": "1.141.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,9 +11,11 @@
11
11
  "types": "./bin/index.d.ts",
12
12
  "scripts": {
13
13
  "test": "jest --coverage --runInBand --forceExit",
14
- "build": "tsc"
14
+ "build": "tsc",
15
+ "lint": "eslint ."
15
16
  },
16
17
  "devDependencies": {
18
+ "@eslint/js": "^9.39.5",
17
19
  "@types/babel__generator": "^7.6.8",
18
20
  "@types/babel__template": "^7.4.4",
19
21
  "@types/caseless": "^0.12.5",
@@ -24,11 +26,14 @@
24
26
  "@types/yargs-parser": "^21.0.3",
25
27
  "baseline-browser-mapping": "^2.9.0",
26
28
  "dotenv": "^16.4.5",
29
+ "eslint": "^9.39.5",
30
+ "globals": "^15.15.0",
27
31
  "jest": "^29.7.0",
28
32
  "jest-junit": "^16.0.0",
29
33
  "npm": "^10.8.2",
30
34
  "ts-jest": "^29.2.3",
31
- "typescript": "^5.5.4"
35
+ "typescript": "^5.5.4",
36
+ "typescript-eslint": "^8.71.0"
32
37
  },
33
38
  "dependencies": {
34
39
  "@types/xml2js": "^0.4.5",
@@ -248,7 +248,7 @@ export class TFSServices {
248
248
 
249
249
  // Log error if needed
250
250
  if (printError) {
251
- this.logDetailedError(e, url);
251
+ this.logDetailedError(e, url, attempts);
252
252
  }
253
253
 
254
254
  throw e;
@@ -286,23 +286,27 @@ export class TFSServices {
286
286
  /**
287
287
  * Log detailed error information
288
288
  */
289
- private static logDetailedError(error: any, url: string): void {
290
- if (error.response) {
291
- logger.error(`Error for ${url} - ${error.message}`);
292
- logger.error(`Status: ${error.response.status}`);
293
-
294
- if (error.response.data) {
295
- if (typeof error.response.data === 'string') {
296
- logger.error(`Response: ${error.response.data.substring(0, 200)}`);
297
- } else {
298
- const dataMessage =
299
- error.response.data.message || JSON.stringify(error.response.data).substring(0, 200);
300
- logger.error(`Response: ${dataMessage}`);
289
+ private static logDetailedError(error: any, url: string, attempt?: number): void {
290
+ let responseExcerpt: string | undefined;
291
+ if (error.response?.data) {
292
+ if (typeof error.response.data === 'string') {
293
+ responseExcerpt = error.response.data.substring(0, 200);
294
+ } else {
295
+ try {
296
+ responseExcerpt = error.response.data.message || JSON.stringify(error.response.data).substring(0, 200);
297
+ } catch {
298
+ responseExcerpt = '[unserializable response data]';
301
299
  }
302
300
  }
303
- } else {
304
- logger.error(`Error for ${url} - ${error.message}`);
305
301
  }
302
+ logger.error('ADO request failed', {
303
+ message: error.message,
304
+ stack: error.stack,
305
+ url,
306
+ status: error.response?.status,
307
+ responseExcerpt,
308
+ attempt,
309
+ });
306
310
  }
307
311
 
308
312
  private static getErrorMessage(error: any): string {
@@ -19,7 +19,7 @@ export default class JfrogDataProvider {
19
19
  if (!serviceConnectionResponse?.url) {
20
20
  throw new Error(`Service connection ${connectionId} returned no URL — identity may lack service-endpoint read permission`);
21
21
  }
22
- logger.debug(`service connection url ${JSON.stringify(serviceConnectionResponse.url)}`);
22
+ logger.debug(`service connection url ${serviceConnectionResponse.url}`);
23
23
  return serviceConnectionResponse.url;
24
24
  }
25
25
 
@@ -1252,7 +1252,7 @@ export default class PipelinesDataProvider {
1252
1252
  },
1253
1253
  parameters: parameter, //'{"Test":"123"}'
1254
1254
  };
1255
- logger.info(JSON.stringify(data));
1255
+ logger.info('Triggering build', data);
1256
1256
  let url = `${this.orgUrl}${projectName}/_apis/build/builds?api-version=5.0`;
1257
1257
  let res = await TFSServices.postRequest(url, this.token, 'post', data, null);
1258
1258
  return res;
@@ -259,10 +259,22 @@ export default class ResultDataProvider {
259
259
 
260
260
  return { combinedResults, openPcrToTestCaseTraceMap, testCaseToOpenPcrTraceMap };
261
261
  } catch (error: any) {
262
- logger.error(`Error during getCombinedResultsSummary: ${error.message}`);
262
+ // One event, one record — this used to be two separate calls (message, then
263
+ // conditionally the response body), which interleave with other requests' output
264
+ // under concurrency and become two unrelated entries in the dashboard's error grouping.
265
+ let responseSummary: string | undefined;
263
266
  if (error.response) {
264
- logger.error(`Response Data: ${JSON.stringify(error.response.data)}`);
267
+ // Truncated to match the bound already used for this in tfs.ts — an untruncated ADO
268
+ // response body isn't a credential, but it can still be large/sensitive work-item content
269
+ // and doesn't need to go into logs unbounded. JSON.stringify itself (not just the
270
+ // output) can throw on a circular value, so that has to be guarded too, not just capped.
271
+ try {
272
+ responseSummary = JSON.stringify(error.response.data).substring(0, 200);
273
+ } catch {
274
+ responseSummary = '[unserializable response data]';
275
+ }
265
276
  }
277
+ logger.error(`Error during getCombinedResultsSummary: ${error.message}`, { responseSummary });
266
278
  // Ensure the error is rethrown to propagate it correctly
267
279
  throw new Error(error.message || 'Unknown error occurred during getCombinedResultsSummary');
268
280
  }
@@ -3261,8 +3273,7 @@ export default class ResultDataProvider {
3261
3273
  );
3262
3274
  return detailedPoints;
3263
3275
  } catch (err: any) {
3264
- logger.error(`Error during fetching Cross Test Points: ${err.message}`);
3265
- logger.error(`Error stack: ${err.stack}`);
3276
+ logger.error('Error during fetching Cross Test Points', err);
3266
3277
  return [];
3267
3278
  }
3268
3279
  }
@@ -3903,10 +3914,7 @@ export default class ResultDataProvider {
3903
3914
  relatedCRs,
3904
3915
  };
3905
3916
  } catch (error: any) {
3906
- logger.error(`Error while fetching run result: ${error.message}`);
3907
- if (isTestReporter) {
3908
- logger.error(`Error stack: ${error.stack}`);
3909
- }
3917
+ logger.error('Error while fetching run result', error);
3910
3918
  return null;
3911
3919
  }
3912
3920
  }
@@ -4595,7 +4603,7 @@ export default class ResultDataProvider {
4595
4603
  ? testCase.workItem.workItemFields
4596
4604
  : [];
4597
4605
  if (testCaseWorkItemFields.length === 0) {
4598
- logger.warn(`Could not fetch the steps from WI ${JSON.stringify(testCase.workItem.id)}`);
4606
+ logger.warn(`Could not fetch the steps from WI ${testCase.workItem.id}`);
4599
4607
  if (!isTestReporter) {
4600
4608
  continue;
4601
4609
  }
@@ -5058,8 +5066,7 @@ export default class ResultDataProvider {
5058
5066
  ? createResponseObject(resultData, testSuiteId, point, ...additionalArgs)
5059
5067
  : null;
5060
5068
  } catch (error: any) {
5061
- logger.error(`Error occurred for point ${point.testCaseId}: ${error.message}`);
5062
- logger.error(`Stack trace: ${error.stack}`);
5069
+ logger.error(`Error occurred for point ${point.testCaseId}`, error);
5063
5070
  return null;
5064
5071
  }
5065
5072
  }
@@ -5153,8 +5160,7 @@ export default class ResultDataProvider {
5153
5160
  summarizedItemMap.set(wi.id, mappedItem);
5154
5161
  }
5155
5162
  } catch (error: any) {
5156
- logger.error(`Error occurred while fetching linked work items: ${error.message}`);
5157
- logger.error(`Error Stack: ${error.stack}`);
5163
+ logger.error('Error occurred while fetching linked work items', error);
5158
5164
  }
5159
5165
  }
5160
5166
 
@@ -5826,8 +5832,7 @@ export default class ResultDataProvider {
5826
5832
  }
5827
5833
  return resultDataResponse;
5828
5834
  } catch (err: any) {
5829
- logger.error(`Error occurred while fetching result data: ${err.message}`);
5830
- logger.error(`Error stack: ${err.stack}`);
5835
+ logger.error('Error occurred while fetching result data', err);
5831
5836
  return null;
5832
5837
  }
5833
5838
  },
@@ -783,11 +783,9 @@ export default class TicketsDataProvider {
783
783
  : null;
784
784
  } catch (err: any) {
785
785
  logger.error(
786
- `Error occurred while constructing the customer query list ${err.message} with query ${JSON.stringify(
787
- rootQuery,
788
- )}`,
786
+ `Error occurred while constructing the customer query list with query ${JSON.stringify(rootQuery)}`,
787
+ err,
789
788
  );
790
- logger.error(`Error stack ${err.message}`);
791
789
  return null;
792
790
  }
793
791
  }
@@ -2697,8 +2695,10 @@ export default class TicketsDataProvider {
2697
2695
  const subRes = await postBatch(currentIds);
2698
2696
  res.push(...subRes.value);
2699
2697
  } catch (error) {
2700
- logger.error(`error populating workitems array`);
2701
- logger.error(JSON.stringify(error));
2698
+ // JSON.stringify(error) on an AxiosError calls its own .toJSON(), which serializes
2699
+ // config.auth.password / config.headers.Authorization — the PAT, in plaintext. Pass
2700
+ // the error as meta instead and let the logger's redact format scrub it.
2701
+ logger.error(`error populating workitems array`, error);
2702
2702
  return [];
2703
2703
  }
2704
2704
  }
@@ -2708,8 +2708,10 @@ export default class TicketsDataProvider {
2708
2708
  const subRes = await postBatch(currentIds);
2709
2709
  res.push(...subRes.value);
2710
2710
  } catch (error) {
2711
- logger.error(`error populating workitems array`);
2712
- logger.error(JSON.stringify(error));
2711
+ // JSON.stringify(error) on an AxiosError calls its own .toJSON(), which serializes
2712
+ // config.auth.password / config.headers.Authorization — the PAT, in plaintext. Pass
2713
+ // the error as meta instead and let the logger's redact format scrub it.
2714
+ logger.error(`error populating workitems array`, error);
2713
2715
  return [];
2714
2716
  }
2715
2717
  }
@@ -2843,8 +2845,9 @@ export default class TicketsDataProvider {
2843
2845
  );
2844
2846
  return attachmentList;
2845
2847
  } catch (e) {
2846
- logger.error(`error fetching attachments for work item ${id}`);
2847
- logger.error(`${JSON.stringify(e)}`);
2848
+ // JSON.stringify(e) on an Error returns "{}" — own enumerable props only, message/stack
2849
+ // are non-enumerable — so this logged literally nothing useful. Pass it as meta instead.
2850
+ logger.error(`error fetching attachments for work item ${id}`, e);
2848
2851
  return [];
2849
2852
  }
2850
2853
  }
@@ -3161,11 +3164,9 @@ export default class TicketsDataProvider {
3161
3164
  return { tree1: tree1Node, tree2: tree2Node };
3162
3165
  } catch (err: any) {
3163
3166
  logger.error(
3164
- `Error occurred while constructing the query list ${err.message} with query ${JSON.stringify(
3165
- rootQuery,
3166
- )}`,
3167
+ `Error occurred while constructing the query list with query ${JSON.stringify(rootQuery)}`,
3168
+ err,
3167
3169
  );
3168
- logger.error(`Error stack ${err.message}`);
3169
3170
  }
3170
3171
  }
3171
3172
 
@@ -36,7 +36,9 @@ describe('JfrogDataProvider', () => {
36
36
  `${mockOrgUrl}${mockTeamProject}/_apis/serviceendpoint/endpoints/${mockConnectionId}?api-version=7.1`,
37
37
  mockTfsToken
38
38
  );
39
- expect(logger.debug).toHaveBeenCalledWith(`service connection url "${mockResponse.url}"`);
39
+ // The log used JSON.stringify(url), which wraps a plain string in quotes for no reason —
40
+ // fixed to interpolate the string directly (see docgen-log-redaction / Phase 2).
41
+ expect(logger.debug).toHaveBeenCalledWith(`service connection url ${mockResponse.url}`);
40
42
  expect(result).toBe(mockResponse.url);
41
43
  });
42
44
 
@@ -6027,8 +6027,12 @@ describe('ResultDataProvider', () => {
6027
6027
  resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName)
6028
6028
  ).rejects.toThrow('boom');
6029
6029
 
6030
- expect(logger.error).toHaveBeenCalledWith('Error during getCombinedResultsSummary: boom');
6031
- expect(logger.error).toHaveBeenCalledWith('Response Data: {"detail":"bad"}');
6030
+ // Was two separate calls (message, then the response body) — consolidated into one
6031
+ // record with the response summary as meta (docgen-log-redaction / Phase 2/4).
6032
+ expect(logger.error).toHaveBeenCalledWith('Error during getCombinedResultsSummary: boom', {
6033
+ responseSummary: '{"detail":"bad"}',
6034
+ });
6035
+ expect(logger.error).toHaveBeenCalledTimes(1);
6032
6036
  });
6033
6037
  });
6034
6038
 
@@ -0,0 +1,270 @@
1
+ import * as winston from 'winston';
2
+ import Transport = require('winston-transport');
3
+ import { redact, DiagnosticsTransport } from '../../utils/logger';
4
+ import { installLogSink, LogSink, DiagnosticEvent } from '../../utils/logSink';
5
+ import { runContextStore } from '../../utils/runContext';
6
+
7
+ const applyRedact = (info: Record<string, unknown>) => (redact() as any).transform({ ...info });
8
+
9
+ describe('logger redact format', () => {
10
+ test('scrubs a top-level token/password/secret regardless of key casing', () => {
11
+ const out = applyRedact({ level: 'info', message: 'x', token: 'abc', Password: 'p', SECRET: 's' });
12
+ expect(out.token).toBe('[REDACTED]');
13
+ expect(out.Password).toBe('[REDACTED]');
14
+ expect(out.SECRET).toBe('[REDACTED]');
15
+ });
16
+
17
+ test('scrubs nested config.auth.password and config.headers.Authorization — the AxiosError.toJSON() leak', () => {
18
+ // This is the exact shape that was leaking via logger.error(JSON.stringify(error))
19
+ // in TicketsDataProvider.ts on ADO batch work-item failures.
20
+ const axiosLikeError = {
21
+ message: 'Request failed',
22
+ config: {
23
+ auth: { username: '', password: 'super-secret-pat' },
24
+ headers: { Authorization: 'Bearer abc.def.ghi' },
25
+ },
26
+ };
27
+ const out: any = applyRedact({ level: 'error', message: 'error populating workitems array', err: axiosLikeError });
28
+ expect(out.err.config.auth.password).toBe('[REDACTED]');
29
+ expect(out.err.config.headers.Authorization).toBe('[REDACTED]');
30
+ expect(out.err.message).toBe('Request failed');
31
+ });
32
+
33
+ test('leaves non-sensitive fields untouched', () => {
34
+ const out = applyRedact({ level: 'info', message: 'ok', project: 'Cube-ADCS', docType: 'SVD' });
35
+ expect(out.project).toBe('Cube-ADCS');
36
+ expect(out.docType).toBe('SVD');
37
+ });
38
+
39
+ test('never throws on a circular object', () => {
40
+ const circular: any = { name: 'x' };
41
+ circular.self = circular;
42
+ expect(() => applyRedact({ level: 'info', message: 'x', circular })).not.toThrow();
43
+ });
44
+
45
+ test('never throws and passes info through on null/undefined meta', () => {
46
+ expect(() => applyRedact({ level: 'info', message: 'x', meta: null })).not.toThrow();
47
+ expect(() => applyRedact({ level: 'info', message: 'x', meta: undefined })).not.toThrow();
48
+ });
49
+ });
50
+
51
+ // A capturing transport + the exact JSON format chain logger.ts uses (errors -> timestamp ->
52
+ // redact -> splat -> json), so these tests exercise the real pipeline, not just redact() alone.
53
+ class CaptureTransport extends Transport {
54
+ lines: Record<string, unknown>[] = [];
55
+ log(info: Record<string, unknown>, callback: () => void) {
56
+ this.lines.push(JSON.parse((info as any)[Symbol.for('message')] ?? JSON.stringify(info)));
57
+ callback();
58
+ }
59
+ }
60
+ function makeTestLogger() {
61
+ const capture = new CaptureTransport();
62
+ const logger = winston.createLogger({
63
+ level: 'silly',
64
+ format: winston.format.combine(
65
+ winston.format.errors({ stack: true }),
66
+ winston.format.timestamp(),
67
+ redact(),
68
+ winston.format.splat(),
69
+ winston.format.json()
70
+ ),
71
+ transports: [capture],
72
+ });
73
+ return { logger, capture };
74
+ }
75
+
76
+ describe('logger pipeline — errors({stack:true})', () => {
77
+ test('logger.error(err) (single-arg Error) still produces a non-empty message and a stack', () => {
78
+ // This is the regression errors({stack:true}) exists to prevent: without it, winston's
79
+ // single-arg path makes `info` *be* the Error, and message/stack are non-enumerable, so
80
+ // json() would emit {"level":"error","timestamp":"…"} — content-free.
81
+ const { logger, capture } = makeTestLogger();
82
+ logger.error(new Error('boom'));
83
+ expect(capture.lines).toHaveLength(1);
84
+ expect(capture.lines[0].message).toBe('boom');
85
+ expect(typeof capture.lines[0].stack).toBe('string');
86
+ expect((capture.lines[0].stack as string).length).toBeGreaterThan(0);
87
+ });
88
+ });
89
+
90
+ describe('logger pipeline — hostile inputs never throw', () => {
91
+ test.each([
92
+ ['null', null],
93
+ ['undefined', undefined],
94
+ ['NaN', NaN],
95
+ ['a circular object', (() => { const c: any = { a: 1 }; c.self = c; return c; })()],
96
+ ['an object with a throwing getter', { get boom() { throw new Error('nope'); } }],
97
+ ['a BigInt', BigInt(9007199254740993)],
98
+ ['a Symbol', Symbol('x')],
99
+ ['an Error with no message', new Error()],
100
+ ['a 10MB string', 'x'.repeat(10 * 1024 * 1024)],
101
+ ])('logger.error(%s) does not throw', (_label, value) => {
102
+ const { logger } = makeTestLogger();
103
+ expect(() => logger.error(value as any)).not.toThrow();
104
+ });
105
+ });
106
+
107
+ describe('DiagnosticsTransport (Phase 6a)', () => {
108
+ function makeDiagnosticsLogger() {
109
+ const capture = new CaptureTransport();
110
+ const logger = winston.createLogger({
111
+ level: 'silly',
112
+ format: winston.format.combine(
113
+ winston.format.errors({ stack: true }),
114
+ winston.format.timestamp(),
115
+ redact(),
116
+ winston.format.splat(),
117
+ winston.format.json()
118
+ ),
119
+ transports: [capture, new DiagnosticsTransport()],
120
+ });
121
+ return { logger, capture };
122
+ }
123
+
124
+ test('pushes warn/error records to whatever sink is installed', () => {
125
+ const events: DiagnosticEvent[] = [];
126
+ const sink: LogSink = { push: (e) => events.push(e) };
127
+ installLogSink(sink);
128
+ const { logger } = makeDiagnosticsLogger();
129
+ logger.warn('a stable warning');
130
+ logger.error('a stable error', Object.assign(new Error('boom'), { code: 'ECONN' }));
131
+ expect(events).toHaveLength(2);
132
+ expect(events[0].level).toBe('warn');
133
+ expect(events[1].err?.code).toBe('ECONN');
134
+ expect(events[1].err?.stack).toEqual(expect.any(String));
135
+ });
136
+
137
+ test('does not push info/debug records', () => {
138
+ const events: DiagnosticEvent[] = [];
139
+ installLogSink({ push: (e) => events.push(e) });
140
+ const { logger } = makeDiagnosticsLogger();
141
+ logger.info('not interesting to the dashboard');
142
+ expect(events).toHaveLength(0);
143
+ });
144
+
145
+ test('a throwing sink cannot break the log call', () => {
146
+ installLogSink({
147
+ push: () => {
148
+ throw new Error('sink is down');
149
+ },
150
+ });
151
+ const { logger } = makeDiagnosticsLogger();
152
+ expect(() => logger.error('still must not throw')).not.toThrow();
153
+ });
154
+
155
+ test.each([
156
+ ['null', null],
157
+ ['undefined', undefined],
158
+ ['NaN', NaN],
159
+ ['a circular object', (() => { const c: any = { a: 1 }; c.self = c; return c; })()],
160
+ ['an object with a throwing getter', { get boom() { throw new Error('nope'); } }],
161
+ ['a BigInt', BigInt(9007199254740993)],
162
+ ['a Symbol', Symbol('x')],
163
+ ['an Error with no message', new Error()],
164
+ ['a 10MB string', 'x'.repeat(10 * 1024 * 1024)],
165
+ ])('logger.error(%s) with a sink installed does not throw', (_label, value) => {
166
+ installLogSink({ push: () => undefined });
167
+ const { logger } = makeDiagnosticsLogger();
168
+ expect(() => logger.error(value as any)).not.toThrow();
169
+ });
170
+ });
171
+
172
+ describe('textFormat safety (regression)', () => {
173
+ // The hostile-input matrix above exercises makeTestLogger()'s json() pipeline — it never
174
+ // touched the real singleton logger's text-format branch (LOG_FORMAT's default), which is
175
+ // where a Symbol message crashed: a template literal's implicit ToString throws on a Symbol,
176
+ // and this formatter sits with no try/catch around it.
177
+ test('logger.error(Symbol) does not throw through the real default-exported logger', () => {
178
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
179
+ const logger = require('../../utils/logger').default;
180
+ expect(() => logger.error(Symbol('x'))).not.toThrow();
181
+ });
182
+ });
183
+
184
+ describe('DiagnosticsTransport capture policy (Phase 6b)', () => {
185
+ function makeDiagnosticsLogger() {
186
+ const capture = new CaptureTransport();
187
+ const logger = winston.createLogger({
188
+ level: 'silly',
189
+ format: winston.format.combine(
190
+ winston.format.errors({ stack: true }),
191
+ winston.format.timestamp(),
192
+ redact(),
193
+ winston.format.splat(),
194
+ winston.format.json()
195
+ ),
196
+ transports: [capture, new DiagnosticsTransport()],
197
+ });
198
+ return { logger, capture };
199
+ }
200
+
201
+ test('normal mode (no captureMode) does not persist debug/info', () => {
202
+ const events: DiagnosticEvent[] = [];
203
+ installLogSink({ push: (e) => events.push(e) });
204
+ const { logger } = makeDiagnosticsLogger();
205
+ runContextStore.run({ runId: 'run-1' }, () => {
206
+ logger.debug('a debug line');
207
+ logger.info('an info line');
208
+ });
209
+ expect(events).toHaveLength(0);
210
+ });
211
+
212
+ test('verbose mode persists debug and info', () => {
213
+ const events: DiagnosticEvent[] = [];
214
+ installLogSink({ push: (e) => events.push(e) });
215
+ const { logger } = makeDiagnosticsLogger();
216
+ runContextStore.run({ runId: 'run-2', captureMode: 'verbose' }, () => {
217
+ logger.debug('a debug line');
218
+ logger.info('an info line');
219
+ });
220
+ expect(events).toHaveLength(2);
221
+ expect(events[0].retainPending).toBeUndefined();
222
+ });
223
+
224
+ test('warn/error persist regardless of capture mode', () => {
225
+ const events: DiagnosticEvent[] = [];
226
+ installLogSink({ push: (e) => events.push(e) });
227
+ const { logger } = makeDiagnosticsLogger();
228
+ runContextStore.run({ runId: 'run-3' }, () => {
229
+ logger.warn('a warning');
230
+ logger.error('an error');
231
+ });
232
+ expect(events).toHaveLength(2);
233
+ });
234
+
235
+ test('retain-on-failure persists debug/info tagged retainPending: true', () => {
236
+ const events: DiagnosticEvent[] = [];
237
+ installLogSink({ push: (e) => events.push(e) });
238
+ const { logger } = makeDiagnosticsLogger();
239
+ runContextStore.run({ runId: 'run-4', captureMode: 'retain-on-failure' }, () => {
240
+ logger.debug('a debug line');
241
+ });
242
+ expect(events).toHaveLength(1);
243
+ expect(events[0].retainPending).toBe(true);
244
+ });
245
+
246
+ test('retain-on-failure does not tag warn/error as retainPending', () => {
247
+ const events: DiagnosticEvent[] = [];
248
+ installLogSink({ push: (e) => events.push(e) });
249
+ const { logger } = makeDiagnosticsLogger();
250
+ runContextStore.run({ runId: 'run-5', captureMode: 'retain-on-failure' }, () => {
251
+ logger.error('an error');
252
+ });
253
+ expect(events).toHaveLength(1);
254
+ expect(events[0].retainPending).toBeUndefined();
255
+ });
256
+
257
+ test('a concurrent normal-mode run is unaffected by a sibling verbose run', () => {
258
+ const events: DiagnosticEvent[] = [];
259
+ installLogSink({ push: (e) => events.push(e) });
260
+ const { logger } = makeDiagnosticsLogger();
261
+ runContextStore.run({ runId: 'verbose-run', captureMode: 'verbose' }, () => {
262
+ logger.debug('verbose debug');
263
+ });
264
+ runContextStore.run({ runId: 'normal-run' }, () => {
265
+ logger.debug('normal debug');
266
+ });
267
+ expect(events).toHaveLength(1);
268
+ expect(events[0].message).toBe('verbose debug');
269
+ });
270
+ });