@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.
Files changed (95) hide show
  1. package/README.md +87 -0
  2. package/app.js +124 -0
  3. package/cli.js +141 -0
  4. package/clients/compliance/compliance-types.d.ts +887 -0
  5. package/clients/compliance/compliance.mjs +1049 -0
  6. package/clients/compliance/compliance.openapi.json +6127 -0
  7. package/clients/control-plane/control-plane-types.d.ts +2696 -0
  8. package/clients/control-plane/control-plane.mjs +3051 -0
  9. package/clients/control-plane/control-plane.openapi.json +13693 -0
  10. package/clients/cron/cron-types.d.ts +1479 -0
  11. package/clients/cron/cron.mjs +872 -0
  12. package/clients/cron/cron.openapi.json +9330 -0
  13. package/compliance/index.js +21 -0
  14. package/compliance/rules/dependencies.js +76 -0
  15. package/compliance/rules/utils.js +12 -0
  16. package/eslint.config.js +11 -0
  17. package/help/start.txt +12 -0
  18. package/help/watt-extra.txt +12 -0
  19. package/index.js +45 -0
  20. package/lib/banner.js +22 -0
  21. package/lib/errors.js +34 -0
  22. package/lib/utils.js +34 -0
  23. package/lib/wattpro.js +580 -0
  24. package/package.json +50 -0
  25. package/plugins/alerts.js +115 -0
  26. package/plugins/auth.js +89 -0
  27. package/plugins/compliancy.js +70 -0
  28. package/plugins/env.js +58 -0
  29. package/plugins/flamegraphs.js +100 -0
  30. package/plugins/init.js +70 -0
  31. package/plugins/metadata.js +84 -0
  32. package/plugins/scheduler.js +48 -0
  33. package/plugins/update.js +128 -0
  34. package/renovate.json +6 -0
  35. package/test/alerts.test.js +607 -0
  36. package/test/auth.test.js +128 -0
  37. package/test/auto-cache.test.js +401 -0
  38. package/test/cli.test.js +75 -0
  39. package/test/compliancy.test.js +87 -0
  40. package/test/fixtures/runtime-domains/alpha/package.json +5 -0
  41. package/test/fixtures/runtime-domains/alpha/platformatic.json +6 -0
  42. package/test/fixtures/runtime-domains/alpha/plugin.js +16 -0
  43. package/test/fixtures/runtime-domains/beta/package.json +5 -0
  44. package/test/fixtures/runtime-domains/beta/platformatic.json +6 -0
  45. package/test/fixtures/runtime-domains/beta/plugin.js +7 -0
  46. package/test/fixtures/runtime-domains/composer/package.json +5 -0
  47. package/test/fixtures/runtime-domains/composer/platformatic.json +19 -0
  48. package/test/fixtures/runtime-domains/package.json +1 -0
  49. package/test/fixtures/runtime-domains/platformatic.json +27 -0
  50. package/test/fixtures/runtime-health/package.json +20 -0
  51. package/test/fixtures/runtime-health/platformatic.json +16 -0
  52. package/test/fixtures/runtime-health/services/service-1/package.json +17 -0
  53. package/test/fixtures/runtime-health/services/service-1/platformatic.json +16 -0
  54. package/test/fixtures/runtime-health/services/service-1/plugins/example.js +6 -0
  55. package/test/fixtures/runtime-health/services/service-1/routes/root.cjs +8 -0
  56. package/test/fixtures/runtime-health/services/service-2/package.json +17 -0
  57. package/test/fixtures/runtime-health/services/service-2/platformatic.json +16 -0
  58. package/test/fixtures/runtime-health/services/service-2/plugins/example.js +6 -0
  59. package/test/fixtures/runtime-health/services/service-2/routes/root.cjs +8 -0
  60. package/test/fixtures/runtime-next/package.json +5 -0
  61. package/test/fixtures/runtime-next/platformatic.json +9 -0
  62. package/test/fixtures/runtime-next/web/next/next.config.js +2 -0
  63. package/test/fixtures/runtime-next/web/next/package.json +7 -0
  64. package/test/fixtures/runtime-next/web/next/platformatic.json +9 -0
  65. package/test/fixtures/runtime-next/web/next/src/app/direct/route.js +3 -0
  66. package/test/fixtures/runtime-next/web/next/src/app/layout.jsx +7 -0
  67. package/test/fixtures/runtime-next/web/next/src/app/page.jsx +3 -0
  68. package/test/fixtures/runtime-scheduler/main/package.json +5 -0
  69. package/test/fixtures/runtime-scheduler/main/platformatic.json +9 -0
  70. package/test/fixtures/runtime-scheduler/main/routes/root.cjs +11 -0
  71. package/test/fixtures/runtime-scheduler/package.json +1 -0
  72. package/test/fixtures/runtime-scheduler/platformatic.json +27 -0
  73. package/test/fixtures/runtime-service/main/package.json +5 -0
  74. package/test/fixtures/runtime-service/main/platformatic.json +12 -0
  75. package/test/fixtures/runtime-service/main/routes/root.cjs +11 -0
  76. package/test/fixtures/runtime-service/package.json +1 -0
  77. package/test/fixtures/runtime-service/platformatic.json +19 -0
  78. package/test/fixtures/service-1/package.json +7 -0
  79. package/test/fixtures/service-1/platformatic.json +18 -0
  80. package/test/fixtures/service-1/routes/root.cjs +48 -0
  81. package/test/fixtures/service-2/platformatic.json +21 -0
  82. package/test/fixtures/service-2/routes/root.cjs +5 -0
  83. package/test/fixtures/service-3/package.json +5 -0
  84. package/test/fixtures/service-3/platformatic.json +21 -0
  85. package/test/fixtures/service-3/routes/root.cjs +8 -0
  86. package/test/health.test.js +44 -0
  87. package/test/helper.js +274 -0
  88. package/test/init.test.js +243 -0
  89. package/test/patch-config.test.js +434 -0
  90. package/test/scheduler.test.js +71 -0
  91. package/test/send-to-icc-retry.test.js +138 -0
  92. package/test/shared-context.test.js +82 -0
  93. package/test/spawn.test.js +110 -0
  94. package/test/trigger-flamegraphs.test.js +226 -0
  95. package/test/update.test.js +519 -0
