@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
package/test/helper.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Agent, setGlobalDispatcher } from 'undici'
|
|
2
|
+
import { mkdir, symlink, writeFile, rm } from 'node:fs/promises'
|
|
3
|
+
import { join, dirname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import fastify from 'fastify'
|
|
6
|
+
import fp from 'fastify-plugin'
|
|
7
|
+
import why from 'why-is-node-running'
|
|
8
|
+
import fastifyWebsocket from '@fastify/websocket'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = dirname(__filename)
|
|
12
|
+
|
|
13
|
+
setInterval(why, 120000).unref()
|
|
14
|
+
|
|
15
|
+
setGlobalDispatcher(new Agent({
|
|
16
|
+
keepAliveTimeout: 10,
|
|
17
|
+
keepAliveMaxTimeout: 10
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
function setUpEnvironment (env = {}) {
|
|
21
|
+
const defaultEnv = {
|
|
22
|
+
PLT_ZIO_HOSTNAME: '127.0.0.1',
|
|
23
|
+
PLT_ZIO_PORT: 4042,
|
|
24
|
+
PLT_ZIO_LOG_LEVEL: 'debug',
|
|
25
|
+
PLT_APP_HOSTNAME: '127.0.0.1',
|
|
26
|
+
PLT_APP_PORT: 3042,
|
|
27
|
+
PLT_METRICS_PORT: 9090,
|
|
28
|
+
PLT_DISABLE_COMPLIANCE_CHECK: 'true',
|
|
29
|
+
PLT_DISABLE_FLAMEGRAPHS: 'true',
|
|
30
|
+
PLT_THROW_ON_COMPLIANCE_FAILURE: 'false',
|
|
31
|
+
PLT_TEST_TOKEN: createJwtToken(3600)
|
|
32
|
+
}
|
|
33
|
+
Object.assign(process.env, defaultEnv, env)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function startICC (t, opts = {}) {
|
|
37
|
+
let {
|
|
38
|
+
applicationId,
|
|
39
|
+
applicationName,
|
|
40
|
+
iccServices,
|
|
41
|
+
iccConfig = {},
|
|
42
|
+
enableOpenTelemetry = false,
|
|
43
|
+
enableSlicerInterceptor = false,
|
|
44
|
+
enableTrafficInterceptor = false,
|
|
45
|
+
controlPlaneResponse
|
|
46
|
+
} = opts
|
|
47
|
+
|
|
48
|
+
iccServices = iccServices || {
|
|
49
|
+
riskEngine: {
|
|
50
|
+
url: 'http://127.0.0.1:3000/risk-service'
|
|
51
|
+
},
|
|
52
|
+
trafficante: {
|
|
53
|
+
url: 'http://127.0.0.1:3000/trafficante'
|
|
54
|
+
},
|
|
55
|
+
compliance: {
|
|
56
|
+
url: 'http://127.0.0.1:3000/compliance'
|
|
57
|
+
},
|
|
58
|
+
cron: {
|
|
59
|
+
url: 'http://127.0.0.1:3000/cron'
|
|
60
|
+
},
|
|
61
|
+
scaler: {
|
|
62
|
+
url: 'http://127.0.0.1:3000/scaler'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const icc = fastify({
|
|
67
|
+
keepAliveTimeout: 1,
|
|
68
|
+
forceCloseConnections: true
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await icc.register(fastifyWebsocket)
|
|
72
|
+
|
|
73
|
+
// Main
|
|
74
|
+
await icc.register(fp(async (icc) => {
|
|
75
|
+
const connections = new Map()
|
|
76
|
+
|
|
77
|
+
icc.decorate('emitApplicationUpdate', (applicationId, config) => {
|
|
78
|
+
const appConnections = connections.get(applicationId)
|
|
79
|
+
if (appConnections) {
|
|
80
|
+
for (const connection of appConnections) {
|
|
81
|
+
connection.send(JSON.stringify(config))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
icc.get('/api/updates/applications/:id', { websocket: true }, async (connection, req) => {
|
|
87
|
+
connection.on('message', (message) => {
|
|
88
|
+
const applicationId = req.params.id
|
|
89
|
+
const { command, topic } = JSON.parse(message.toString())
|
|
90
|
+
|
|
91
|
+
if (command === 'subscribe' && topic === '/config') {
|
|
92
|
+
connection.send(JSON.stringify({ command: 'ack' }))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let appConnections = connections.get(applicationId)
|
|
96
|
+
if (!appConnections) {
|
|
97
|
+
appConnections = []
|
|
98
|
+
connections.set(applicationId, appConnections)
|
|
99
|
+
}
|
|
100
|
+
appConnections.push(connection)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
// Control Plane
|
|
106
|
+
await icc.register(async (icc) => {
|
|
107
|
+
icc.post('/pods/:podId/instance', async (req) => {
|
|
108
|
+
if (typeof controlPlaneResponse === 'function') {
|
|
109
|
+
return controlPlaneResponse(req)
|
|
110
|
+
}
|
|
111
|
+
return controlPlaneResponse || {
|
|
112
|
+
applicationId,
|
|
113
|
+
applicationName,
|
|
114
|
+
iccServices,
|
|
115
|
+
config: iccConfig,
|
|
116
|
+
enableOpenTelemetry,
|
|
117
|
+
enableSlicerInterceptor,
|
|
118
|
+
enableTrafficInterceptor
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
icc.post('/applications/:id/metadata', async (req) => {
|
|
123
|
+
const { applicationId, data } = req.body
|
|
124
|
+
return opts.saveComplianceMetadata?.(applicationId, data)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
icc.post('/pods/:id/instance/state', async (req) => {
|
|
128
|
+
const instanceId = req.params.id
|
|
129
|
+
const state = req.body
|
|
130
|
+
|
|
131
|
+
await opts.saveApplicationInstanceState?.({ instanceId, state })
|
|
132
|
+
return {}
|
|
133
|
+
})
|
|
134
|
+
}, { prefix: '/control-plane' })
|
|
135
|
+
|
|
136
|
+
// Compliance
|
|
137
|
+
await icc.register(async (icc) => {
|
|
138
|
+
icc.post('/metadata', async (req) => {
|
|
139
|
+
const { applicationId, data } = req.body
|
|
140
|
+
return opts.saveComplianceMetadata?.(applicationId, data)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
icc.post('/compliance', async (req) => {
|
|
144
|
+
const { applicationId } = req.body
|
|
145
|
+
return opts.getComplianceReport?.(applicationId)
|
|
146
|
+
})
|
|
147
|
+
}, { prefix: '/compliance' })
|
|
148
|
+
|
|
149
|
+
// Trafficante
|
|
150
|
+
await icc.register(async (icc) => {
|
|
151
|
+
icc.post('/requests/hash', async (req) => {
|
|
152
|
+
const { taxonomyId, applicationId } = JSON.parse(req.headers['x-labels'])
|
|
153
|
+
const { timestamp, request, response } = req.body
|
|
154
|
+
opts.saveRequestHash?.({ taxonomyId, applicationId, timestamp, request, response })
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
icc.post('/requests', async (req) => {
|
|
158
|
+
const { taxonomyId, applicationId } = JSON.parse(req.headers['x-labels'])
|
|
159
|
+
const request = JSON.parse(req.headers['x-request-data'])
|
|
160
|
+
const response = JSON.parse(req.headers['x-response-data'])
|
|
161
|
+
response.body = req.body
|
|
162
|
+
|
|
163
|
+
opts.saveRequest?.({ taxonomyId, applicationId, request, response })
|
|
164
|
+
})
|
|
165
|
+
}, { prefix: '/trafficante' })
|
|
166
|
+
|
|
167
|
+
// Risk Service
|
|
168
|
+
await icc.register(async (icc) => {
|
|
169
|
+
icc.addContentTypeParser(
|
|
170
|
+
'application/x-protobuf',
|
|
171
|
+
(req, payload, done) => {
|
|
172
|
+
payload.resume()
|
|
173
|
+
payload.on('end', done)
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
icc.post('/v1/traces', async () => {})
|
|
177
|
+
}, { prefix: '/risk-service' })
|
|
178
|
+
|
|
179
|
+
// Scaler
|
|
180
|
+
await icc.register(async (icc) => {
|
|
181
|
+
icc.addContentTypeParser(
|
|
182
|
+
'application/octet-stream',
|
|
183
|
+
function (request, payload, done) {
|
|
184
|
+
const chunks = []
|
|
185
|
+
payload.on('data', chunk => chunks.push(chunk))
|
|
186
|
+
payload.on('end', () => {
|
|
187
|
+
done(null, Buffer.concat(chunks))
|
|
188
|
+
})
|
|
189
|
+
payload.on('error', done)
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
icc.post('/alerts', async (req) => {
|
|
193
|
+
return opts.processAlerts?.(req)
|
|
194
|
+
})
|
|
195
|
+
icc.post('/pods/:podId/services/:serviceId/flamegraph', async (req) => {
|
|
196
|
+
return opts.processFlamegraphs?.(req)
|
|
197
|
+
})
|
|
198
|
+
}, { prefix: '/scaler' })
|
|
199
|
+
|
|
200
|
+
// Cron
|
|
201
|
+
await icc.register(async (icc) => {
|
|
202
|
+
icc.put('/watt-jobs', async (req) => {
|
|
203
|
+
const body = req.body
|
|
204
|
+
opts.saveWattJob?.(body)
|
|
205
|
+
})
|
|
206
|
+
}, { prefix: '/cron' })
|
|
207
|
+
|
|
208
|
+
await icc.listen({ port: 3000 })
|
|
209
|
+
return icc
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function installDeps (t, appDir, packageNames, testDependencies = []) {
|
|
213
|
+
const appDepsDir = join(appDir, 'node_modules')
|
|
214
|
+
await rm(appDepsDir, { force: true, recursive: true }).catch(() => {})
|
|
215
|
+
await mkdir(appDepsDir, { recursive: true })
|
|
216
|
+
t.after(() => rm(appDepsDir, { force: true, recursive: true }))
|
|
217
|
+
|
|
218
|
+
const toInstall = Array.from(new Set([
|
|
219
|
+
...packageNames ?? [],
|
|
220
|
+
'@platformatic/runtime',
|
|
221
|
+
'@platformatic/config',
|
|
222
|
+
'@platformatic/service',
|
|
223
|
+
'@platformatic/node',
|
|
224
|
+
'undici'
|
|
225
|
+
]))
|
|
226
|
+
|
|
227
|
+
for (const packageName of toInstall) {
|
|
228
|
+
let targetDir = null
|
|
229
|
+
let packageDir = null
|
|
230
|
+
|
|
231
|
+
if (packageName.includes('/')) {
|
|
232
|
+
const [scope, name] = packageName.split('/')
|
|
233
|
+
await mkdir(join(appDepsDir, scope), { recursive: true })
|
|
234
|
+
|
|
235
|
+
targetDir = join(__dirname, '..', 'node_modules', scope, name)
|
|
236
|
+
packageDir = join(appDepsDir, scope, name)
|
|
237
|
+
} else {
|
|
238
|
+
packageDir = join(appDepsDir, packageName)
|
|
239
|
+
targetDir = join(__dirname, '..', 'node_modules', packageName)
|
|
240
|
+
}
|
|
241
|
+
await symlink(targetDir, packageDir)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const testDependency of testDependencies) {
|
|
245
|
+
const { name, version } = testDependency
|
|
246
|
+
const packageDir = join(appDepsDir, name)
|
|
247
|
+
await mkdir(packageDir, { recursive: true })
|
|
248
|
+
|
|
249
|
+
const packageJsonPath = join(packageDir, 'package.json')
|
|
250
|
+
const packageJsonFile = JSON.stringify({ name, version })
|
|
251
|
+
await writeFile(packageJsonPath, packageJsonFile)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function createJwtToken (expiresInSeconds) {
|
|
256
|
+
const header = { alg: 'HS256', typ: 'JWT' }
|
|
257
|
+
const currentTime = Math.floor(Date.now() / 1000)
|
|
258
|
+
const payload = {
|
|
259
|
+
sub: '1234567890',
|
|
260
|
+
iat: currentTime,
|
|
261
|
+
exp: currentTime + (expiresInSeconds || 3600) // Default 1 hour expiration
|
|
262
|
+
}
|
|
263
|
+
const base64Header = Buffer.from(JSON.stringify(header)).toString('base64').replace(/=/g, '')
|
|
264
|
+
const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64').replace(/=/g, '')
|
|
265
|
+
const signature = 'test_signature'
|
|
266
|
+
return `${base64Header}.${base64Payload}.${signature}`
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export {
|
|
270
|
+
setUpEnvironment,
|
|
271
|
+
startICC,
|
|
272
|
+
installDeps,
|
|
273
|
+
createJwtToken
|
|
274
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { equal } from 'node:assert'
|
|
3
|
+
import { hostname } from 'node:os'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
import { startICC } from './helper.js'
|
|
6
|
+
import initPlugin from '../plugins/init.js'
|
|
7
|
+
|
|
8
|
+
const createMockApp = (env = {}) => {
|
|
9
|
+
const logMessages = []
|
|
10
|
+
return {
|
|
11
|
+
env: {
|
|
12
|
+
PLT_CONTROL_PLANE_URL: 'http://127.0.0.1:3000/control-plane',
|
|
13
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000',
|
|
14
|
+
...env
|
|
15
|
+
},
|
|
16
|
+
log: {
|
|
17
|
+
info: (msg) => {
|
|
18
|
+
logMessages.push(msg)
|
|
19
|
+
},
|
|
20
|
+
warn: () => {},
|
|
21
|
+
error: () => {}
|
|
22
|
+
},
|
|
23
|
+
getAuthorizationHeader: async () => 'Bearer test-token',
|
|
24
|
+
logMessages
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('init plugin with explicit PLT_APP_NAME', async (t) => {
|
|
29
|
+
const applicationName = 'test-app-explicit'
|
|
30
|
+
const applicationId = randomUUID()
|
|
31
|
+
const instanceId = hostname()
|
|
32
|
+
|
|
33
|
+
const icc = await startICC(t, {
|
|
34
|
+
applicationId,
|
|
35
|
+
applicationName
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
t.after(async () => {
|
|
39
|
+
await icc.close()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const app = createMockApp({
|
|
43
|
+
PLT_APP_NAME: applicationName,
|
|
44
|
+
PLT_APP_DIR: '/test/dir'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await initPlugin(app)
|
|
48
|
+
|
|
49
|
+
equal(app.applicationName, applicationName)
|
|
50
|
+
equal(app.instanceId, instanceId)
|
|
51
|
+
equal(app.instanceConfig.applicationId, applicationId)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('init plugin with optional PLT_APP_NAME - uses ICC response', async (t) => {
|
|
55
|
+
const applicationName = 'test-app-from-icc'
|
|
56
|
+
const applicationId = randomUUID()
|
|
57
|
+
const instanceId = hostname()
|
|
58
|
+
|
|
59
|
+
const icc = await startICC(t, {
|
|
60
|
+
applicationId,
|
|
61
|
+
controlPlaneResponse: {
|
|
62
|
+
applicationId,
|
|
63
|
+
applicationName, // ICC returns the application name
|
|
64
|
+
iccServices: {
|
|
65
|
+
riskEngine: { url: 'http://127.0.0.1:3000/risk-service' },
|
|
66
|
+
trafficante: { url: 'http://127.0.0.1:3000/trafficante' },
|
|
67
|
+
compliance: { url: 'http://127.0.0.1:3000/compliance' },
|
|
68
|
+
cron: { url: 'http://127.0.0.1:3000/cron' },
|
|
69
|
+
scaler: { url: 'http://127.0.0.1:3000/scaler' }
|
|
70
|
+
},
|
|
71
|
+
config: {},
|
|
72
|
+
enableOpenTelemetry: false,
|
|
73
|
+
enableSlicerInterceptor: false,
|
|
74
|
+
enableTrafficInterceptor: false
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
t.after(async () => {
|
|
79
|
+
await icc.close()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const app = createMockApp({
|
|
83
|
+
// PLT_APP_NAME is not set
|
|
84
|
+
PLT_APP_DIR: '/test/dir'
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
await initPlugin(app)
|
|
88
|
+
|
|
89
|
+
equal(app.applicationName, applicationName)
|
|
90
|
+
equal(app.instanceId, instanceId)
|
|
91
|
+
equal(app.instanceConfig.applicationId, applicationId)
|
|
92
|
+
|
|
93
|
+
// Check that logging shows the resolved application name
|
|
94
|
+
const resolvedNameLog = app.logMessages.find(msg =>
|
|
95
|
+
typeof msg === 'object' && msg.applicationName === applicationName
|
|
96
|
+
)
|
|
97
|
+
equal(!!resolvedNameLog, true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('init plugin in standalone mode - no ICC URL', async (t) => {
|
|
101
|
+
const applicationName = 'test-app-standalone'
|
|
102
|
+
const instanceId = hostname()
|
|
103
|
+
|
|
104
|
+
const app = createMockApp({
|
|
105
|
+
PLT_APP_NAME: applicationName,
|
|
106
|
+
PLT_APP_DIR: '/test/dir',
|
|
107
|
+
PLT_ICC_URL: undefined // No ICC URL set
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await initPlugin(app)
|
|
111
|
+
|
|
112
|
+
equal(app.applicationName, applicationName)
|
|
113
|
+
equal(app.instanceId, instanceId)
|
|
114
|
+
equal(app.instanceConfig, null)
|
|
115
|
+
|
|
116
|
+
// Check that logging shows ICC initialization was skipped
|
|
117
|
+
const skipLog = app.logMessages.find(msg =>
|
|
118
|
+
typeof msg === 'string' && msg.includes('skipping ICC initialization')
|
|
119
|
+
)
|
|
120
|
+
equal(!!skipLog, true)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('init plugin handles ICC connection failure gracefully', async (t) => {
|
|
124
|
+
const applicationName = 'test-app-failure'
|
|
125
|
+
const instanceId = hostname()
|
|
126
|
+
|
|
127
|
+
const app = createMockApp({
|
|
128
|
+
PLT_APP_NAME: applicationName,
|
|
129
|
+
PLT_APP_DIR: '/test/dir',
|
|
130
|
+
PLT_ICC_URL: 'http://127.0.0.1:9999' // Non-existent ICC
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Override error logging to capture errors
|
|
134
|
+
const errorMessages = []
|
|
135
|
+
app.log.error = (err, msg) => {
|
|
136
|
+
errorMessages.push({ err, msg })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await initPlugin(app)
|
|
140
|
+
|
|
141
|
+
// Should still set applicationName and instanceId
|
|
142
|
+
equal(app.applicationName, applicationName)
|
|
143
|
+
equal(app.instanceId, instanceId)
|
|
144
|
+
|
|
145
|
+
// Should have logged an error
|
|
146
|
+
equal(errorMessages.length, 1)
|
|
147
|
+
equal(errorMessages[0].msg, 'Failed to get application information')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('init plugin sends correct request structure when PLT_APP_NAME provided', async (t) => {
|
|
151
|
+
const applicationName = 'test-app-request'
|
|
152
|
+
const applicationId = randomUUID()
|
|
153
|
+
const instanceId = hostname()
|
|
154
|
+
|
|
155
|
+
let capturedRequest = null
|
|
156
|
+
|
|
157
|
+
const icc = await startICC(t, {
|
|
158
|
+
applicationId,
|
|
159
|
+
controlPlaneResponse: (req) => {
|
|
160
|
+
capturedRequest = {
|
|
161
|
+
params: req.params,
|
|
162
|
+
body: req.body
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
applicationId,
|
|
166
|
+
applicationName,
|
|
167
|
+
iccServices: {
|
|
168
|
+
riskEngine: { url: 'http://127.0.0.1:3000/risk-service' },
|
|
169
|
+
trafficante: { url: 'http://127.0.0.1:3000/trafficante' },
|
|
170
|
+
compliance: { url: 'http://127.0.0.1:3000/compliance' },
|
|
171
|
+
cron: { url: 'http://127.0.0.1:3000/cron' },
|
|
172
|
+
scaler: { url: 'http://127.0.0.1:3000/scaler' }
|
|
173
|
+
},
|
|
174
|
+
config: {}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
t.after(async () => {
|
|
180
|
+
await icc.close()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const app = createMockApp({
|
|
184
|
+
PLT_APP_NAME: applicationName,
|
|
185
|
+
PLT_APP_DIR: '/test/dir'
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
await initPlugin(app)
|
|
189
|
+
|
|
190
|
+
// Verify request structure
|
|
191
|
+
equal(capturedRequest.params.podId, instanceId)
|
|
192
|
+
equal(capturedRequest.body.applicationName, applicationName)
|
|
193
|
+
equal(capturedRequest.body.podId, instanceId)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('init plugin sends request without applicationName when not provided', async (t) => {
|
|
197
|
+
const applicationName = 'test-app-no-name'
|
|
198
|
+
const applicationId = randomUUID()
|
|
199
|
+
const instanceId = hostname()
|
|
200
|
+
|
|
201
|
+
let capturedRequest = null
|
|
202
|
+
|
|
203
|
+
const icc = await startICC(t, {
|
|
204
|
+
applicationId,
|
|
205
|
+
controlPlaneResponse: (req) => {
|
|
206
|
+
capturedRequest = {
|
|
207
|
+
params: req.params,
|
|
208
|
+
body: req.body
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
applicationId,
|
|
212
|
+
applicationName, // ICC provides the name
|
|
213
|
+
iccServices: {
|
|
214
|
+
riskEngine: { url: 'http://127.0.0.1:3000/risk-service' },
|
|
215
|
+
trafficante: { url: 'http://127.0.0.1:3000/trafficante' },
|
|
216
|
+
compliance: { url: 'http://127.0.0.1:3000/compliance' },
|
|
217
|
+
cron: { url: 'http://127.0.0.1:3000/cron' },
|
|
218
|
+
scaler: { url: 'http://127.0.0.1:3000/scaler' }
|
|
219
|
+
},
|
|
220
|
+
config: {}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
t.after(async () => {
|
|
226
|
+
await icc.close()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const app = createMockApp({
|
|
230
|
+
// PLT_APP_NAME is not provided
|
|
231
|
+
PLT_APP_DIR: '/test/dir'
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
await initPlugin(app)
|
|
235
|
+
|
|
236
|
+
// Verify request structure - should not contain applicationName
|
|
237
|
+
equal(capturedRequest.params.podId, instanceId)
|
|
238
|
+
equal(capturedRequest.body.podId, instanceId)
|
|
239
|
+
equal(capturedRequest.body.applicationName, undefined)
|
|
240
|
+
|
|
241
|
+
// But app should have the name from ICC response
|
|
242
|
+
equal(app.applicationName, applicationName)
|
|
243
|
+
})
|