@adalo/metrics 0.1.164 → 0.1.165

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,221 @@
1
+ const http = require('http')
2
+ const https = require('https')
3
+ const { RedisMetricsClient } = require('../src/metrics/metricsRedisClient')
4
+ const { IOREDIS, REDIS_V3, REDIS_V4 } = require('../src/redisUtils')
5
+
6
+ jest.mock('../src/redisUtils', () => ({
7
+ ...jest.requireActual('../src/redisUtils'),
8
+ getRedisClientType: jest.fn(),
9
+ IOREDIS: 'IOREDIS',
10
+ REDIS_V3: 'REDIS_V3',
11
+ REDIS_V4: 'REDIS_V4',
12
+ }))
13
+
14
+ const { getRedisClientType } = require('../src/redisUtils')
15
+
16
+ describe('RedisMetricsClient', () => {
17
+ const originalEnv = process.env
18
+ let mockRedisClient
19
+ let mockHttpRequest
20
+ let mockHttpsRequest
21
+
22
+ beforeEach(() => {
23
+ jest.clearAllMocks()
24
+ process.env = { ...originalEnv }
25
+ process.exit = jest.fn()
26
+
27
+ mockRedisClient = {
28
+ duplicate: jest.fn(() => ({
29
+ on: jest.fn(),
30
+ subscribe: jest.fn(),
31
+ disconnect: jest.fn(),
32
+ call: jest.fn().mockResolvedValue(null),
33
+ sendCommand: jest.fn().mockResolvedValue(null),
34
+ send_command: jest.fn((cmd, args, cb) => (cb ? cb(null, null) : undefined)),
35
+ })),
36
+ publish: jest.fn().mockResolvedValue(1),
37
+ sendCommand: jest.fn().mockResolvedValue(1),
38
+ send_command: jest.fn((cmd, args, cb) => (cb ? cb(null, 1) : undefined)),
39
+ disconnect: jest.fn(),
40
+ quit: jest.fn().mockResolvedValue(undefined),
41
+ }
42
+
43
+ const createMockRequest = (cb) => {
44
+ const req = {
45
+ setHeader: jest.fn(),
46
+ end: jest.fn(),
47
+ on: jest.fn(),
48
+ }
49
+ setImmediate(() => {
50
+ const res = { statusCode: 200, on: jest.fn() }
51
+ if (cb) cb(res)
52
+ })
53
+ return req
54
+ }
55
+ mockHttpRequest = jest.spyOn(http, 'request').mockImplementation((opts, cb) => {
56
+ const req = createMockRequest((res) => {
57
+ if (typeof cb === 'function') cb(res)
58
+ })
59
+ return req
60
+ })
61
+ mockHttpsRequest = jest.spyOn(https, 'request').mockImplementation((opts, cb) => {
62
+ const req = createMockRequest((res) => {
63
+ if (typeof cb === 'function') cb(res)
64
+ })
65
+ return req
66
+ })
67
+ })
68
+
69
+ afterEach(() => {
70
+ process.env = originalEnv
71
+ mockHttpRequest.mockRestore()
72
+ mockHttpsRequest.mockRestore()
73
+ })
74
+
75
+ describe('constructor', () => {
76
+ it('throws when redisClient is missing', () => {
77
+ expect(() => new RedisMetricsClient({})).toThrow('RedisMetricsClient requires redisClient')
78
+ expect(() => new RedisMetricsClient()).toThrow('RedisMetricsClient requires redisClient')
79
+ expect(() => new RedisMetricsClient({ redisClient: null })).toThrow('RedisMetricsClient requires redisClient')
80
+ })
81
+
82
+ it('accepts redisClient and passes other options via metricsConfig', () => {
83
+ getRedisClientType.mockReturnValue(IOREDIS)
84
+ const client = new RedisMetricsClient({
85
+ redisClient: mockRedisClient,
86
+ appName: 'my-app',
87
+ processType: 'web',
88
+ pushgatewayUrl: 'http://vm:8428/api/v1/import/prometheus',
89
+ })
90
+ expect(client.redisClient).toBe(mockRedisClient)
91
+ expect(client.appName).toBe('my-app')
92
+ expect(client.processType).toBe('web')
93
+ expect(client.pushgatewayUrl).toBe('http://vm:8428/api/v1/import/prometheus')
94
+ })
95
+
96
+ it('reads gracefulShutdownRedis from metricsConfig', () => {
97
+ getRedisClientType.mockReturnValue(IOREDIS)
98
+ const client = new RedisMetricsClient({
99
+ redisClient: mockRedisClient,
100
+ gracefulShutdownRedis: false,
101
+ })
102
+ expect(client._gracefulShutdownRedis).toBe(false)
103
+ expect(client._gracefulShutdownStream).toBeNull()
104
+ })
105
+
106
+ it('enables graceful shutdown by default', () => {
107
+ getRedisClientType.mockReturnValue(IOREDIS)
108
+ const client = new RedisMetricsClient({
109
+ redisClient: mockRedisClient,
110
+ appName: 'test-app',
111
+ processType: 'worker',
112
+ })
113
+ expect(client._gracefulShutdownRedis).toBe(true)
114
+ expect(client._gracefulShutdownStream).toBe('metrics:graceful-shutdown:test-app:worker')
115
+ expect(client._gracefulShutdownAckChannel).toBe('metrics:graceful-shutdown-ack:test-app:worker')
116
+ })
117
+ })
118
+
119
+ describe('_initGracefulShutdown', () => {
120
+ it('sets channels and log prefix when enabled', () => {
121
+ getRedisClientType.mockReturnValue(IOREDIS)
122
+ const client = new RedisMetricsClient({
123
+ redisClient: mockRedisClient,
124
+ appName: 'myapp',
125
+ processType: 'queue-metrics',
126
+ })
127
+ expect(client._gracefulShutdownLogPrefix).toContain('myapp')
128
+ expect(client._gracefulShutdownLogPrefix).toContain('queue-metrics')
129
+ })
130
+
131
+ it('respects METRICS_GRACEFUL_SHUTDOWN_REDIS=false', () => {
132
+ process.env.METRICS_GRACEFUL_SHUTDOWN_REDIS = 'false'
133
+ getRedisClientType.mockReturnValue(IOREDIS)
134
+ const client = new RedisMetricsClient({
135
+ redisClient: mockRedisClient,
136
+ })
137
+ expect(client._gracefulShutdownRedis).toBe(false)
138
+ })
139
+ })
140
+
141
+ describe('_createSubscriberClient', () => {
142
+ it('uses duplicate() for IOREDIS client', () => {
143
+ getRedisClientType.mockReturnValue(IOREDIS)
144
+ const dup = {
145
+ on: jest.fn(),
146
+ subscribe: jest.fn(),
147
+ call: jest.fn().mockResolvedValue(null),
148
+ }
149
+ mockRedisClient.duplicate.mockReturnValue(dup)
150
+ const client = new RedisMetricsClient({
151
+ redisClient: mockRedisClient,
152
+ gracefulShutdownRedis: true,
153
+ })
154
+ expect(client._subClient).toBe(dup)
155
+ expect(mockRedisClient.duplicate).toHaveBeenCalled()
156
+ })
157
+
158
+ it('uses duplicate() for node-redis v4 when available', () => {
159
+ getRedisClientType.mockReturnValue(REDIS_V4)
160
+ const dup = {
161
+ on: jest.fn(),
162
+ subscribe: jest.fn(),
163
+ quit: jest.fn().mockResolvedValue(),
164
+ sendCommand: jest.fn().mockResolvedValue(null),
165
+ }
166
+ mockRedisClient.duplicate = jest.fn(() => dup)
167
+ const client = new RedisMetricsClient({
168
+ redisClient: mockRedisClient,
169
+ gracefulShutdownRedis: true,
170
+ })
171
+ expect(client._subClient).toBe(dup)
172
+ expect(mockRedisClient.duplicate).toHaveBeenCalled()
173
+ })
174
+
175
+ it('does not override skipFirstPush from config', () => {
176
+ getRedisClientType.mockReturnValue(IOREDIS)
177
+ const client = new RedisMetricsClient({
178
+ redisClient: mockRedisClient,
179
+ skipFirstPush: false,
180
+ })
181
+ expect(client.skipFirstPush).toBe(false)
182
+ })
183
+ })
184
+
185
+ describe('gateway push (mocked)', () => {
186
+ it('calls http.request when pushgatewayUrl is set', async () => {
187
+ getRedisClientType.mockReturnValue(IOREDIS)
188
+ const client = new RedisMetricsClient({
189
+ redisClient: mockRedisClient,
190
+ pushgatewayUrl: 'http://localhost:8428/api/v1/import/prometheus',
191
+ enabled: true,
192
+ cleanupExitsProcess: false,
193
+ })
194
+ await client.gatewayPush()
195
+ expect(mockHttpRequest).toHaveBeenCalled()
196
+ expect(mockHttpRequest.mock.calls[0][0].method).toBe('POST')
197
+ expect(mockHttpRequest.mock.calls[0][0].path).toContain('import/prometheus')
198
+ })
199
+ })
200
+
201
+ describe('gateway delete (mocked)', () => {
202
+ it('calls http.request for delete_series when removeOldMetrics and pushgatewayUrl set', async () => {
203
+ getRedisClientType.mockReturnValue(IOREDIS)
204
+ const client = new RedisMetricsClient({
205
+ redisClient: mockRedisClient,
206
+ pushgatewayUrl: 'http://localhost:8428/api/v1/import/prometheus',
207
+ removeOldMetrics: true,
208
+ enabled: true,
209
+ appName: 'test-app',
210
+ dynoId: 'dyno-1',
211
+ processType: 'web',
212
+ cleanupExitsProcess: false,
213
+ })
214
+ await client.gatewayDelete()
215
+ expect(mockHttpRequest).toHaveBeenCalled()
216
+ const call = mockHttpRequest.mock.calls[0][0]
217
+ expect(call.path).toContain('delete_series')
218
+ expect(call.path).toContain('match')
219
+ })
220
+ })
221
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.164",
3
+ "version": "0.1.165",
4
4
  "description": "Reusable metrics utilities for Node.js and Laravel apps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -31,10 +31,10 @@
31
31
  "@types/pg": "^8.15.6"
32
32
  },
33
33
  "devDependencies": {
34
- "@babel/cli": "7.18.6",
35
- "@babel/core": "7.18.6",
36
- "@babel/preset-env": "7.18.6",
37
- "@babel/preset-typescript": "7.18.6",
34
+ "@babel/cli": "^7.24.0",
35
+ "@babel/core": "^7.24.0",
36
+ "@babel/preset-env": "^7.24.0",
37
+ "@babel/preset-typescript": "^7.24.0",
38
38
  "@types/ioredis": "^5.0.0",
39
39
  "@types/jest": "28.1.6",
40
40
  "@types/node": "18.0.4",