@devopsplaybook.io/common-utils 1.0.0-beta.5.9c5ecde

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 (50) hide show
  1. package/.github/workflows/main-build.yml +17 -0
  2. package/.github/workflows/npm-upgrade.yml +16 -0
  3. package/.github/workflows/pr-check.yml +26 -0
  4. package/.github/workflows/reusable-merge-build.yml +141 -0
  5. package/.github/workflows/reusable-npm-merge.yml +135 -0
  6. package/.github/workflows/reusable-npm-pr.yml +153 -0
  7. package/.github/workflows/reusable-npm-upgrade.yml +92 -0
  8. package/.github/workflows/reusable-pr-verify.yml +135 -0
  9. package/README.md +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.js +24 -0
  12. package/dist/src/ConfigBase.d.ts +104 -0
  13. package/dist/src/ConfigBase.js +201 -0
  14. package/dist/src/DbUtils.d.ts +50 -0
  15. package/dist/src/DbUtils.js +117 -0
  16. package/dist/src/DbUtilsNoTelemetry.d.ts +27 -0
  17. package/dist/src/DbUtilsNoTelemetry.js +89 -0
  18. package/dist/src/OTelContext.d.ts +35 -0
  19. package/dist/src/OTelContext.js +42 -0
  20. package/dist/src/PostgresDbUtils.d.ts +52 -0
  21. package/dist/src/PostgresDbUtils.js +217 -0
  22. package/dist/src/SqlDbUtils.d.ts +40 -0
  23. package/dist/src/SqlDbUtils.js +156 -0
  24. package/dist/src/SystemCommand.d.ts +9 -0
  25. package/dist/src/SystemCommand.js +56 -0
  26. package/dist/src/Timeout.d.ts +6 -0
  27. package/dist/src/Timeout.js +15 -0
  28. package/eslint.config.mjs +10 -0
  29. package/index.ts +8 -0
  30. package/jest.config.js +13 -0
  31. package/package.json +50 -0
  32. package/prettierrc.json +5 -0
  33. package/src/ConfigBase.spec.ts +108 -0
  34. package/src/ConfigBase.ts +213 -0
  35. package/src/DbUtils.spec.ts +23 -0
  36. package/src/DbUtils.ts +118 -0
  37. package/src/DbUtilsNoTelemetry.spec.ts +174 -0
  38. package/src/DbUtilsNoTelemetry.ts +121 -0
  39. package/src/OTelContext.spec.ts +58 -0
  40. package/src/OTelContext.ts +65 -0
  41. package/src/PostgresDbUtils.spec.ts +155 -0
  42. package/src/PostgresDbUtils.ts +233 -0
  43. package/src/SqlDbUtils.spec.ts +111 -0
  44. package/src/SqlDbUtils.ts +153 -0
  45. package/src/SystemCommand.spec.ts +18 -0
  46. package/src/SystemCommand.ts +23 -0
  47. package/src/Timeout.spec.ts +18 -0
  48. package/src/Timeout.ts +12 -0
  49. package/tsconfig.json +14 -0
  50. package/tsconfig.spec.json +7 -0
