opticedge-cloud-utils 1.1.21 → 1.1.23

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 (73) hide show
  1. package/dist/{src/index.d.ts → index.d.ts} +1 -0
  2. package/dist/{src/index.js → index.js} +1 -0
  3. package/dist/logger.d.ts +8 -0
  4. package/dist/logger.js +53 -0
  5. package/package.json +1 -1
  6. package/src/index.ts +1 -0
  7. package/src/logger.ts +55 -0
  8. package/tests/logger.test.ts +116 -0
  9. package/tsconfig.json +1 -1
  10. package/dist/tests/auth.test.d.ts +0 -1
  11. package/dist/tests/auth.test.js +0 -79
  12. package/dist/tests/chunk.test.d.ts +0 -1
  13. package/dist/tests/chunk.test.js +0 -45
  14. package/dist/tests/db/mongo.test.d.ts +0 -1
  15. package/dist/tests/db/mongo.test.js +0 -43
  16. package/dist/tests/db/mongo2.test.d.ts +0 -1
  17. package/dist/tests/db/mongo2.test.js +0 -49
  18. package/dist/tests/db/mongo3.test.d.ts +0 -1
  19. package/dist/tests/db/mongo3.test.js +0 -60
  20. package/dist/tests/env.test.d.ts +0 -1
  21. package/dist/tests/env.test.js +0 -17
  22. package/dist/tests/number.test.d.ts +0 -1
  23. package/dist/tests/number.test.js +0 -30
  24. package/dist/tests/parser.test.d.ts +0 -1
  25. package/dist/tests/parser.test.js +0 -24
  26. package/dist/tests/pub.test.d.ts +0 -1
  27. package/dist/tests/pub.test.js +0 -102
  28. package/dist/tests/regex.test.d.ts +0 -1
  29. package/dist/tests/regex.test.js +0 -60
  30. package/dist/tests/retry.test.d.ts +0 -1
  31. package/dist/tests/retry.test.js +0 -339
  32. package/dist/tests/secrets.test.d.ts +0 -1
  33. package/dist/tests/secrets.test.js +0 -38
  34. package/dist/tests/task.test.d.ts +0 -1
  35. package/dist/tests/task.test.js +0 -262
  36. package/dist/tests/tw/utils.test.d.ts +0 -1
  37. package/dist/tests/tw/utils.test.js +0 -26
  38. package/dist/tests/tw/wallet.test.d.ts +0 -1
  39. package/dist/tests/tw/wallet.test.js +0 -108
  40. package/dist/tests/validator.d.ts +0 -1
  41. package/dist/tests/validator.js +0 -34
  42. /package/dist/{src/auth.d.ts → auth.d.ts} +0 -0
  43. /package/dist/{src/auth.js → auth.js} +0 -0
  44. /package/dist/{src/chunk.d.ts → chunk.d.ts} +0 -0
  45. /package/dist/{src/chunk.js → chunk.js} +0 -0
  46. /package/dist/{src/db → db}/mongo.d.ts +0 -0
  47. /package/dist/{src/db → db}/mongo.js +0 -0
  48. /package/dist/{src/db → db}/mongo2.d.ts +0 -0
  49. /package/dist/{src/db → db}/mongo2.js +0 -0
  50. /package/dist/{src/db → db}/mongo3.d.ts +0 -0
  51. /package/dist/{src/db → db}/mongo3.js +0 -0
  52. /package/dist/{src/env.d.ts → env.d.ts} +0 -0
  53. /package/dist/{src/env.js → env.js} +0 -0
  54. /package/dist/{src/number.d.ts → number.d.ts} +0 -0
  55. /package/dist/{src/number.js → number.js} +0 -0
  56. /package/dist/{src/parser.d.ts → parser.d.ts} +0 -0
  57. /package/dist/{src/parser.js → parser.js} +0 -0
  58. /package/dist/{src/pub.d.ts → pub.d.ts} +0 -0
  59. /package/dist/{src/pub.js → pub.js} +0 -0
  60. /package/dist/{src/regex.d.ts → regex.d.ts} +0 -0
  61. /package/dist/{src/regex.js → regex.js} +0 -0
  62. /package/dist/{src/retry.d.ts → retry.d.ts} +0 -0
  63. /package/dist/{src/retry.js → retry.js} +0 -0
  64. /package/dist/{src/secrets.d.ts → secrets.d.ts} +0 -0
  65. /package/dist/{src/secrets.js → secrets.js} +0 -0
  66. /package/dist/{src/task.d.ts → task.d.ts} +0 -0
  67. /package/dist/{src/task.js → task.js} +0 -0
  68. /package/dist/{src/tw → tw}/utils.d.ts +0 -0
  69. /package/dist/{src/tw → tw}/utils.js +0 -0
  70. /package/dist/{src/tw → tw}/wallet.d.ts +0 -0
  71. /package/dist/{src/tw → tw}/wallet.js +0 -0
  72. /package/dist/{src/validator.d.ts → validator.d.ts} +0 -0
  73. /package/dist/{src/validator.js → validator.js} +0 -0