@@ -0,0 +1,128 @@
1
+ import { test } from 'node:test'
2
+ import { equal } from 'node:assert'
3
+ import fastify from 'fastify'
4
+ import { request } from 'undici'
5
+ import { setUpEnvironment, createJwtToken } from './helper.js'
6
+ import authPlugin from '../plugins/auth.js'
7
+
8
+ const createMockApp = () => {
9
+ return {
10
+ log: {
11
+ info: () => {},
12
+ warn: () => {},
13
+ error: () => {}
14
+ }
15
+ }
16
+ }
17
+
18
+ test('auth plugin sets authorization header with token', async (t) => {
19
+ const originalEnv = { ...process.env }
20
+
21
+ t.after(() => {
22
+ process.env = originalEnv
23
+ })
24
+
25
+ const testToken = 'test-env-token-5678'
26
+ setUpEnvironment({ PLT_TEST_TOKEN: testToken })
27
+
28
+ const server = fastify()
29
+ server.get('/', async (request) => {
30
+ return { headers: request.headers }
31
+ })
32
+ await server.listen({ port: 0 })
33
+ const url = `http://localhost:${server.server.address().port}`
34
+
35
+ t.after(() => server.close())
36
+
37
+ const app = createMockApp()
38
+ await authPlugin(app)
39
+
40
+ const tokenResponse = await request(url, { dispatcher: app.dispatcher })
41
+ const tokenResponseBody = await tokenResponse.body.json()
42
+
43
+ equal(tokenResponse.statusCode, 200)
44
+ equal(tokenResponseBody.headers.authorization, `Bearer ${testToken}`)
45
+ equal(app.token, testToken)
46
+ })
47
+
48
+ test('auth plugin falls back to env variable when k8s token is not available', async (t) => {
49
+ const originalEnv = { ...process.env }
50
+
51
+ t.after(() => {
52
+ process.env = originalEnv
53
+ })
54
+
55
+ const testToken = 'test-env-token-5678'
56
+ setUpEnvironment({ PLT_TEST_TOKEN: testToken })
57
+
58
+ const server = fastify()
59
+ server.get('/', async (request) => {
60
+ return { headers: request.headers }
61
+ })
62
+ await server.listen({ port: 0 })
63
+ const url = `http://localhost:${server.server.address().port}`
64
+
65
+ t.after(() => server.close())
66
+
67
+ const app = createMockApp()
68
+ await authPlugin(app)
69
+
70
+ equal(app.token, testToken)
71
+
72
+ const tokenResponse = await request(url, { dispatcher: app.dispatcher })
73
+ const tokenResponseBody = await tokenResponse.body.json()
74
+
75
+ equal(tokenResponse.statusCode, 200)
76
+ equal(tokenResponseBody.headers.authorization, `Bearer ${testToken}`)
77
+ })
78
+
79
+ test('auth plugin reloads expired token', async (t) => {
80
+ const originalEnv = { ...process.env }
81
+
82
+ t.after(() => {
83
+ process.env = originalEnv
84
+ })
85
+
86
+ const expiredToken = createJwtToken(-10) // Already expired (by 10 seconds)
87
+ const validToken = createJwtToken(3600) // Valid for 1 hour
88
+
89
+ process.env.PLT_TEST_TOKEN = expiredToken
90
+
91
+ const logMessages = []
92
+ const app = {
93
+ log: {
94
+ info: (msg) => {
95
+ if (typeof msg === 'string') {
96
+ logMessages.push(msg)
97
+ }
98
+ },
99
+ warn: () => {},
100
+ error: () => {}
101
+ }
102
+ }
103
+
104
+ // Set up test server
105
+ const server = fastify()
106
+ server.get('/', async (request) => {
107
+ return { headers: request.headers }
108
+ })
109
+ await server.listen({ port: 0 })
110
+ const url = `http://localhost:${server.server.address().port}`
111
+
112
+ t.after(async () => server.close())
113
+
114
+ await authPlugin(app)
115
+
116
+ // The initial token is the expired one
117
+ equal(app.token, expiredToken, 'Should initially load the expired token')
118
+
119
+ // Now change the environment variable for the next token load
120
+ process.env.PLT_TEST_TOKEN = validToken
121
+
122
+ const response = await request(url, { dispatcher: app.dispatcher })
123
+ const responseBody = await response.body.json()
124
+ equal(app.token, validToken, 'Token should be reloaded with valid token')
125
+ equal(responseBody.headers.authorization, `Bearer ${validToken}`, 'Valid token should be used in authorization header')
126
+ const reloadLogMessage = logMessages.find(msg => msg === 'JWT token expired, reloading')
127
+ equal(!!reloadLogMessage, true, 'Should log message about token reload')
128
+ })
@@ -0,0 +1,401 @@
1
+ import assert from 'node:assert'
2
+ import { test } from 'node:test'
3
+ import { join, dirname } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ import { randomUUID } from 'node:crypto'
7
+ import { setTimeout as sleep } from 'node:timers/promises'
8
+ import { request } from 'undici'
9
+ import {
10
+ startICC,
11
+ installDeps,
12
+ setUpEnvironment
13
+ } from './helper.js'
14
+ import { start } from '../index.js'
15
+
16
+ const __filename = fileURLToPath(import.meta.url)
17
+ const __dirname = dirname(__filename)
18
+
19
+ test('should spawn an app with auto caching', async (t) => {
20
+ const applicationName = 'test-app'
21
+ const applicationId = randomUUID()
22
+ const applicationPath = join(__dirname, 'fixtures', 'runtime-domains')
23
+
24
+ await installDeps(t, applicationPath, ['@platformatic/composer'])
25
+
26
+ const savedRequestHashes = []
27
+ const savedRequests = []
28
+
29
+ const cacheConfig = {
30
+ rules: [{
31
+ routeToMatch: 'http://alpha.plt.local/counter',
32
+ headers: {
33
+ 'cache-control': 'public, max-age=3',
34
+ 'x-foo-bar': 'baz'
35
+ },
36
+ cacheTags: {
37
+ // eslint-disable-next-line
38
+ fgh: `'default', 'custom-cache-tag-' + .querystring["app-id"]`
39
+ }
40
+ }]
41
+ }
42
+
43
+ const iccConfig = {
44
+ httpCacheConfig: cacheConfig
45
+ }
46
+ const icc = await startICC(t, {
47
+ applicationId,
48
+ applicationName,
49
+ iccConfig,
50
+ enableSlicerInterceptor: true,
51
+ enableTrafficInterceptor: true,
52
+ saveRequestHash: (data) => { savedRequestHashes.push(data) },
53
+ saveRequest: (data) => { savedRequests.push(data) }
54
+ })
55
+
56
+ setUpEnvironment({
57
+ PLT_APP_NAME: applicationName,
58
+ PLT_APP_DIR: applicationPath,
59
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
60
+ })
61
+
62
+ const app = await start()
63
+
64
+ t.after(async () => {
65
+ await app.close()
66
+ await icc.close()
67
+ })
68
+
69
+ {
70
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
71
+ query: { 'app-id': '123' }
72
+ })
73
+ assert.strictEqual(statusCode, 200)
74
+
75
+ const { counter } = await body.json()
76
+ assert.strictEqual(counter, 0)
77
+
78
+ assert.strictEqual(headers['cache-control'], 'public, max-age=3')
79
+ assert.strictEqual(headers['x-foo-bar'], 'baz')
80
+ assert.strictEqual(headers['x-custom-cache-tags'], 'default,custom-cache-tag-123')
81
+
82
+ assert.strictEqual(savedRequestHashes.length, 0)
83
+ assert.strictEqual(savedRequests.length, 0)
84
+ }
85
+
86
+ {
87
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
88
+ query: { 'app-id': '123' }
89
+ })
90
+ assert.strictEqual(statusCode, 200)
91
+
92
+ const { counter } = await body.json()
93
+ assert.strictEqual(counter, 0)
94
+
95
+ assert.strictEqual(headers['cache-control'], 'public, max-age=3')
96
+ assert.strictEqual(headers['x-foo-bar'], 'baz')
97
+ assert.strictEqual(headers['x-custom-cache-tags'], 'default,custom-cache-tag-123')
98
+
99
+ assert.strictEqual(savedRequestHashes.length, 0)
100
+ assert.strictEqual(savedRequests.length, 0)
101
+ }
102
+
103
+ // Wait for the cache to expire
104
+ await sleep(3500)
105
+
106
+ {
107
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
108
+ query: { 'app-id': '123' }
109
+ })
110
+ assert.strictEqual(statusCode, 200)
111
+
112
+ const { counter } = await body.json()
113
+ assert.strictEqual(counter, 1)
114
+
115
+ assert.strictEqual(headers['cache-control'], 'public, max-age=3')
116
+ assert.strictEqual(headers['x-foo-bar'], 'baz')
117
+ assert.strictEqual(headers['x-custom-cache-tags'], 'default,custom-cache-tag-123')
118
+
119
+ assert.strictEqual(savedRequestHashes.length, 0)
120
+ assert.strictEqual(savedRequests.length, 0)
121
+ }
122
+
123
+ {
124
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/2/beta')
125
+ assert.strictEqual(statusCode, 200)
126
+
127
+ assert.strictEqual(headers['cache-control'], undefined)
128
+ assert.strictEqual(headers['x-foo-bar'], undefined)
129
+ assert.strictEqual(headers['x-custom-cache-tags'], undefined)
130
+
131
+ // Wait for interceptor to send data to the Trafficante
132
+ await sleep(1000)
133
+
134
+ assert.strictEqual(savedRequestHashes.length, 1)
135
+ assert.strictEqual(savedRequests.length, 1)
136
+
137
+ const savedRequestHash = savedRequestHashes[0]
138
+ assert.strictEqual(savedRequestHash.applicationId, applicationId)
139
+ assert.strictEqual(typeof savedRequestHash.timestamp, 'number')
140
+ assert.strictEqual(savedRequestHash.request.url, 'http://beta.plt.local/beta')
141
+
142
+ const savedRequest = savedRequests[0]
143
+ assert.strictEqual(savedRequest.applicationId, applicationId)
144
+ assert.strictEqual(savedRequest.request.url, 'http://beta.plt.local/beta')
145
+ assert.ok(savedRequest.request.headers)
146
+ assert.ok(savedRequest.response.headers)
147
+ assert.deepStrictEqual(savedRequest.response.body, { from: 'beta' })
148
+ }
149
+ })
150
+
151
+ test('should update the cache config at runtime', async (t) => {
152
+ const applicationName = 'test-app'
153
+ const applicationId = randomUUID()
154
+ const applicationPath = join(__dirname, 'fixtures', 'runtime-domains')
155
+
156
+ await installDeps(t, applicationPath, ['@platformatic/composer'])
157
+
158
+ const savedRequestHashes = []
159
+ const savedRequests = []
160
+
161
+ const initialIccConfig = {
162
+ httpCacheConfig: {
163
+ rules: [{
164
+ routeToMatch: 'http://alpha.plt.local/counter',
165
+ headers: { 'x-foo-bar': 'baz' }
166
+ }]
167
+ }
168
+ }
169
+
170
+ const icc = await startICC(t, {
171
+ applicationId,
172
+ applicationName,
173
+ iccConfig: initialIccConfig,
174
+ enableSlicerInterceptor: true,
175
+ saveRequestHash: (data) => { savedRequestHashes.push(data) },
176
+ saveRequest: (data) => { savedRequests.push(data) }
177
+ })
178
+
179
+ setUpEnvironment({
180
+ PLT_APP_NAME: applicationName,
181
+ PLT_APP_DIR: applicationPath,
182
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
183
+ })
184
+
185
+ const app = await start()
186
+
187
+ t.after(async () => {
188
+ await app.close()
189
+ await icc.close()
190
+ })
191
+
192
+ {
193
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/1/counter')
194
+ assert.strictEqual(statusCode, 200)
195
+ assert.strictEqual(headers['x-foo-bar'], 'baz')
196
+ }
197
+
198
+ const updatedIccConfig = {
199
+ httpCacheConfig: {
200
+ rules: [{
201
+ routeToMatch: 'http://alpha.plt.local/counter',
202
+ headers: { 'x-foo-bar': 'updated-baz' }
203
+ }]
204
+ }
205
+ }
206
+
207
+ await icc.emitApplicationUpdate(applicationId, {
208
+ topic: 'config',
209
+ type: 'config-updated',
210
+ data: updatedIccConfig
211
+ })
212
+
213
+ {
214
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/1/counter')
215
+ assert.strictEqual(statusCode, 200)
216
+ assert.strictEqual(headers['x-foo-bar'], 'updated-baz')
217
+ }
218
+ })
219
+
220
+ test('should set the cache config at runtime', async (t) => {
221
+ const applicationName = 'test-app'
222
+ const applicationId = randomUUID()
223
+ const applicationPath = join(__dirname, 'fixtures', 'runtime-domains')
224
+
225
+ await installDeps(t, applicationPath, ['@platformatic/composer'])
226
+
227
+ const savedRequestHashes = []
228
+ const savedRequests = []
229
+
230
+ const initialIccConfig = {
231
+ httpCacheConfig: null
232
+ }
233
+
234
+ const icc = await startICC(t, {
235
+ applicationId,
236
+ applicationName,
237
+ iccConfig: initialIccConfig,
238
+ enableSlicerInterceptor: true,
239
+ saveRequestHash: (data) => { savedRequestHashes.push(data) },
240
+ saveRequest: (data) => { savedRequests.push(data) }
241
+ })
242
+
243
+ setUpEnvironment({
244
+ PLT_APP_NAME: applicationName,
245
+ PLT_APP_DIR: applicationPath,
246
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
247
+ })
248
+
249
+ const app = await start()
250
+
251
+ t.after(async () => {
252
+ await app.close()
253
+ await icc.close()
254
+ })
255
+
256
+ {
257
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/1/counter')
258
+ assert.strictEqual(statusCode, 200)
259
+ assert.strictEqual(headers['x-foo-bar'], undefined)
260
+ }
261
+
262
+ const updatedIccConfig = {
263
+ httpCacheConfig: {
264
+ rules: [{
265
+ routeToMatch: 'http://alpha.plt.local/counter',
266
+ headers: { 'x-foo-bar': 'updated-baz' }
267
+ }]
268
+ }
269
+ }
270
+
271
+ await icc.emitApplicationUpdate(applicationId, {
272
+ topic: 'config',
273
+ type: 'config-updated',
274
+ data: updatedIccConfig
275
+ })
276
+
277
+ {
278
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/1/counter')
279
+ assert.strictEqual(statusCode, 200)
280
+ assert.strictEqual(headers['x-foo-bar'], 'updated-baz')
281
+ }
282
+ })
283
+
284
+ test('should not set auto cache if it is disabled', async (t) => {
285
+ const applicationName = 'test-app'
286
+ const applicationId = randomUUID()
287
+ const applicationPath = join(__dirname, 'fixtures', 'runtime-domains')
288
+
289
+ await installDeps(t, applicationPath, ['@platformatic/composer'])
290
+
291
+ const savedRequestHashes = []
292
+ const savedRequests = []
293
+
294
+ const cacheConfig = {
295
+ rules: [{
296
+ routeToMatch: 'http://alpha.plt.local/counter',
297
+ headers: {
298
+ 'cache-control': 'public, max-age=3',
299
+ 'x-foo-bar': 'baz'
300
+ },
301
+ cacheTags: {
302
+ // eslint-disable-next-line
303
+ fgh: `'default', 'custom-cache-tag-' + .querystring["app-id"]`
304
+ }
305
+ }]
306
+ }
307
+
308
+ const iccConfig = {
309
+ httpCacheConfig: cacheConfig
310
+ }
311
+ const icc = await startICC(t, {
312
+ applicationId,
313
+ applicationName,
314
+ iccConfig,
315
+ enableSlicerInterceptor: false,
316
+ enableTrafficInterceptor: false,
317
+ saveRequestHash: (data) => { savedRequestHashes.push(data) },
318
+ saveRequest: (data) => { savedRequests.push(data) }
319
+ })
320
+
321
+ setUpEnvironment({
322
+ PLT_APP_NAME: applicationName,
323
+ PLT_APP_DIR: applicationPath,
324
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
325
+ })
326
+
327
+ const app = await start()
328
+
329
+ t.after(async () => {
330
+ await app.close()
331
+ await icc.close()
332
+ })
333
+
334
+ {
335
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
336
+ query: { 'app-id': '123' }
337
+ })
338
+ assert.strictEqual(statusCode, 200)
339
+
340
+ const { counter } = await body.json()
341
+ assert.strictEqual(counter, 0)
342
+
343
+ assert.strictEqual(headers['cache-control'], undefined)
344
+ assert.strictEqual(headers['x-foo-bar'], undefined)
345
+ assert.strictEqual(headers['x-custom-cache-tags'], undefined)
346
+
347
+ assert.strictEqual(savedRequestHashes.length, 0)
348
+ assert.strictEqual(savedRequests.length, 0)
349
+ }
350
+
351
+ {
352
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
353
+ query: { 'app-id': '123' }
354
+ })
355
+ assert.strictEqual(statusCode, 200)
356
+
357
+ const { counter } = await body.json()
358
+ assert.strictEqual(counter, 1)
359
+
360
+ assert.strictEqual(headers['cache-control'], undefined)
361
+ assert.strictEqual(headers['x-foo-bar'], undefined)
362
+ assert.strictEqual(headers['x-custom-cache-tags'], undefined)
363
+
364
+ assert.strictEqual(savedRequestHashes.length, 0)
365
+ assert.strictEqual(savedRequests.length, 0)
366
+ }
367
+
368
+ // Wait for the cache to expire
369
+ await sleep(3500)
370
+
371
+ {
372
+ const { statusCode, headers, body } = await request('http://127.0.0.1:3042/1/counter', {
373
+ query: { 'app-id': '123' }
374
+ })
375
+ assert.strictEqual(statusCode, 200)
376
+
377
+ const { counter } = await body.json()
378
+ assert.strictEqual(counter, 2)
379
+
380
+ assert.strictEqual(headers['cache-control'], undefined)
381
+ assert.strictEqual(headers['x-foo-bar'], undefined)
382
+ assert.strictEqual(headers['x-custom-cache-tags'], undefined)
383
+
384
+ assert.strictEqual(savedRequestHashes.length, 0)
385
+ assert.strictEqual(savedRequests.length, 0)
386
+ }
387
+
388
+ {
389
+ const { statusCode, headers } = await request('http://127.0.0.1:3042/2/beta')
390
+ assert.strictEqual(statusCode, 200)
391
+
392
+ assert.strictEqual(headers['cache-control'], undefined)
393
+ assert.strictEqual(headers['x-foo-bar'], undefined)
394
+ assert.strictEqual(headers['x-custom-cache-tags'], undefined)
395
+
396
+ await sleep(1000)
397
+
398
+ assert.strictEqual(savedRequestHashes.length, 0)
399
+ assert.strictEqual(savedRequests.length, 0)
400
+ }
401
+ })
@@ -0,0 +1,75 @@
1
+ import assert from 'node:assert'
2
+ import { test } from 'node:test'
3
+ import { spawn } from 'node:child_process'
4
+ import { join, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+
10
+ // Helper function to run CLI with arguments and capture output
11
+ function runCLI (args = []) {
12
+ return new Promise((resolve, reject) => {
13
+ const cliPath = join(__dirname, '..', 'cli.js')
14
+ const proc = spawn('node', [cliPath, ...args], {
15
+ stdio: ['ignore', 'pipe', 'pipe']
16
+ })
17
+
18
+ const stdout = []
19
+ const stderr = []
20
+
21
+ proc.stdout.on('data', (data) => {
22
+ stdout.push(data.toString())
23
+ })
24
+
25
+ proc.stderr.on('data', (data) => {
26
+ stderr.push(data.toString())
27
+ })
28
+
29
+ proc.on('error', reject)
30
+
31
+ proc.on('close', (code) => {
32
+ resolve({
33
+ code,
34
+ stdout: stdout.join(''),
35
+ stderr: stderr.join('')
36
+ })
37
+ })
38
+ })
39
+ }
40
+
41
+ test('CLI should exit with error code for non-existent command', async (t) => {
42
+ const { code } = await runCLI(['nonexistentcommand'])
43
+
44
+ // The important part is that a non-existent command exits with error code 1
45
+ assert.strictEqual(code, 1, 'Process should exit with code 1 for non-existent command')
46
+ })
47
+
48
+ test('CLI should show help when no arguments are provided', async (t) => {
49
+ const { code, stdout } = await runCLI([])
50
+
51
+ assert.strictEqual(code, 0, 'Process should exit with code 0')
52
+ assert.ok(
53
+ stdout.includes('WattExtra'),
54
+ 'Help output should include application name'
55
+ )
56
+ })
57
+
58
+ test('CLI should show help with help command', async (t) => {
59
+ const { code, stdout } = await runCLI(['help'])
60
+
61
+ assert.strictEqual(code, 0, 'Process should exit with code 0')
62
+ assert.ok(
63
+ stdout.includes('WattExtra'),
64
+ 'Help output should include application name'
65
+ )
66
+ })
67
+
68
+ test('CLI should show version with version command', async (t) => {
69
+ const { code, stdout } = await runCLI(['version'])
70
+ assert.strictEqual(code, 0, 'Process should exit with code 0')
71
+ assert.ok(
72
+ stdout.includes('WattExtra v'),
73
+ 'Version output should include version number'
74
+ )
75
+ })
@@ -0,0 +1,87 @@
1
+ import assert from 'node:assert'
2
+ import { test } from 'node:test'
3
+ import { join, dirname } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { createRequire } from 'node:module'
6
+ import { randomUUID } from 'node:crypto'
7
+ import { start } from '../index.js'
8
+ import {
9
+ setUpEnvironment,
10
+ startICC,
11
+ installDeps
12
+ } from './helper.js'
13
+
14
+ const __filename = fileURLToPath(import.meta.url)
15
+ const __dirname = dirname(__filename)
16
+
17
+ const require = createRequire(import.meta.url)
18
+ const platformaticVersion = require('@platformatic/runtime/package.json').version
19
+
20
+ test('should retrieve and send compliancy metadata', async (t) => {
21
+ const applicationName = 'test-app'
22
+ const applicationId = randomUUID()
23
+ const applicationPath = join(__dirname, 'fixtures', 'service-1')
24
+
25
+ await installDeps(t, applicationPath, null, [
26
+ { name: '@foo/bar-1', version: '1.2.3' }
27
+ ])
28
+
29
+ const receivedMetadata = []
30
+
31
+ const icc = await startICC(t, {
32
+ applicationId,
33
+ saveComplianceMetadata: async (applicationId, data) => {
34
+ receivedMetadata.push({ applicationId, data })
35
+ },
36
+ getComplianceReport: async () => {
37
+ return { compliant: true }
38
+ }
39
+ })
40
+
41
+ setUpEnvironment({
42
+ PLT_APP_NAME: applicationName,
43
+ PLT_APP_DIR: applicationPath,
44
+ PLT_ICC_URL: 'http://127.0.0.1:3000',
45
+ PLT_DISABLE_COMPLIANCE_CHECK: false
46
+ })
47
+
48
+ const app = await start()
49
+
50
+ t.after(async () => {
51
+ await app.close()
52
+ await icc.close()
53
+ })
54
+
55
+ assert.strictEqual(receivedMetadata.length, 1)
56
+
57
+ const [metadata] = receivedMetadata
58
+ assert.strictEqual(metadata.applicationId, applicationId)
59
+ assert.deepStrictEqual(metadata.data, {
60
+ npmDependencies: {
61
+ runtime: {
62
+ current: {
63
+ '@platformatic/runtime': platformaticVersion,
64
+ '@foo/bar-1': '1.2.3'
65
+ },
66
+ dependencies: {
67
+ '@platformatic/runtime': 'workspace:*',
68
+ '@foo/bar-1': '^1.0.0',
69
+ missing: '^1.33.3'
70
+ }
71
+ },
72
+ services: {
73
+ main: {
74
+ current: {
75
+ '@platformatic/runtime': platformaticVersion,
76
+ '@foo/bar-1': '1.2.3'
77
+ },
78
+ dependencies: {
79
+ '@platformatic/runtime': 'workspace:*',
80
+ '@foo/bar-1': '^1.0.0',
81
+ missing: '^1.33.3'
82
+ }
83
+ }
84
+ }
85
+ }
86
+ })
87
+ })
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@platformatic/service": "workspace:*"
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/2.44.0.json",
3
+ "plugins": {
4
+ "paths": ["./plugin.js"]
5
+ }
6
+ }