@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,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
|
+
})
|
package/test/cli.test.js
ADDED
|
@@ -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
|
+
})
|