@elisra-devops/docgen-data-provider 1.143.0 → 1.144.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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // Guards the run context (runId / docType / project) that every log record is stamped from.
4
+ // Prod showed data-provider records with a run id but no project / doc type; these tests pin
5
+ // down what must hold in this package for a record to be attributed to the right run.
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const winston = require("winston");
9
+ const logger_1 = require("../../utils/logger");
10
+ const logSink_1 = require("../../utils/logSink");
11
+ const runContext_1 = require("../../utils/runContext");
12
+ const pLimit = require('p-limit');
13
+ const pause = (ms) => new Promise((r) => setTimeout(r, ms));
14
+ function makeLogger() {
15
+ const events = [];
16
+ (0, logSink_1.installLogSink)({ push: (e) => events.push(e), flush: async () => undefined });
17
+ const logger = winston.createLogger({
18
+ level: 'debug',
19
+ format: winston.format.combine((0, logger_1.withRunContext)(), winston.format.json()),
20
+ transports: [new logger_1.DiagnosticsTransport()],
21
+ });
22
+ return { logger, events };
23
+ }
24
+ describe('run context reaches log events from the places the data provider logs from', () => {
25
+ test('from tasks queued behind a per-instance p-limit, and from a retry timer', async () => {
26
+ const { logger, events } = makeLogger();
27
+ const limit = pLimit(2); // per request, like the providers' `private limit = pLimit(10)` fields
28
+ await runContext_1.runContextStore.run({ runId: 'run-1', docType: 'STD', project: 'MEWP' }, async () => {
29
+ await Promise.all([1, 2, 3, 4, 5, 6].map((i) => limit(async () => {
30
+ await pause(3);
31
+ logger.error(`queued task ${i}`);
32
+ })));
33
+ await new Promise((resolve) => setTimeout(() => {
34
+ logger.warn('Request failed. Retrying');
35
+ resolve();
36
+ }, 5));
37
+ });
38
+ expect(events).toHaveLength(7);
39
+ events.forEach((e) => expect(e).toMatchObject({ runId: 'run-1', docType: 'STD', project: 'MEWP' }));
40
+ });
41
+ test('concurrent runs, each with its own limiter, never see each other\'s context', async () => {
42
+ const { logger, events } = makeLogger();
43
+ await Promise.all(['A', 'B', 'C', 'D'].map((id) => runContext_1.runContextStore.run({ runId: `run-${id}`, docType: id === 'A' ? 'STD' : 'SVD', project: `P-${id}` }, async () => {
44
+ const limit = pLimit(2);
45
+ await Promise.all([1, 2, 3, 4].map((i) => limit(async () => { await pause(i); logger.error(`${id}:${i}`); })));
46
+ })));
47
+ expect(events).toHaveLength(16);
48
+ events.forEach((e) => {
49
+ const id = e.message.split(':')[0];
50
+ expect(e).toMatchObject({ runId: `run-${id}`, project: `P-${id}` });
51
+ });
52
+ });
53
+ });
54
+ // A p-limit queue shared by two concurrent runs resumes a queued task from whichever task last
55
+ // finished — i.e. in the OTHER run's async context — so its log lines are attributed to the wrong
56
+ // run (measured: half the tasks of two interleaved runs). Per-instance limiters are safe because
57
+ // a provider instance serves one request. If you need a limiter, make it a per-request/instance
58
+ // field; do not hoist it to module or class-static scope.
59
+ describe('no process-wide p-limit queue', () => {
60
+ const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((d) => {
61
+ const p = path.join(dir, d.name);
62
+ if (d.isDirectory())
63
+ return d.name === 'tests' ? [] : walk(p);
64
+ return p.endsWith('.ts') ? [p] : [];
65
+ });
66
+ test('no pLimit(...) at module scope or in a static member', () => {
67
+ const offenders = [];
68
+ for (const file of walk(path.resolve(__dirname, '../..'))) {
69
+ fs.readFileSync(file, 'utf8')
70
+ .split('\n')
71
+ .forEach((line, i) => {
72
+ const moduleScope = /^(export\s+)?(const|let|var)\s+\w+\s*=\s*pLimit\(/.test(line);
73
+ const staticMember = /\bstatic\b[^=]*=\s*pLimit\(/.test(line);
74
+ if (moduleScope || staticMember)
75
+ offenders.push(`${path.relative(process.cwd(), file)}:${i + 1}: ${line.trim()}`);
76
+ });
77
+ }
78
+ expect(offenders).toEqual([]);
79
+ });
80
+ });
81
+ //# sourceMappingURL=runContextPropagation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runContextPropagation.test.js","sourceRoot":"","sources":["../../../src/tests/utils/runContextPropagation.test.ts"],"names":[],"mappings":";;AAAA,4FAA4F;AAC5F,6FAA6F;AAC7F,sFAAsF;AACtF,yBAAyB;AACzB,6BAA6B;AAC7B,mCAAmC;AACnC,+CAA0E;AAC1E,iDAA+E;AAC/E,uDAAyD;AAEzD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpE,SAAS,UAAU;IACjB,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,IAAA,wBAAc,EAAC,EAAE,IAAI,EAAE,CAAC,CAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,EAAa,CAAC,CAAC;IAC1G,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QAClC,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAA,uBAAc,GAAE,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvE,UAAU,EAAE,CAAC,IAAI,6BAAoB,EAAE,CAAC;KACzC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,QAAQ,CAAC,4EAA4E,EAAE,GAAG,EAAE;IAC1F,IAAI,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,uEAAuE;QAChG,MAAM,4BAAe,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,EAAE;YACxF,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3B,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;gBACf,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;YACnC,CAAC,CAAC,CACH,CACF,CAAC;YACF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAClC,UAAU,CAAC,GAAG,EAAE;gBACd,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;gBACxC,OAAO,EAAE,CAAC;YACZ,CAAC,EAAE,CAAC,CAAC,CACN,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC7F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxC,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAC9B,4BAAe,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE;YAC9G,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjH,CAAC,CAAC,CACH,CACF,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,+FAA+F;AAC/F,kGAAkG;AAClG,iGAAiG;AACjG,gGAAgG;AAChG,0DAA0D;AAC1D,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,MAAM,IAAI,GAAG,CAAC,GAAW,EAAY,EAAE,CACrC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACzD,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9D,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;IAEL,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;YAC1D,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;iBAC1B,KAAK,CAAC,IAAI,CAAC;iBACX,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;gBACnB,MAAM,WAAW,GAAG,mDAAmD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnF,MAAM,YAAY,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9D,IAAI,WAAW,IAAI,YAAY;oBAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACpH,CAAC,CAAC,CAAC;QACP,CAAC;QACD,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.143.0",
3
+ "version": "1.144.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,93 @@
1
+ // Guards the run context (runId / docType / project) that every log record is stamped from.
2
+ // Prod showed data-provider records with a run id but no project / doc type; these tests pin
3
+ // down what must hold in this package for a record to be attributed to the right run.
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import * as winston from 'winston';
7
+ import { DiagnosticsTransport, withRunContext } from '../../utils/logger';
8
+ import { installLogSink, LogSink, DiagnosticEvent } from '../../utils/logSink';
9
+ import { runContextStore } from '../../utils/runContext';
10
+
11
+ const pLimit = require('p-limit');
12
+ const pause = (ms: number) => new Promise((r) => setTimeout(r, ms));
13
+
14
+ function makeLogger() {
15
+ const events: DiagnosticEvent[] = [];
16
+ installLogSink({ push: (e: DiagnosticEvent) => events.push(e), flush: async () => undefined } as LogSink);
17
+ const logger = winston.createLogger({
18
+ level: 'debug',
19
+ format: winston.format.combine(withRunContext(), winston.format.json()),
20
+ transports: [new DiagnosticsTransport()],
21
+ });
22
+ return { logger, events };
23
+ }
24
+
25
+ describe('run context reaches log events from the places the data provider logs from', () => {
26
+ test('from tasks queued behind a per-instance p-limit, and from a retry timer', async () => {
27
+ const { logger, events } = makeLogger();
28
+ const limit = pLimit(2); // per request, like the providers' `private limit = pLimit(10)` fields
29
+ await runContextStore.run({ runId: 'run-1', docType: 'STD', project: 'MEWP' }, async () => {
30
+ await Promise.all(
31
+ [1, 2, 3, 4, 5, 6].map((i) =>
32
+ limit(async () => {
33
+ await pause(3);
34
+ logger.error(`queued task ${i}`);
35
+ })
36
+ )
37
+ );
38
+ await new Promise<void>((resolve) =>
39
+ setTimeout(() => {
40
+ logger.warn('Request failed. Retrying');
41
+ resolve();
42
+ }, 5)
43
+ );
44
+ });
45
+ expect(events).toHaveLength(7);
46
+ events.forEach((e) => expect(e).toMatchObject({ runId: 'run-1', docType: 'STD', project: 'MEWP' }));
47
+ });
48
+
49
+ test('concurrent runs, each with its own limiter, never see each other\'s context', async () => {
50
+ const { logger, events } = makeLogger();
51
+ await Promise.all(
52
+ ['A', 'B', 'C', 'D'].map((id) =>
53
+ runContextStore.run({ runId: `run-${id}`, docType: id === 'A' ? 'STD' : 'SVD', project: `P-${id}` }, async () => {
54
+ const limit = pLimit(2);
55
+ await Promise.all([1, 2, 3, 4].map((i) => limit(async () => { await pause(i); logger.error(`${id}:${i}`); })));
56
+ })
57
+ )
58
+ );
59
+ expect(events).toHaveLength(16);
60
+ events.forEach((e) => {
61
+ const id = e.message.split(':')[0];
62
+ expect(e).toMatchObject({ runId: `run-${id}`, project: `P-${id}` });
63
+ });
64
+ });
65
+ });
66
+
67
+ // A p-limit queue shared by two concurrent runs resumes a queued task from whichever task last
68
+ // finished — i.e. in the OTHER run's async context — so its log lines are attributed to the wrong
69
+ // run (measured: half the tasks of two interleaved runs). Per-instance limiters are safe because
70
+ // a provider instance serves one request. If you need a limiter, make it a per-request/instance
71
+ // field; do not hoist it to module or class-static scope.
72
+ describe('no process-wide p-limit queue', () => {
73
+ const walk = (dir: string): string[] =>
74
+ fs.readdirSync(dir, { withFileTypes: true }).flatMap((d) => {
75
+ const p = path.join(dir, d.name);
76
+ if (d.isDirectory()) return d.name === 'tests' ? [] : walk(p);
77
+ return p.endsWith('.ts') ? [p] : [];
78
+ });
79
+
80
+ test('no pLimit(...) at module scope or in a static member', () => {
81
+ const offenders: string[] = [];
82
+ for (const file of walk(path.resolve(__dirname, '../..'))) {
83
+ fs.readFileSync(file, 'utf8')
84
+ .split('\n')
85
+ .forEach((line, i) => {
86
+ const moduleScope = /^(export\s+)?(const|let|var)\s+\w+\s*=\s*pLimit\(/.test(line);
87
+ const staticMember = /\bstatic\b[^=]*=\s*pLimit\(/.test(line);
88
+ if (moduleScope || staticMember) offenders.push(`${path.relative(process.cwd(), file)}:${i + 1}: ${line.trim()}`);
89
+ });
90
+ }
91
+ expect(offenders).toEqual([]);
92
+ });
93
+ });