@@ -1,102 +0,0 @@
1
- "use strict";
2
- // tests/pub.test.ts
3
- jest.mock('@google-cloud/pubsub', () => {
4
- const instances = [];
5
- class PubSub {
6
- constructor(opts) {
7
- this.topics = new Map();
8
- this.opts = opts;
9
- instances.push(this);
10
- }
11
- topic(name) {
12
- if (!this.topics.has(name)) {
13
- const publishMessage = jest.fn(({ data }) => Promise.resolve(`${this.opts?.projectId}-${name}`));
14
- this.topics.set(name, { publishMessage });
15
- }
16
- return this.topics.get(name);
17
- }
18
- }
19
- return { PubSub, __instances: instances };
20
- });
21
- const path = '../src/pub';
22
- describe('publishMessage', () => {
23
- beforeEach(() => {
24
- // ensure fresh module state between tests
25
- jest.resetModules();
26
- // clear any existing mock instances array if present
27
- try {
28
- const mockPubsub = require('@google-cloud/pubsub');
29
- if (mockPubsub && Array.isArray(mockPubsub.__instances)) {
30
- mockPubsub.__instances.length = 0;
31
- }
32
- }
33
- catch {
34
- // ignore if not loaded yet
35
- }
36
- });
37
- test('publishes and returns messageId, sends JSON buffer', async () => {
38
- const mockPubsub = require('@google-cloud/pubsub');
39
- const { publishMessage } = require(path);
40
- const envelope = { hello: 'world' };
41
- const msgId = await publishMessage('project-1', 'topic-a', envelope);
42
- expect(msgId).toBe('project-1-topic-a');
43
- const instance = mockPubsub.__instances[0];
44
- expect(instance).toBeDefined();
45
- const publishMock = instance.topics.get('topic-a').publishMessage;
46
- expect(publishMock).toHaveBeenCalledTimes(1);
47
- const callArg = publishMock.mock.calls[0][0];
48
- expect(callArg).toHaveProperty('data');
49
- expect(callArg.data.toString()).toBe(JSON.stringify(envelope));
50
- });
51
- test('throws on missing arguments', async () => {
52
- const { publishMessage } = require(path);
53
- await expect(publishMessage('', 't', {})).rejects.toThrow('projectId is required');
54
- await expect(publishMessage('p', '', {})).rejects.toThrow('topicName is required');
55
- await expect(publishMessage('p', 't', undefined)).rejects.toThrow('envelope is required');
56
- });
57
- test('caches one PubSub instance across multiple calls for the same project', async () => {
58
- const mockPubsub = require('@google-cloud/pubsub');
59
- const { publishMessage } = require(path);
60
- await publishMessage('project-1', 'topic-x', { a: 1 });
61
- await publishMessage('project-1', 'topic-x', { b: 2 });
62
- const instances = mockPubsub.__instances;
63
- expect(instances.length).toBe(1);
64
- const publishMock = instances[0].topics.get('topic-x').publishMessage;
65
- expect(publishMock).toHaveBeenCalledTimes(2);
66
- });
67
- test('reuses the same PubSub instance even when called with a different projectId', async () => {
68
- const mockPubsub = require('@google-cloud/pubsub');
69
- const { publishMessage } = require(path);
70
- // first call creates instance with project-A
71
- await publishMessage('project-A', 't1', { x: 1 });
72
- // second call passes a different project id but module currently reuses the cached instance
73
- await publishMessage('project-B', 't2', { y: 2 });
74
- const instances = mockPubsub.__instances;
75
- // because src/pub.ts caches a single PubSub instance, only one instance should exist
76
- expect(instances.length).toBe(1);
77
- // the created instance was constructed with the first project id
78
- expect(instances[0].opts).toEqual({ projectId: 'project-A' });
79
- // ensure both publishes were invoked on the same instance (different topics)
80
- expect(instances[0].topics.get('t1').publishMessage).toHaveBeenCalledTimes(1);
81
- expect(instances[0].topics.get('t2').publishMessage).toHaveBeenCalledTimes(1);
82
- });
83
- test('logs error and rethrows when publish fails', async () => {
84
- const mockPubsub = require('@google-cloud/pubsub');
85
- // Override the mock PubSub.topic to return a publishMessage that rejects
86
- mockPubsub.PubSub.prototype.topic = function (name) {
87
- if (!this.topics.has(name)) {
88
- const publishMessage = jest.fn(() => Promise.reject(new Error('boom')));
89
- this.topics.set(name, { publishMessage });
90
- }
91
- return this.topics.get(name);
92
- };
93
- const { publishMessage } = require(path);
94
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
95
- await expect(publishMessage('project-err', 'topic-err', { fail: true })).rejects.toThrow('boom');
96
- expect(consoleErrorSpy).toHaveBeenCalled();
97
- const firstArg = consoleErrorSpy.mock.calls[0][0];
98
- expect(typeof firstArg).toBe('string');
99
- expect(firstArg).toContain('ERROR: publish failed:');
100
- consoleErrorSpy.mockRestore();
101
- });
102
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,60 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // tests/regex.test.ts
4
- const regex_1 = require("../src/regex");
5
- describe('escapeForRegex', () => {
6
- it('escapes regex special characters (including backslash)', () => {
7
- const input = 'Pikachu.$^*+?()[]{}|\\';
8
- const expected = 'Pikachu\\.\\$\\^\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\';
9
- expect((0, regex_1.escapeForRegex)(input)).toBe(expected);
10
- });
11
- it('replaces whitespace runs with \\s+ and escapes other metachars', () => {
12
- const input = 'Charizard (Holo Rare)'; // multiple spaces inside
13
- const expected = 'Charizard\\s+\\(Holo\\s+Rare\\)';
14
- expect((0, regex_1.escapeForRegex)(input)).toBe(expected);
15
- });
16
- it('leaves plain alphanumerics unchanged', () => {
17
- const input = 'Charmander123';
18
- expect((0, regex_1.escapeForRegex)(input)).toBe('Charmander123');
19
- });
20
- it('converts tabs and single spaces to \\s+ and escapes dots', () => {
21
- const input = 'Lt. Surge\tPikachu';
22
- const expected = 'Lt\\.\\s+Surge\\s+Pikachu';
23
- expect((0, regex_1.escapeForRegex)(input)).toBe(expected);
24
- });
25
- it('truncates input when maxLength is passed', () => {
26
- const long = 'a'.repeat(200);
27
- // override maxLength to 50 for this test
28
- expect((0, regex_1.escapeForRegex)(long, 50)).toBe('a'.repeat(50));
29
- });
30
- it('applies default cap of 100 characters when input is longer and contains no metachars', () => {
31
- const long = 'b'.repeat(150);
32
- const out = (0, regex_1.escapeForRegex)(long); // default cap = 100
33
- expect(out).toBe('b'.repeat(100));
34
- expect(out.length).toBe(100);
35
- });
36
- it('handles empty string', () => {
37
- expect((0, regex_1.escapeForRegex)('')).toBe('');
38
- });
39
- // -----------------------------
40
- // NEW: coverage for safeStr branch
41
- // -----------------------------
42
- it('handles undefined input (covers input ?? "")', () => {
43
- // call with undefined; TypeScript may complain so cast to any in test
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- expect((0, regex_1.escapeForRegex)(undefined)).toBe('');
46
- });
47
- it('handles null input (covers input ?? "")', () => {
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- expect((0, regex_1.escapeForRegex)(null)).toBe('');
50
- });
51
- it('coerces non-string input to string and escapes metachars if present', () => {
52
- // number becomes "42"
53
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
- expect((0, regex_1.escapeForRegex)(42)).toBe('42');
55
- // object coerces to "[object Object]" and special chars will be escaped if present
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- const objInput = { toString: () => 'X(1) Y' };
58
- expect((0, regex_1.escapeForRegex)(objInput)).toBe('X\\(1\\)\\s+Y');
59
- });
60
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,339 +0,0 @@
1
- "use strict";
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- // tests/retry.test.ts
4
- var __importDefault = (this && this.__importDefault) || function (mod) {
5
- return (mod && mod.__esModule) ? mod : { "default": mod };
6
- };
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- const axios_1 = __importDefault(require("axios"));
9
- const src_1 = require("../src");
10
- describe('sleep()', () => {
11
- const realAbortController = global.AbortController;
12
- afterEach(() => {
13
- // restore timers and AbortController if changed
14
- jest.useRealTimers();
15
- global.AbortController = realAbortController;
16
- jest.clearAllMocks();
17
- });
18
- test('resolves after the specified delay', async () => {
19
- jest.useFakeTimers();
20
- const p = (0, src_1.sleep)(100);
21
- // advance timers by the sleep duration
22
- jest.advanceTimersByTime(100);
23
- // allow the Promise microtask queue to run
24
- await Promise.resolve();
25
- await expect(p).resolves.toBeUndefined();
26
- });
27
- test('rejects immediately if signal is already aborted', async () => {
28
- const ac = new AbortController();
29
- ac.abort();
30
- await expect((0, src_1.sleep)(50, ac.signal)).rejects.toThrow('Aborted');
31
- });
32
- test('rejects when aborted after scheduling', async () => {
33
- jest.useFakeTimers();
34
- const ac = new AbortController();
35
- const p = (0, src_1.sleep)(1000, ac.signal);
36
- // abort after the sleep has been scheduled but before timeout
37
- ac.abort();
38
- // allow the event-loop microtasks to run
39
- await Promise.resolve();
40
- await expect(p).rejects.toThrow('Aborted');
41
- // advancing timers should not resolve the promise
42
- jest.advanceTimersByTime(1000);
43
- await Promise.resolve();
44
- await expect(p).rejects.toThrow('Aborted');
45
- });
46
- });
47
- describe('isRetryableDefault', () => {
48
- test('returns false for falsy errors (covers `if (!err) return false`)', () => {
49
- expect((0, src_1.isRetryableDefault)(null)).toBe(false);
50
- expect((0, src_1.isRetryableDefault)(undefined)).toBe(false);
51
- // Also check a falsy-but-not-nullish value is handled (should coerce to false at top)
52
- expect((0, src_1.isRetryableDefault)(0)).toBe(false);
53
- });
54
- test('returns false for an object with no retryable properties', () => {
55
- expect((0, src_1.isRetryableDefault)({})).toBe(false);
56
- expect((0, src_1.isRetryableDefault)({ foo: 'bar' })).toBe(false);
57
- });
58
- test('matches message containing "timeout" (case-insensitive)', () => {
59
- expect((0, src_1.isRetryableDefault)(new Error('Request timed out'))).toBe(true);
60
- expect((0, src_1.isRetryableDefault)(new Error('TIMEOUT occurred'))).toBe(true);
61
- expect((0, src_1.isRetryableDefault)({ message: 'this is a timeout error' })).toBe(true);
62
- });
63
- test('matches message containing "temporary"', () => {
64
- expect((0, src_1.isRetryableDefault)(new Error('Temporary failure contacting host'))).toBe(true);
65
- expect((0, src_1.isRetryableDefault)({ message: 'temporary issue' })).toBe(true);
66
- });
67
- test('matches message containing "unavailable"', () => {
68
- expect((0, src_1.isRetryableDefault)(new Error('Service Unavailable'))).toBe(true);
69
- expect((0, src_1.isRetryableDefault)({ message: 'unavailable resource' })).toBe(true);
70
- });
71
- test('matches message containing "econnreset" or "etimedout" (substrings, case-insensitive)', () => {
72
- expect((0, src_1.isRetryableDefault)(new Error('ECONNRESET by peer'))).toBe(true);
73
- expect((0, src_1.isRetryableDefault)(new Error('econnreset'))).toBe(true);
74
- expect((0, src_1.isRetryableDefault)(new Error('ETIMEDOUT waiting for response'))).toBe(true);
75
- expect((0, src_1.isRetryableDefault)({ message: 'some ETIMEDOUT occurred' })).toBe(true);
76
- });
77
- test('does not treat unrelated messages as retryable', () => {
78
- expect((0, src_1.isRetryableDefault)(new Error('Bad request'))).toBe(false);
79
- expect((0, src_1.isRetryableDefault)({ message: 'invalid parameter provided' })).toBe(false);
80
- });
81
- test('still returns true when message sits alongside other properties (defensive)', () => {
82
- const errLike = {
83
- code: undefined,
84
- response: undefined,
85
- message: 'temporary network hiccup'
86
- };
87
- expect((0, src_1.isRetryableDefault)(errLike)).toBe(true);
88
- });
89
- });
90
- describe('isRetryableAxios()', () => {
91
- // Helper to synthesize axios-like errors
92
- function makeAxiosError(payload = {}) {
93
- // minimal axios-style error: flag + response optionally
94
- const err = new Error(payload.message ?? 'axios-err');
95
- err.isAxiosError = true;
96
- if (Object.prototype.hasOwnProperty.call(payload, 'response')) {
97
- err.response = payload.response;
98
- }
99
- else if (payload.response === undefined && payload.noResponse) {
100
- // explicit network/no-response: leave out response
101
- }
102
- if (payload.code)
103
- err.code = payload.code;
104
- if (payload.message)
105
- err.message = payload.message;
106
- return err;
107
- }
108
- test('returns false when err is falsy', () => {
109
- expect((0, src_1.isRetryableAxios)(null)).toBe(false);
110
- expect((0, src_1.isRetryableAxios)(undefined)).toBe(false);
111
- // also check other falsy-ish values that shouldn't be considered retryable
112
- expect((0, src_1.isRetryableAxios)(false)).toBe(false);
113
- expect((0, src_1.isRetryableAxios)('')).toBe(false);
114
- });
115
- test('returns true for network/no-response axios errors', () => {
116
- const networkErr = makeAxiosError({ noResponse: true });
117
- expect(axios_1.default.isAxiosError(networkErr)).toBe(true); // sanity check
118
- expect((0, src_1.isRetryableAxios)(networkErr)).toBe(true);
119
- });
120
- test('returns true for 500 server error', () => {
121
- const serverErr = makeAxiosError({ response: { status: 500, data: {} } });
122
- expect((0, src_1.isRetryableAxios)(serverErr)).toBe(true);
123
- });
124
- test('returns true for 503 server error', () => {
125
- const serverErr = makeAxiosError({ response: { status: 503, data: {} } });
126
- expect((0, src_1.isRetryableAxios)(serverErr)).toBe(true);
127
- });
128
- test('returns true for 429 rate limit', () => {
129
- const rateErr = makeAxiosError({ response: { status: 429, data: {} } });
130
- expect((0, src_1.isRetryableAxios)(rateErr)).toBe(true);
131
- });
132
- test('returns false for 400 client error (do not retry)', () => {
133
- const badReq = makeAxiosError({ response: { status: 400, data: {} } });
134
- expect((0, src_1.isRetryableAxios)(badReq)).toBe(false);
135
- });
136
- test('delegates to isRetryableDefault for non-axios errors', () => {
137
- // make a network-style Node error that isRetryableDefault recognizes
138
- const nodeErr = { code: 'ECONNRESET', message: 'socket closed' };
139
- expect((0, src_1.isRetryableDefault)(nodeErr)).toBe(true); // sanity
140
- expect((0, src_1.isRetryableAxios)(nodeErr)).toBe(true);
141
- });
142
- test('returns false for non-retryable non-axios error', () => {
143
- const normalErr = new Error('something bad but not retryable');
144
- // isRetryableDefault likely returns false for a plain Error w/o hint
145
- expect((0, src_1.isRetryableDefault)(normalErr)).toBe(false);
146
- expect((0, src_1.isRetryableAxios)(normalErr)).toBe(false);
147
- });
148
- });
149
- describe('retry util', () => {
150
- afterEach(() => {
151
- jest.restoreAllMocks();
152
- });
153
- test('returns result when fn succeeds immediately', async () => {
154
- const fn = jest.fn(async () => 'ok');
155
- const res = await (0, src_1.retry)(fn, { retries: 3 });
156
- expect(res).toBe('ok');
157
- expect(fn).toHaveBeenCalledTimes(1);
158
- });
159
- test('retries on transient error and eventually succeeds; onRetry called expected times', async () => {
160
- // fail twice with retryable error (gRPC code 14), then succeed
161
- let calls = 0;
162
- const fn = jest.fn(async () => {
163
- calls++;
164
- if (calls <= 2) {
165
- const e = new Error('transient-gprc');
166
- e.code = 14;
167
- throw e;
168
- }
169
- return 'ok-after-retries';
170
- });
171
- const onRetry = jest.fn();
172
- // Make delays zero by forcing Math.random to 0 so tests are deterministic and fast
173
- const mathSpy = jest.spyOn(Math, 'random').mockReturnValue(0);
174
- const res = await (0, src_1.retry)(fn, { retries: 5, baseDelayMs: 10, onRetry });
175
- expect(res).toBe('ok-after-retries');
176
- expect(fn).toHaveBeenCalledTimes(3);
177
- // onRetry called for the 2 transient failures
178
- expect(onRetry).toHaveBeenCalledTimes(2);
179
- mathSpy.mockRestore();
180
- });
181
- test('non-retryable error is rethrown immediately', async () => {
182
- const fn = jest.fn(async () => {
183
- const e = new Error('bad request');
184
- e.status = 400;
185
- throw e;
186
- });
187
- await expect((0, src_1.retry)(fn, { retries: 3 })).rejects.toThrow('bad request');
188
- expect(fn).toHaveBeenCalledTimes(1);
189
- });
190
- test('exhausts retries and throws final error preserving original', async () => {
191
- const original = new Error('permanent transient-like');
192
- original.code = 14; // make it retryable
193
- const fn = jest.fn(async () => {
194
- throw original;
195
- });
196
- // make delays 0 to run fast
197
- jest.spyOn(Math, 'random').mockReturnValue(0);
198
- await expect((0, src_1.retry)(fn, { retries: 2, baseDelayMs: 1 })).rejects.toHaveProperty('original', original);
199
- // initial call + 2 retries = 3 attempts
200
- expect(fn).toHaveBeenCalledTimes(3);
201
- });
202
- test('isRetryableDefault recognizes common shapes', () => {
203
- const e1 = new Error('econnreset happened');
204
- e1.code = 'ECONNRESET';
205
- expect((0, src_1.isRetryableDefault)(e1)).toBe(true);
206
- const e2 = new Error('http 500');
207
- e2.response = { status: 500 };
208
- expect((0, src_1.isRetryableDefault)(e2)).toBe(true);
209
- const e3 = new Error('not retryable');
210
- e3.status = 400;
211
- expect((0, src_1.isRetryableDefault)(e3)).toBe(false);
212
- const e4 = new Error('gRPC unavailable');
213
- e4.code = 14;
214
- expect((0, src_1.isRetryableDefault)(e4)).toBe(true);
215
- });
216
- test('abort signal aborts immediately when already aborted', async () => {
217
- const controller = new AbortController();
218
- controller.abort();
219
- const fn = jest.fn(async () => 'should-not-run');
220
- await expect((0, src_1.retry)(fn, { signal: controller.signal })).rejects.toThrow('Aborted');
221
- // function should not be called at all because we check signal at loop start
222
- expect(fn).toHaveBeenCalledTimes(0);
223
- });
224
- test('abort during wait causes rejection', async () => {
225
- // Function will fail once (retryable), then retry -> sleep -> we abort during sleep.
226
- let calls = 0;
227
- const fn = jest.fn(async () => {
228
- calls++;
229
- if (calls === 1) {
230
- const e = new Error('transient');
231
- e.code = 14;
232
- throw e;
233
- }
234
- return 'should-not-get-here';
235
- });
236
- // Make delay large so we can abort during sleep (but we will abort synchronously)
237
- jest.spyOn(Math, 'random').mockReturnValue(1); // picks the max exp
238
- const controller = new AbortController();
239
- // Start the retry but abort immediately after allowing the utility to start.
240
- // We do not await retry here yet; create a Promise and then abort and await
241
- const p = (0, src_1.retry)(fn, { retries: 3, baseDelayMs: 200, signal: controller.signal });
242
- // Abort immediately — this should cause the pending sleep to reject
243
- controller.abort();
244
- await expect(p).rejects.toThrow('Aborted');
245
- });
246
- test('abort during wait causes rejection and triggers onAbort (clears timeout)', async () => {
247
- // Function will fail once (retryable), then retry -> sleep -> we abort during sleep.
248
- let calls = 0;
249
- const fn = jest.fn(async () => {
250
- calls++;
251
- if (calls === 1) {
252
- const e = new Error('transient');
253
- e.code = 14;
254
- throw e;
255
- }
256
- return 'should-not-get-here';
257
- });
258
- // Make sure there is a non-zero delay so sleep schedules a timer
259
- jest.spyOn(Math, 'random').mockReturnValue(0.5);
260
- const controller = new AbortController();
261
- // Start the retry (it will throw once, then call sleep)
262
- const p = (0, src_1.retry)(fn, { retries: 3, baseDelayMs: 200, signal: controller.signal });
263
- // Wait a tick so `sleep` has a chance to attach the abort listener and call setTimeout.
264
- // Using setTimeout 0 (or small timeout) guarantees the timer and listener are attached.
265
- await new Promise(resolve => setTimeout(resolve, 0));
266
- // Now abort — this should call the onAbort handler inside sleep which clears the timeout
267
- controller.abort();
268
- await expect(p).rejects.toThrow('Aborted');
269
- Math.random.mockRestore();
270
- });
271
- test('respects timeoutMs and breaks before waiting (covers timeout check)', async () => {
272
- // Make the wrapped fn throw a retryable error once
273
- const original = new Error('transient-timeout');
274
- original.code = 14; // make it retryable via isRetryableDefault
275
- const fn = jest.fn(async () => {
276
- throw original;
277
- });
278
- // Force Math.random to 1 so delay === exp (max for that attempt)
279
- const mathSpy = jest.spyOn(Math, 'random').mockReturnValue(1);
280
- // Use a baseDelayMs larger than timeoutMs so elapsed + delay > timeoutMs triggers the "break"
281
- await expect((0, src_1.retry)(fn, {
282
- retries: 5,
283
- baseDelayMs: 50, // exp for attempt 1 = 50
284
- timeoutMs: 10 // 50 > 10 so we should break before sleeping
285
- })).rejects.toHaveProperty('original', original);
286
- // fn should only have been called once (no retry/sleep occurred)
287
- expect(fn).toHaveBeenCalledTimes(1);
288
- mathSpy.mockRestore();
289
- });
290
- test('omitted opts uses default retries and eventually succeeds', async () => {
291
- // Fail twice with retryable error (gRPC code 14), then succeed
292
- let calls = 0;
293
- const fn = jest.fn(async () => {
294
- calls++;
295
- if (calls <= 2) {
296
- const e = new Error('transient');
297
- e.code = 14;
298
- throw e;
299
- }
300
- return 'ok-defaults';
301
- });
302
- // Make delays deterministic & small (Math.random = 0 -> delay = 0)
303
- jest.spyOn(Math, 'random').mockReturnValue(0);
304
- const res = await (0, src_1.retry)(fn); // <- call WITHOUT opts to exercise default param
305
- expect(res).toBe('ok-defaults');
306
- // Called initial + 2 retries = 3 times
307
- expect(fn).toHaveBeenCalledTimes(3);
308
- Math.random.mockRestore();
309
- });
310
- test('omitted opts exhausts default retries (5 attempts total) and throws', async () => {
311
- const original = new Error('always-transient');
312
- original.code = 14; // make it retryable
313
- const fn = jest.fn(async () => {
314
- throw original;
315
- });
316
- // Make delays 0 so test runs fast
317
- jest.spyOn(Math, 'random').mockReturnValue(0);
318
- // Default retries is 4 -> total attempts = 5
319
- await expect((0, src_1.retry)(fn)).rejects.toHaveProperty('original', original);
320
- expect(fn).toHaveBeenCalledTimes(5);
321
- Math.random.mockRestore();
322
- });
323
- test('final error message falls back to String(lastErr) when lastErr.message is undefined', async () => {
324
- // original is a plain object (no .message)
325
- const original = { code: 14, detail: 'plain-object-no-message' };
326
- const fn = jest.fn(async () => {
327
- throw original; // will be assigned to lastErr in retry()
328
- });
329
- // Make delays deterministic & fast
330
- const mathSpy = jest.spyOn(Math, 'random').mockReturnValue(0);
331
- // retries = 1 -> attempt will be 1 in finalErr message
332
- await expect((0, src_1.retry)(fn, { retries: 1, baseDelayMs: 1 })).rejects.toMatchObject({
333
- message: expect.stringContaining('[object Object]'),
334
- // Ensure original error was preserved on the thrown final error
335
- original
336
- });
337
- mathSpy.mockRestore();
338
- });
339
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,38 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const secrets_1 = require("../src/secrets");
4
- const secret_manager_1 = require("@google-cloud/secret-manager");
5
- jest.mock('@google-cloud/secret-manager');
6
- // Mock implementation
7
- const mockAccessSecretVersion = jest.fn();
8
- secret_manager_1.SecretManagerServiceClient.mockImplementation(() => ({
9
- accessSecretVersion: mockAccessSecretVersion
10
- }));
11
- describe('getSecret', () => {
12
- beforeEach(() => {
13
- jest.clearAllMocks();
14
- });
15
- it('returns secret value as string', async () => {
16
- mockAccessSecretVersion.mockResolvedValue([
17
- {
18
- payload: { data: Buffer.from('super-secret-value') }
19
- }
20
- ]);
21
- const result = await (0, secrets_1.getSecret)('test-project', 'test-secret');
22
- expect(result).toBe('super-secret-value');
23
- expect(mockAccessSecretVersion).toHaveBeenCalledWith({
24
- name: 'projects/test-project/secrets/test-secret/versions/latest'
25
- });
26
- });
27
- it('throws if projectId is missing', async () => {
28
- await expect((0, secrets_1.getSecret)('', 'secret')).rejects.toThrow('projectId is required');
29
- });
30
- it('throws if secretName is missing', async () => {
31
- await expect((0, secrets_1.getSecret)('project', '')).rejects.toThrow('secretName is required');
32
- });
33
- it('returns empty string if payload or data is missing', async () => {
34
- mockAccessSecretVersion.mockResolvedValue([{}]); // no payload
35
- const result = await (0, secrets_1.getSecret)('project', 'secret');
36
- expect(result).toBe('');
37
- });
38
- });
@@ -1 +0,0 @@
1
- export {};