@@ -0,0 +1,174 @@
1
+ import {
2
+ DbUtilsNoTelemetryBatchInsert,
3
+ DbUtilsNoTelemetryExecSQL,
4
+ DbUtilsNoTelemetryQuerySQL,
5
+ } from "./DbUtilsNoTelemetry";
6
+
7
+ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
8
+
9
+ // Shared mock DB handle – defined BEFORE jest.mock factory so it's hoisted correctly.
10
+ // We use jest.fn() at module scope; the mock factory captures the same reference.
11
+ const mockPrepare = jest.fn(() => ({
12
+ run: jest.fn().mockReturnValue({ changes: 0 }),
13
+ all: jest.fn().mockReturnValue([]),
14
+ }));
15
+ const mockQuery = jest.fn();
16
+ const mockDbHandle = { prepare: mockPrepare, query: mockQuery };
17
+
18
+ let currentDbType: "sqlite" | "postgres" = "sqlite";
19
+
20
+ jest.mock("./DbUtils", () => ({
21
+ DbUtilsGetDatabase: jest.fn(() => mockDbHandle),
22
+ DbUtilsGetType: jest.fn(() => currentDbType),
23
+ convertToPostgresPlaceholders: jest.fn((sql: string) => {
24
+ let idx = 1;
25
+ return sql.replace(/\?/g, () => `$${idx++}`);
26
+ }),
27
+ }));
28
+
29
+ jest.mock("@devopsplaybook.io/otel-utils", () => ({
30
+ ModuleLogger: jest.fn(),
31
+ StandardLogger: jest.fn(),
32
+ }));
33
+
34
+ import * as DbUtilsNoTelemetryModule from "./DbUtilsNoTelemetry";
35
+
36
+ beforeAll(() => {
37
+ const mockLogger = {
38
+ error: jest.fn(),
39
+ info: jest.fn(),
40
+ warn: jest.fn(),
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ } as any;
43
+ DbUtilsNoTelemetryModule.DbUtilsNoTelemetrySetLogger({
44
+ createModuleLogger: () => mockLogger,
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ } as any);
47
+ });
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ currentDbType = "sqlite";
52
+ });
53
+
54
+ describe("DbUtilsNoTelemetryBatchInsert", () => {
55
+ it("returns 0 for empty rows", () => {
56
+ const result = DbUtilsNoTelemetryBatchInsert("INTO t (c)", 1, []);
57
+ expect(result).toBe(0);
58
+ });
59
+
60
+ it("generates correct multi-row VALUES SQL (sqlite)", () => {
61
+ currentDbType = "sqlite";
62
+ mockPrepare.mockReturnValue({
63
+ run: jest.fn().mockReturnValue({ changes: 2 }),
64
+ all: jest.fn(),
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ } as any);
67
+
68
+ const rows = [
69
+ ["a1", "b1"],
70
+ ["a2", "b2"],
71
+ ];
72
+ const result = DbUtilsNoTelemetryBatchInsert("INTO t (c1,c2)", 2, rows);
73
+ expect(result).toBe(2);
74
+ });
75
+ });
76
+
77
+ describe("DbUtilsNoTelemetryExecSQL (sqlite)", () => {
78
+ beforeEach(() => {
79
+ currentDbType = "sqlite";
80
+ });
81
+
82
+ it("resolves with changes count on success", () => {
83
+ mockPrepare.mockReturnValue({
84
+ run: jest.fn().mockReturnValue({ changes: 3 }),
85
+ all: jest.fn(),
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ } as any);
88
+
89
+ const result = DbUtilsNoTelemetryExecSQL("INSERT INTO t (c) VALUES (?)", [
90
+ "x",
91
+ ]);
92
+ expect(result).toBe(3);
93
+ });
94
+ });
95
+
96
+ describe("DbUtilsNoTelemetryExecSQL (postgres)", () => {
97
+ beforeEach(() => {
98
+ currentDbType = "postgres";
99
+ });
100
+
101
+ it("resolves with rowCount on success", async () => {
102
+ mockQuery.mockImplementation(
103
+ (_sql: string, _params: unknown[], cb: Function) => {
104
+ cb(null, { rowCount: 5 });
105
+ },
106
+ );
107
+
108
+ const result = await DbUtilsNoTelemetryExecSQL(
109
+ "INSERT INTO t (c) VALUES (?)",
110
+ ["x"],
111
+ );
112
+ expect(result).toBe(5);
113
+ });
114
+
115
+ it("rejects on error", async () => {
116
+ mockQuery.mockImplementation(
117
+ (_sql: string, _params: unknown[], cb: Function) => {
118
+ cb(new Error("deadlock detected"));
119
+ },
120
+ );
121
+
122
+ await expect(
123
+ DbUtilsNoTelemetryExecSQL("INSERT INTO t (c) VALUES (?)", ["x"]),
124
+ ).rejects.toThrow("deadlock detected");
125
+ });
126
+ });
127
+
128
+ describe("DbUtilsNoTelemetryQuerySQL (sqlite)", () => {
129
+ beforeEach(() => {
130
+ currentDbType = "sqlite";
131
+ });
132
+
133
+ it("returns rows on success", () => {
134
+ const expectedRows = [{ id: 1 }, { id: 2 }];
135
+ mockPrepare.mockReturnValue({
136
+ run: jest.fn(),
137
+ all: jest.fn().mockReturnValue(expectedRows),
138
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ } as any);
140
+
141
+ const result = DbUtilsNoTelemetryQuerySQL("SELECT * FROM t");
142
+ expect(result).toEqual(expectedRows);
143
+ });
144
+ });
145
+
146
+ describe("DbUtilsNoTelemetryQuerySQL (postgres)", () => {
147
+ beforeEach(() => {
148
+ currentDbType = "postgres";
149
+ });
150
+
151
+ it("returns rows on success", async () => {
152
+ const expectedRows = [{ id: 1 }, { id: 2 }];
153
+ mockQuery.mockImplementation(
154
+ (_sql: string, _params: unknown[], cb: Function) => {
155
+ cb(null, { rows: expectedRows });
156
+ },
157
+ );
158
+
159
+ const result = await DbUtilsNoTelemetryQuerySQL("SELECT * FROM t");
160
+ expect(result).toEqual(expectedRows);
161
+ });
162
+
163
+ it("rejects on error", async () => {
164
+ mockQuery.mockImplementation(
165
+ (_sql: string, _params: unknown[], cb: Function) => {
166
+ cb(new Error("connection lost"));
167
+ },
168
+ );
169
+
170
+ await expect(DbUtilsNoTelemetryQuerySQL("SELECT * FROM t")).rejects.toThrow(
171
+ "connection lost",
172
+ );
173
+ });
174
+ });
@@ -0,0 +1,121 @@
1
+ import { StandardLogger, ModuleLogger } from "@devopsplaybook.io/otel-utils";
2
+ import {
3
+ DbUtilsGetDatabase,
4
+ DbUtilsGetType,
5
+ convertToPostgresPlaceholders,
6
+ } from "./DbUtils";
7
+
8
+ let logger: ModuleLogger;
9
+
10
+ /**
11
+ * Injects the OTel logger instance used by no-telemetry DB operations.
12
+ * Must be called once at startup.
13
+ */
14
+ export function DbUtilsNoTelemetrySetLogger(loggerIn: StandardLogger): void {
15
+ logger = loggerIn.createModuleLogger("DbUtilsNoTelemetry");
16
+ }
17
+
18
+ /**
19
+ * Execute a multi-row INSERT with a flat parameter array.
20
+ * Builds: INSERT INTO <tableCols> VALUES (?,?...),(?,?...),...
21
+ *
22
+ * @returns Number of rows inserted.
23
+ */
24
+ export function DbUtilsNoTelemetryBatchInsert(
25
+ tableCols: string,
26
+ numCols: number,
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ rows: any[][],
29
+ ): number | Promise<number> {
30
+ if (rows.length === 0) return 0;
31
+ const rowSQL = `(${Array.from({ length: numCols }, () => "?").join(",")})`;
32
+ const multiValues = Array.from({ length: rows.length }, () => rowSQL).join(
33
+ ",",
34
+ );
35
+ const sql = `INSERT ${tableCols} VALUES ${multiValues}`;
36
+ return DbUtilsNoTelemetryExecSQL(sql, rows.flat());
37
+ }
38
+
39
+ /**
40
+ * Execute a write SQL statement **without** creating an OTel span.
41
+ * Use this on high-throughput paths where span overhead matters.
42
+ *
43
+ * @returns Number of rows changed.
44
+ */
45
+ export function DbUtilsNoTelemetryExecSQL(
46
+ sql: string,
47
+ params: unknown[] = [],
48
+ ): number | Promise<number> {
49
+ const dbType = DbUtilsGetType();
50
+ if (dbType === "postgres") {
51
+ const pgSql = convertToPostgresPlaceholders(sql);
52
+ return new Promise((resolve, reject) => {
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ (DbUtilsGetDatabase() as any).query(
55
+ pgSql,
56
+ params,
57
+ (error: Error | null, result: { rowCount: number | null }) => {
58
+ if (error) {
59
+ logger.error(`SQL INSERT ERROR: ${sql.substring(0, 200)}`, error);
60
+ reject(error);
61
+ } else {
62
+ resolve(result.rowCount || 0);
63
+ }
64
+ },
65
+ );
66
+ });
67
+ }
68
+ // SQLite (better-sqlite3) – synchronous
69
+ const stmt = (
70
+ DbUtilsGetDatabase() as {
71
+ prepare: (sql: string) => {
72
+ run: (params: unknown[]) => { changes: number };
73
+ };
74
+ }
75
+ ).prepare(sql);
76
+ const result = stmt.run(params);
77
+ return result.changes;
78
+ }
79
+
80
+ /**
81
+ * Execute a read SQL query **without** creating an OTel span.
82
+ * Use this on high-throughput paths where span overhead matters.
83
+ *
84
+ * @returns Array of row objects.
85
+ */
86
+ export function DbUtilsNoTelemetryQuerySQL(
87
+ sql: string,
88
+ params: unknown[] = [],
89
+ debug = false,
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ ): any[] | Promise<any[]> {
92
+ if (debug) {
93
+ console.log(sql);
94
+ }
95
+ const dbType = DbUtilsGetType();
96
+ if (dbType === "postgres") {
97
+ const pgSql = convertToPostgresPlaceholders(sql);
98
+ return new Promise((resolve, reject) => {
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ (DbUtilsGetDatabase() as any).query(
101
+ pgSql,
102
+ params,
103
+ (error: Error | null, result: { rows: unknown[] }) => {
104
+ if (error) {
105
+ logger.error(`SQL ERROR: ${sql}`, error);
106
+ reject(error);
107
+ } else {
108
+ resolve(result.rows);
109
+ }
110
+ },
111
+ );
112
+ });
113
+ }
114
+ // SQLite (better-sqlite3) – synchronous
115
+ const stmt = (
116
+ DbUtilsGetDatabase() as {
117
+ prepare: (sql: string) => { all: (params: unknown[]) => unknown[] };
118
+ }
119
+ ).prepare(sql);
120
+ return stmt.all(params);
121
+ }
@@ -0,0 +1,58 @@
1
+ import { createOTelContext } from "./OTelContext";
2
+
3
+ describe("createOTelContext", () => {
4
+ it("should return an object with all expected methods", () => {
5
+ const ctx = createOTelContext();
6
+ expect(typeof ctx.OTelTracer).toBe("function");
7
+ expect(typeof ctx.OTelSetTracer).toBe("function");
8
+ expect(typeof ctx.OTelMeter).toBe("function");
9
+ expect(typeof ctx.OTelSetMeter).toBe("function");
10
+ expect(typeof ctx.OTelLogger).toBe("function");
11
+ expect(typeof ctx.OTelRequestSpan).toBe("function");
12
+ });
13
+
14
+ it("should create a StandardLogger lazily", () => {
15
+ const ctx = createOTelContext();
16
+ const logger = ctx.OTelLogger();
17
+ expect(logger).toBeDefined();
18
+ // Same instance on second call
19
+ expect(ctx.OTelLogger()).toBe(logger);
20
+ });
21
+
22
+ it("should store and retrieve tracer via setter/getter", () => {
23
+ const ctx = createOTelContext();
24
+ const fakeTracer = { id: "tracer-1" } as never;
25
+ ctx.OTelSetTracer(fakeTracer);
26
+ expect(ctx.OTelTracer()).toBe(fakeTracer);
27
+ });
28
+
29
+ it("should store and retrieve meter via setter/getter", () => {
30
+ const ctx = createOTelContext();
31
+ const fakeMeter = { id: "meter-1" } as never;
32
+ ctx.OTelSetMeter(fakeMeter);
33
+ expect(ctx.OTelMeter()).toBe(fakeMeter);
34
+ });
35
+
36
+ it("should isolate contexts from each other", () => {
37
+ const ctx1 = createOTelContext();
38
+ const ctx2 = createOTelContext();
39
+ const fakeTracer1 = { id: "tracer-1" } as never;
40
+ const fakeTracer2 = { id: "tracer-2" } as never;
41
+ ctx1.OTelSetTracer(fakeTracer1);
42
+ ctx2.OTelSetTracer(fakeTracer2);
43
+ expect(ctx1.OTelTracer()).toBe(fakeTracer1);
44
+ expect(ctx2.OTelTracer()).toBe(fakeTracer2);
45
+ });
46
+
47
+ it("OTelRequestSpan should return undefined for object without tracerSpanApi", () => {
48
+ const ctx = createOTelContext();
49
+ expect(ctx.OTelRequestSpan({})).toBeUndefined();
50
+ });
51
+
52
+ it("OTelRequestSpan should return the span when present", () => {
53
+ const ctx = createOTelContext();
54
+ const fakeSpan = { spanId: "123" } as never;
55
+ const req = { tracerSpanApi: fakeSpan };
56
+ expect(ctx.OTelRequestSpan(req)).toBe(fakeSpan);
57
+ });
58
+ });
@@ -0,0 +1,65 @@
1
+ import {
2
+ StandardLogger,
3
+ StandardMeter,
4
+ StandardTracer,
5
+ } from "@devopsplaybook.io/otel-utils";
6
+ import { Span } from "@opentelemetry/sdk-trace-base";
7
+
8
+ /**
9
+ * Result of {@link createOTelContext}.
10
+ * Holds module-level singletons for the OpenTelemetry tracer, meter and logger
11
+ * used throughout a server process.
12
+ */
13
+ export interface OTelContext {
14
+ OTelTracer: () => StandardTracer;
15
+ OTelSetTracer: (tracer: StandardTracer) => void;
16
+ OTelMeter: () => StandardMeter;
17
+ OTelSetMeter: (meter: StandardMeter) => void;
18
+ OTelLogger: () => StandardLogger;
19
+ /**
20
+ * Retrieves the span previously attached to a request object.
21
+ * Equivalent to `req.tracerSpanApi`.
22
+ */
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ OTelRequestSpan: (req: any) => Span | undefined;
25
+ }
26
+
27
+ /**
28
+ * Creates an isolated OTel context (tracer / meter / logger singletons).
29
+ *
30
+ * Each server process should call this once at startup and pass the returned
31
+ * object to modules that need telemetry access. This avoids polluting the
32
+ * global module scope and makes unit-testing straightforward.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const otel = createOTelContext();
37
+ * otel.OTelSetTracer(new StandardTracer(config));
38
+ * otel.OTelSetMeter(new StandardMeter(config));
39
+ * otel.OTelLogger().initOTel(config);
40
+ * ```
41
+ */
42
+ export function createOTelContext(): OTelContext {
43
+ let tracer: StandardTracer;
44
+ let meter: StandardMeter;
45
+ let logger: StandardLogger;
46
+
47
+ return {
48
+ OTelTracer: () => tracer,
49
+ OTelSetTracer: (t: StandardTracer) => {
50
+ tracer = t;
51
+ },
52
+ OTelMeter: () => meter,
53
+ OTelSetMeter: (m: StandardMeter) => {
54
+ meter = m;
55
+ },
56
+ OTelLogger: () => {
57
+ if (!logger) {
58
+ logger = new StandardLogger();
59
+ }
60
+ return logger;
61
+ },
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ OTelRequestSpan: (req: any) => req?.tracerSpanApi as Span | undefined,
64
+ };
65
+ }
@@ -0,0 +1,155 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ // Mock pg before imports
4
+ const mockQuery = jest.fn();
5
+ jest.mock("pg", () => {
6
+ return {
7
+ Pool: jest.fn().mockImplementation(() => ({
8
+ query: mockQuery,
9
+ on: jest.fn(),
10
+ })),
11
+ };
12
+ });
13
+
14
+ jest.mock("fs-extra", () => ({
15
+ readdir: jest.fn().mockResolvedValue(["init-0000.sql"]),
16
+ readFile: jest
17
+ .fn()
18
+ .mockResolvedValue(Buffer.from("CREATE TABLE metadata ();")),
19
+ }));
20
+
21
+ jest.mock("@devopsplaybook.io/otel-utils", () => ({
22
+ StandardTracer: jest.fn(),
23
+ StandardLogger: jest.fn(),
24
+ ModuleLogger: jest.fn(),
25
+ }));
26
+
27
+ jest.mock("@opentelemetry/sdk-trace-base", () => ({
28
+ Span: jest.fn(),
29
+ }));
30
+
31
+ jest.mock("@opentelemetry/api", () => ({
32
+ SpanStatusCode: { ERROR: 2, OK: 1 },
33
+ }));
34
+
35
+ describe("PostgresDbUtils", () => {
36
+ let PostgresDbUtils: typeof import("./PostgresDbUtils");
37
+
38
+ const mockSpan = {
39
+ end: jest.fn(),
40
+ addEvent: jest.fn(),
41
+ setStatus: jest.fn(),
42
+ };
43
+
44
+ const mockModuleLogger = {
45
+ info: jest.fn(),
46
+ error: jest.fn(),
47
+ warn: jest.fn(),
48
+ };
49
+
50
+ const mockTracer = {
51
+ startSpan: jest.fn().mockReturnValue(mockSpan),
52
+ };
53
+
54
+ const mockLogger = {
55
+ createModuleLogger: jest.fn().mockReturnValue(mockModuleLogger),
56
+ };
57
+
58
+ beforeEach(() => {
59
+ jest.clearAllMocks();
60
+ jest.isolateModules(() => {
61
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
62
+ PostgresDbUtils = require("./PostgresDbUtils");
63
+ });
64
+ PostgresDbUtils.PostgresDbUtilsSetOTel(
65
+ mockTracer as any,
66
+ mockLogger as any,
67
+ );
68
+ });
69
+
70
+ it("PostgresDbUtilsSetOTel should accept tracer and logger", () => {
71
+ expect(() =>
72
+ PostgresDbUtils.PostgresDbUtilsSetOTel(
73
+ mockTracer as any,
74
+ mockLogger as any,
75
+ ),
76
+ ).not.toThrow();
77
+ });
78
+
79
+ it("PostgresDbUtilsGetPool should return undefined before init", () => {
80
+ expect(PostgresDbUtils.PostgresDbUtilsGetPool()).toBeUndefined();
81
+ });
82
+
83
+ it("PostgresDbUtilsExecSQL should create a span and call pool.query", async () => {
84
+ const config = {
85
+ DATABASE_POSTGRES_HOST: "localhost",
86
+ DATABASE_POSTGRES_PORT: 5432,
87
+ DATABASE_POSTGRES_USER: "user",
88
+ DATABASE_POSTGRES_PASSWORD: "pass",
89
+ DATABASE_POSTGRES_DATABASE: "testdb",
90
+ };
91
+
92
+ // Mock the version query to return version 0
93
+ // pool.query can be called with 2 args (sql, cb) or 3 args (sql, params, cb)
94
+ mockQuery.mockImplementation((...args: any[]) => {
95
+ const cb =
96
+ typeof args[args.length - 1] === "function"
97
+ ? args[args.length - 1]
98
+ : null;
99
+ if (!cb) return;
100
+ // Determine which call this is based on arg count
101
+ if (args.length === 2) {
102
+ // execSQLFile call
103
+ cb(null);
104
+ } else if (args.length === 3) {
105
+ const sql = args[0] as string;
106
+ if (sql.includes("MAX")) {
107
+ // version query
108
+ cb(null, { rows: [{ version: 0 }] });
109
+ } else {
110
+ // insert version
111
+ cb(null, { rowCount: 1 });
112
+ }
113
+ }
114
+ });
115
+
116
+ await PostgresDbUtils.PostgresDbUtilsInit(
117
+ mockSpan as any,
118
+ config,
119
+ "/fake/sql",
120
+ );
121
+
122
+ expect(mockTracer.startSpan).toHaveBeenCalledWith(
123
+ "PostgresDbUtilsInit",
124
+ mockSpan,
125
+ );
126
+ });
127
+
128
+ it("PostgresDbUtilsTransactionStart should call BEGIN", async () => {
129
+ const config = {
130
+ DATABASE_POSTGRES_HOST: "localhost",
131
+ DATABASE_POSTGRES_PORT: 5432,
132
+ DATABASE_POSTGRES_USER: "user",
133
+ DATABASE_POSTGRES_PASSWORD: "pass",
134
+ DATABASE_POSTGRES_DATABASE: "testdb",
135
+ };
136
+
137
+ // Need to init first to create pool
138
+ mockQuery.mockImplementation((_sql: any, ...args: any[]) => {
139
+ const cb = args[args.length - 1];
140
+ if (typeof cb === "function") cb(null, { rows: [], rowCount: 0 });
141
+ });
142
+
143
+ await PostgresDbUtils.PostgresDbUtilsInit(
144
+ mockSpan as any,
145
+ config,
146
+ "/fake/sql",
147
+ );
148
+ await PostgresDbUtils.PostgresDbUtilsTransactionStart(mockSpan as any);
149
+
150
+ expect(mockTracer.startSpan).toHaveBeenCalledWith(
151
+ "PostgresDbUtilsTransactionStart",
152
+ mockSpan,
153
+ );
154
+ });
155
+ });