@platformatic/watt-extra 0.1.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.
- package/README.md +87 -0
- package/app.js +124 -0
- package/cli.js +141 -0
- package/clients/compliance/compliance-types.d.ts +887 -0
- package/clients/compliance/compliance.mjs +1049 -0
- package/clients/compliance/compliance.openapi.json +6127 -0
- package/clients/control-plane/control-plane-types.d.ts +2696 -0
- package/clients/control-plane/control-plane.mjs +3051 -0
- package/clients/control-plane/control-plane.openapi.json +13693 -0
- package/clients/cron/cron-types.d.ts +1479 -0
- package/clients/cron/cron.mjs +872 -0
- package/clients/cron/cron.openapi.json +9330 -0
- package/compliance/index.js +21 -0
- package/compliance/rules/dependencies.js +76 -0
- package/compliance/rules/utils.js +12 -0
- package/eslint.config.js +11 -0
- package/help/start.txt +12 -0
- package/help/watt-extra.txt +12 -0
- package/index.js +45 -0
- package/lib/banner.js +22 -0
- package/lib/errors.js +34 -0
- package/lib/utils.js +34 -0
- package/lib/wattpro.js +580 -0
- package/package.json +50 -0
- package/plugins/alerts.js +115 -0
- package/plugins/auth.js +89 -0
- package/plugins/compliancy.js +70 -0
- package/plugins/env.js +58 -0
- package/plugins/flamegraphs.js +100 -0
- package/plugins/init.js +70 -0
- package/plugins/metadata.js +84 -0
- package/plugins/scheduler.js +48 -0
- package/plugins/update.js +128 -0
- package/renovate.json +6 -0
- package/test/alerts.test.js +607 -0
- package/test/auth.test.js +128 -0
- package/test/auto-cache.test.js +401 -0
- package/test/cli.test.js +75 -0
- package/test/compliancy.test.js +87 -0
- package/test/fixtures/runtime-domains/alpha/package.json +5 -0
- package/test/fixtures/runtime-domains/alpha/platformatic.json +6 -0
- package/test/fixtures/runtime-domains/alpha/plugin.js +16 -0
- package/test/fixtures/runtime-domains/beta/package.json +5 -0
- package/test/fixtures/runtime-domains/beta/platformatic.json +6 -0
- package/test/fixtures/runtime-domains/beta/plugin.js +7 -0
- package/test/fixtures/runtime-domains/composer/package.json +5 -0
- package/test/fixtures/runtime-domains/composer/platformatic.json +19 -0
- package/test/fixtures/runtime-domains/package.json +1 -0
- package/test/fixtures/runtime-domains/platformatic.json +27 -0
- package/test/fixtures/runtime-health/package.json +20 -0
- package/test/fixtures/runtime-health/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-1/package.json +17 -0
- package/test/fixtures/runtime-health/services/service-1/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-1/plugins/example.js +6 -0
- package/test/fixtures/runtime-health/services/service-1/routes/root.cjs +8 -0
- package/test/fixtures/runtime-health/services/service-2/package.json +17 -0
- package/test/fixtures/runtime-health/services/service-2/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-2/plugins/example.js +6 -0
- package/test/fixtures/runtime-health/services/service-2/routes/root.cjs +8 -0
- package/test/fixtures/runtime-next/package.json +5 -0
- package/test/fixtures/runtime-next/platformatic.json +9 -0
- package/test/fixtures/runtime-next/web/next/next.config.js +2 -0
- package/test/fixtures/runtime-next/web/next/package.json +7 -0
- package/test/fixtures/runtime-next/web/next/platformatic.json +9 -0
- package/test/fixtures/runtime-next/web/next/src/app/direct/route.js +3 -0
- package/test/fixtures/runtime-next/web/next/src/app/layout.jsx +7 -0
- package/test/fixtures/runtime-next/web/next/src/app/page.jsx +3 -0
- package/test/fixtures/runtime-scheduler/main/package.json +5 -0
- package/test/fixtures/runtime-scheduler/main/platformatic.json +9 -0
- package/test/fixtures/runtime-scheduler/main/routes/root.cjs +11 -0
- package/test/fixtures/runtime-scheduler/package.json +1 -0
- package/test/fixtures/runtime-scheduler/platformatic.json +27 -0
- package/test/fixtures/runtime-service/main/package.json +5 -0
- package/test/fixtures/runtime-service/main/platformatic.json +12 -0
- package/test/fixtures/runtime-service/main/routes/root.cjs +11 -0
- package/test/fixtures/runtime-service/package.json +1 -0
- package/test/fixtures/runtime-service/platformatic.json +19 -0
- package/test/fixtures/service-1/package.json +7 -0
- package/test/fixtures/service-1/platformatic.json +18 -0
- package/test/fixtures/service-1/routes/root.cjs +48 -0
- package/test/fixtures/service-2/platformatic.json +21 -0
- package/test/fixtures/service-2/routes/root.cjs +5 -0
- package/test/fixtures/service-3/package.json +5 -0
- package/test/fixtures/service-3/platformatic.json +21 -0
- package/test/fixtures/service-3/routes/root.cjs +8 -0
- package/test/health.test.js +44 -0
- package/test/helper.js +274 -0
- package/test/init.test.js +243 -0
- package/test/patch-config.test.js +434 -0
- package/test/scheduler.test.js +71 -0
- package/test/send-to-icc-retry.test.js +138 -0
- package/test/shared-context.test.js +82 -0
- package/test/spawn.test.js +110 -0
- package/test/trigger-flamegraphs.test.js +226 -0
- package/test/update.test.js +519 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { equal, deepEqual } from 'node:assert'
|
|
3
|
+
import { WebSocketServer } from 'ws'
|
|
4
|
+
import { setUpEnvironment } from './helper.js'
|
|
5
|
+
import updatePlugin from '../plugins/update.js'
|
|
6
|
+
import { once, EventEmitter } from 'node:events'
|
|
7
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
8
|
+
|
|
9
|
+
function createMockApp (port) {
|
|
10
|
+
return {
|
|
11
|
+
log: {
|
|
12
|
+
info: () => {},
|
|
13
|
+
error: () => {},
|
|
14
|
+
warn: () => {},
|
|
15
|
+
debug: () => {},
|
|
16
|
+
},
|
|
17
|
+
instanceConfig: {
|
|
18
|
+
applicationId: 'test-application-id',
|
|
19
|
+
},
|
|
20
|
+
getAuthorizationHeader: async () => {
|
|
21
|
+
return { Authorization: 'Bearer test-token' }
|
|
22
|
+
},
|
|
23
|
+
env: {
|
|
24
|
+
PLT_APP_NAME: 'test-app',
|
|
25
|
+
PLT_APP_DIR: '/path/to/app',
|
|
26
|
+
PLT_ICC_URL: `http://localhost:${port}`,
|
|
27
|
+
PLT_UPDATES_RECONNECT_INTERVAL_SEC: 1,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const port = 13000
|
|
32
|
+
|
|
33
|
+
test('update plugin connects to websocket', async (t) => {
|
|
34
|
+
const ee = new EventEmitter()
|
|
35
|
+
setUpEnvironment()
|
|
36
|
+
|
|
37
|
+
// Setup WebSocket server
|
|
38
|
+
const wss = new WebSocketServer({ port })
|
|
39
|
+
t.after(async () => wss.close())
|
|
40
|
+
|
|
41
|
+
wss.on('connection', (ws, req) => {
|
|
42
|
+
equal(
|
|
43
|
+
req.headers.authorization,
|
|
44
|
+
'Bearer test-token',
|
|
45
|
+
'Should authenticate with token'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Send a test subscription acknowledgment
|
|
49
|
+
ws.on('message', (data) => {
|
|
50
|
+
const message = JSON.parse(data.toString())
|
|
51
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
52
|
+
ws.send(
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
command: 'ack',
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
ee.emit('subscriptionAckSent')
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const app = createMockApp(port)
|
|
63
|
+
t.after(() => app.closeUpdates())
|
|
64
|
+
|
|
65
|
+
const recordedMessages = {
|
|
66
|
+
info: [],
|
|
67
|
+
warn: [],
|
|
68
|
+
error: [],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
app.log.info = (data, msg) => {
|
|
72
|
+
if (msg) {
|
|
73
|
+
recordedMessages.info.push({ data, msg })
|
|
74
|
+
} else {
|
|
75
|
+
recordedMessages.info.push(data)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await updatePlugin(app)
|
|
80
|
+
|
|
81
|
+
const ack = once(ee, 'subscriptionAckSent')
|
|
82
|
+
// don't await on purpose
|
|
83
|
+
app.connectToUpdates()
|
|
84
|
+
await ack
|
|
85
|
+
|
|
86
|
+
await sleep(200)
|
|
87
|
+
|
|
88
|
+
const subscriptionAckLog = recordedMessages.info.find(
|
|
89
|
+
(entry) =>
|
|
90
|
+
entry === 'Received subscription acknowledgment from updates websocket'
|
|
91
|
+
)
|
|
92
|
+
equal(!!subscriptionAckLog, true, 'Should log subscription acknowledgment')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('update plugin handles config update messages', async (t) => {
|
|
96
|
+
const ee = new EventEmitter()
|
|
97
|
+
setUpEnvironment()
|
|
98
|
+
|
|
99
|
+
const wss = new WebSocketServer({ port })
|
|
100
|
+
t.after(async () => wss.close())
|
|
101
|
+
|
|
102
|
+
let clientSocket = null
|
|
103
|
+
wss.on('connection', (ws, req) => {
|
|
104
|
+
clientSocket = ws
|
|
105
|
+
equal(
|
|
106
|
+
req.headers.authorization,
|
|
107
|
+
'Bearer test-token',
|
|
108
|
+
'Should authenticate with token'
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
ws.on('message', (data) => {
|
|
112
|
+
const message = JSON.parse(data.toString())
|
|
113
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
114
|
+
ws.send(
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
command: 'ack',
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
ee.emit('subscriptionAckSent')
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const app = createMockApp(port)
|
|
125
|
+
t.after(() => app.closeUpdates())
|
|
126
|
+
|
|
127
|
+
const loggedMessages = {
|
|
128
|
+
info: [],
|
|
129
|
+
warn: [],
|
|
130
|
+
error: [],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
app.log.info = (data, msg) => {
|
|
134
|
+
if (msg) {
|
|
135
|
+
loggedMessages.info.push({ data, msg })
|
|
136
|
+
} else {
|
|
137
|
+
loggedMessages.info.push(data)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await updatePlugin(app)
|
|
142
|
+
|
|
143
|
+
// Track processed messages
|
|
144
|
+
const processedMessages = []
|
|
145
|
+
app.updateConfig = async (message) => {
|
|
146
|
+
processedMessages.push(message)
|
|
147
|
+
ee.emit('config-updated', message)
|
|
148
|
+
}
|
|
149
|
+
const ack = once(ee, 'subscriptionAckSent')
|
|
150
|
+
// don't await on purpose
|
|
151
|
+
app.connectToUpdates()
|
|
152
|
+
await ack
|
|
153
|
+
|
|
154
|
+
await sleep(200)
|
|
155
|
+
|
|
156
|
+
const testMessage = {
|
|
157
|
+
topic: '/config',
|
|
158
|
+
type: 'config-updated',
|
|
159
|
+
data: {
|
|
160
|
+
version: '1.0.0',
|
|
161
|
+
settings: {
|
|
162
|
+
feature1: true,
|
|
163
|
+
feature2: false,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
clientSocket.send(JSON.stringify(testMessage))
|
|
169
|
+
|
|
170
|
+
const configUpdate = once(ee, 'config-updated')
|
|
171
|
+
await configUpdate
|
|
172
|
+
|
|
173
|
+
equal(processedMessages.length, 1)
|
|
174
|
+
|
|
175
|
+
const updatedMessage = processedMessages[0]
|
|
176
|
+
equal(updatedMessage.topic, '/config', 'Should receive the correct topic')
|
|
177
|
+
equal(
|
|
178
|
+
updatedMessage.type,
|
|
179
|
+
'config-updated',
|
|
180
|
+
'Should receive the correct type'
|
|
181
|
+
)
|
|
182
|
+
deepEqual(
|
|
183
|
+
updatedMessage.data,
|
|
184
|
+
testMessage.data,
|
|
185
|
+
'Should receive the correct data'
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('update plugin ignores messages with unknown type', async (t) => {
|
|
190
|
+
const ee = new EventEmitter()
|
|
191
|
+
setUpEnvironment()
|
|
192
|
+
|
|
193
|
+
// Setup WebSocket server
|
|
194
|
+
const wss = new WebSocketServer({ port })
|
|
195
|
+
t.after(async () => wss.close())
|
|
196
|
+
|
|
197
|
+
let clientSocket = null
|
|
198
|
+
wss.on('connection', (ws, req) => {
|
|
199
|
+
clientSocket = ws
|
|
200
|
+
ws.on('message', (data) => {
|
|
201
|
+
const message = JSON.parse(data.toString())
|
|
202
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
203
|
+
ws.send(
|
|
204
|
+
JSON.stringify({
|
|
205
|
+
command: 'ack',
|
|
206
|
+
})
|
|
207
|
+
)
|
|
208
|
+
ee.emit('subscriptionAckSent')
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const app = createMockApp(port)
|
|
214
|
+
t.after(() => app.closeUpdates())
|
|
215
|
+
|
|
216
|
+
// Track processed messages
|
|
217
|
+
const processedMessages = []
|
|
218
|
+
app.updateConfig = async (message) => {
|
|
219
|
+
processedMessages.push(message)
|
|
220
|
+
ee.emit('config-updated', message)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const loggedMessages = {
|
|
224
|
+
info: [],
|
|
225
|
+
warn: [],
|
|
226
|
+
error: [],
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
app.log.info = (data, msg) => {
|
|
230
|
+
if (msg) {
|
|
231
|
+
loggedMessages.info.push({ data, msg })
|
|
232
|
+
} else {
|
|
233
|
+
loggedMessages.info.push(data)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await updatePlugin(app)
|
|
238
|
+
await app.connectToUpdates()
|
|
239
|
+
|
|
240
|
+
// Send a message with unknown type
|
|
241
|
+
const testMessage = {
|
|
242
|
+
topic: '/config',
|
|
243
|
+
type: 'unknownType',
|
|
244
|
+
data: { test: true },
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
clientSocket.send(JSON.stringify(testMessage))
|
|
248
|
+
await sleep(200)
|
|
249
|
+
|
|
250
|
+
// The message should be logged but not processed by updateConfig
|
|
251
|
+
equal(
|
|
252
|
+
processedMessages.length,
|
|
253
|
+
0,
|
|
254
|
+
'Should not process messages with unknown type'
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const unknownTypeLog = loggedMessages.info.find(
|
|
258
|
+
(log) =>
|
|
259
|
+
log.data?.topic === '/config' &&
|
|
260
|
+
log.data?.type === 'unknownType' &&
|
|
261
|
+
log.msg === 'Received message, not handled type'
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
equal(
|
|
265
|
+
!!unknownTypeLog,
|
|
266
|
+
true,
|
|
267
|
+
'Should log when receiving message with unknown type'
|
|
268
|
+
)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('update plugin handles invalid messages', async (t) => {
|
|
272
|
+
const ee = new EventEmitter()
|
|
273
|
+
setUpEnvironment()
|
|
274
|
+
|
|
275
|
+
const wss = new WebSocketServer({ port })
|
|
276
|
+
t.after(async () => wss.close())
|
|
277
|
+
|
|
278
|
+
let clientSocket = null
|
|
279
|
+
wss.on('connection', (ws, req) => {
|
|
280
|
+
clientSocket = ws
|
|
281
|
+
|
|
282
|
+
ws.on('message', (data) => {
|
|
283
|
+
const message = JSON.parse(data.toString())
|
|
284
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
285
|
+
ws.send(
|
|
286
|
+
JSON.stringify({
|
|
287
|
+
command: 'ack',
|
|
288
|
+
})
|
|
289
|
+
)
|
|
290
|
+
ee.emit('subscriptionAckSent')
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const app = createMockApp(port)
|
|
296
|
+
t.after(() => app.closeUpdates())
|
|
297
|
+
|
|
298
|
+
const loggedMessages = {
|
|
299
|
+
info: [],
|
|
300
|
+
warn: [],
|
|
301
|
+
error: [],
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
app.log.warn = (data, msg) => {
|
|
305
|
+
if (msg) {
|
|
306
|
+
loggedMessages.warn.push({ data, msg })
|
|
307
|
+
} else {
|
|
308
|
+
loggedMessages.warn.push(data)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await updatePlugin(app)
|
|
313
|
+
await app.connectToUpdates()
|
|
314
|
+
|
|
315
|
+
// Send an invalid message (missing topic)
|
|
316
|
+
const invalidMessage = {
|
|
317
|
+
type: 'configUpdate',
|
|
318
|
+
data: { test: true },
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
clientSocket.send(JSON.stringify(invalidMessage))
|
|
322
|
+
|
|
323
|
+
await sleep(100)
|
|
324
|
+
|
|
325
|
+
const invalidMsgLog = loggedMessages.warn.find(
|
|
326
|
+
(log) => log.msg === 'Received invalid message from updates websocket'
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
equal(
|
|
330
|
+
!!invalidMsgLog,
|
|
331
|
+
true,
|
|
332
|
+
'Should log warning when receiving invalid message'
|
|
333
|
+
)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test('update plugin handles connection errors', async (t) => {
|
|
337
|
+
setUpEnvironment()
|
|
338
|
+
|
|
339
|
+
const app = createMockApp(9999) // Non-existent port
|
|
340
|
+
t.after(() => app.closeUpdates())
|
|
341
|
+
|
|
342
|
+
const loggedErrors = []
|
|
343
|
+
app.log.error = (err, msg) => {
|
|
344
|
+
loggedErrors.push({ err, msg })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await updatePlugin(app)
|
|
348
|
+
await app.connectToUpdates()
|
|
349
|
+
|
|
350
|
+
equal(loggedErrors.length >= 1, true, 'Error should be logged')
|
|
351
|
+
equal(
|
|
352
|
+
loggedErrors[0].msg,
|
|
353
|
+
'Failed to connect and subscribe to updates websocket',
|
|
354
|
+
'Should log connection error'
|
|
355
|
+
)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
test('update plugin closeUpdates method closes the connection', async (t) => {
|
|
359
|
+
const ee = new EventEmitter()
|
|
360
|
+
setUpEnvironment()
|
|
361
|
+
|
|
362
|
+
const closedConnections = []
|
|
363
|
+
|
|
364
|
+
const wss = new WebSocketServer({ port })
|
|
365
|
+
t.after(async () => wss.close())
|
|
366
|
+
|
|
367
|
+
wss.on('connection', (ws) => {
|
|
368
|
+
ws.on('close', () => {
|
|
369
|
+
closedConnections.push(ws)
|
|
370
|
+
ee.emit('connectionClosed')
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
ws.on('message', (data) => {
|
|
374
|
+
const message = JSON.parse(data.toString())
|
|
375
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
376
|
+
ws.send(
|
|
377
|
+
JSON.stringify({
|
|
378
|
+
command: 'ack',
|
|
379
|
+
})
|
|
380
|
+
)
|
|
381
|
+
ee.emit('subscriptionAckSent')
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
const app = createMockApp(port)
|
|
387
|
+
|
|
388
|
+
await updatePlugin(app)
|
|
389
|
+
await app.connectToUpdates()
|
|
390
|
+
|
|
391
|
+
await app.closeUpdates()
|
|
392
|
+
|
|
393
|
+
const closeEvent = once(ee, 'connectionClosed')
|
|
394
|
+
await Promise.race([closeEvent, sleep(1000)])
|
|
395
|
+
|
|
396
|
+
equal(closedConnections.length, 1, 'WebSocket connection should be closed')
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
test('update plugin handles missing PLT_ICC_URL', async (t) => {
|
|
400
|
+
setUpEnvironment()
|
|
401
|
+
|
|
402
|
+
const app = createMockApp(port)
|
|
403
|
+
// Remove PLT_ICC_URL to test missing URL scenario
|
|
404
|
+
delete app.env.PLT_ICC_URL
|
|
405
|
+
|
|
406
|
+
const loggedMessages = {
|
|
407
|
+
warn: [],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
app.log.warn = (msg) => {
|
|
411
|
+
loggedMessages.warn.push(msg)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
await updatePlugin(app)
|
|
415
|
+
const result = await app.connectToUpdates()
|
|
416
|
+
|
|
417
|
+
equal(result, null, 'Should return null when PLT_ICC_URL is missing')
|
|
418
|
+
equal(loggedMessages.warn.length, 1, 'Should log a warning')
|
|
419
|
+
equal(
|
|
420
|
+
loggedMessages.warn[0],
|
|
421
|
+
'No PLT_ICC_URL found in environment, cannot connect to updates websocket'
|
|
422
|
+
)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test('update plugin handles missing applicationId', async (t) => {
|
|
426
|
+
setUpEnvironment()
|
|
427
|
+
|
|
428
|
+
const app = createMockApp(port)
|
|
429
|
+
// Remove applicationId to test missing ID scenario
|
|
430
|
+
delete app.instanceConfig.applicationId
|
|
431
|
+
|
|
432
|
+
const loggedMessages = {
|
|
433
|
+
warn: [],
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
app.log.warn = (msg) => {
|
|
437
|
+
loggedMessages.warn.push(msg)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
await updatePlugin(app)
|
|
441
|
+
const result = await app.connectToUpdates()
|
|
442
|
+
|
|
443
|
+
equal(result, null, 'Should return null when applicationId is missing')
|
|
444
|
+
equal(loggedMessages.warn.length, 1, 'Should log a warning')
|
|
445
|
+
equal(
|
|
446
|
+
loggedMessages.warn[0],
|
|
447
|
+
'No application ID found, cannot connect to updates websocket'
|
|
448
|
+
)
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
test('should reconnect to updates if connection closes', async (t) => {
|
|
452
|
+
function startWebSocketServer () {
|
|
453
|
+
// Setup WebSocket server
|
|
454
|
+
const wss = new WebSocketServer({ port })
|
|
455
|
+
|
|
456
|
+
wss.connections = []
|
|
457
|
+
wss.on('connection', (ws) => {
|
|
458
|
+
wss.connections.push(ws)
|
|
459
|
+
ws.on('message', (data) => {
|
|
460
|
+
const message = JSON.parse(data.toString())
|
|
461
|
+
if (message.command === 'subscribe' && message.topic === '/config') {
|
|
462
|
+
ws.send(JSON.stringify({ command: 'ack' }))
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
wss.broadcast = (data) => {
|
|
468
|
+
wss.connections.forEach((ws) => {
|
|
469
|
+
ws.send(data)
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return wss
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const wss1 = startWebSocketServer()
|
|
477
|
+
|
|
478
|
+
const app = createMockApp(port)
|
|
479
|
+
t.after(() => app.closeUpdates())
|
|
480
|
+
await updatePlugin(app)
|
|
481
|
+
|
|
482
|
+
const processedMessages = []
|
|
483
|
+
app.updateConfig = async (message) => {
|
|
484
|
+
processedMessages.push(message)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
await app.connectToUpdates()
|
|
488
|
+
|
|
489
|
+
const testMessage1 = {
|
|
490
|
+
topic: '/config',
|
|
491
|
+
type: 'config-updated',
|
|
492
|
+
data: { foo: 'bar' },
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
wss1.broadcast(JSON.stringify(testMessage1))
|
|
496
|
+
await sleep(200)
|
|
497
|
+
|
|
498
|
+
deepEqual(processedMessages, [testMessage1])
|
|
499
|
+
|
|
500
|
+
wss1.close()
|
|
501
|
+
for (const ws of wss1.connections) {
|
|
502
|
+
ws.terminate()
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const wss2 = startWebSocketServer()
|
|
506
|
+
await sleep(2000)
|
|
507
|
+
|
|
508
|
+
const testMessage2 = {
|
|
509
|
+
topic: '/config',
|
|
510
|
+
type: 'config-updated',
|
|
511
|
+
data: { foo: 'baz' },
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
wss2.broadcast(JSON.stringify(testMessage2))
|
|
515
|
+
await sleep(200)
|
|
516
|
+
|
|
517
|
+
deepEqual(processedMessages, [testMessage1, testMessage2])
|
|
518
|
+
wss2.close()
|
|
519
|
+
})
|