@vltpkg/vsr 0.0.0-26
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/DEPLOY.md +163 -0
- package/LICENSE +119 -0
- package/README.md +314 -0
- package/config.ts +221 -0
- package/drizzle.config.js +40 -0
- package/info/COMPARISONS.md +37 -0
- package/info/CONFIGURATION.md +143 -0
- package/info/CONTRIBUTING.md +32 -0
- package/info/DATABASE_SETUP.md +108 -0
- package/info/GRANULAR_ACCESS_TOKENS.md +160 -0
- package/info/PROJECT_STRUCTURE.md +291 -0
- package/info/ROADMAP.md +27 -0
- package/info/SUPPORT.md +39 -0
- package/info/TESTING.md +301 -0
- package/info/USER_SUPPORT.md +31 -0
- package/package.json +77 -0
- package/scripts/build-assets.js +31 -0
- package/scripts/build-bin.js +62 -0
- package/scripts/prepack.js +27 -0
- package/src/assets/public/images/bg.png +0 -0
- package/src/assets/public/images/clients/logo-bun.png +0 -0
- package/src/assets/public/images/clients/logo-deno.png +0 -0
- package/src/assets/public/images/clients/logo-npm.png +0 -0
- package/src/assets/public/images/clients/logo-pnpm.png +0 -0
- package/src/assets/public/images/clients/logo-vlt.png +0 -0
- package/src/assets/public/images/clients/logo-yarn.png +0 -0
- package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
- package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
- package/src/assets/public/images/favicon/favicon.ico +0 -0
- package/src/assets/public/images/favicon/favicon.svg +3 -0
- package/src/assets/public/images/favicon/site.webmanifest +21 -0
- package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
- package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
- package/src/assets/public/styles/styles.css +231 -0
- package/src/bin/demo/package.json +6 -0
- package/src/bin/demo/vlt.json +1 -0
- package/src/bin/vsr.ts +484 -0
- package/src/db/client.ts +590 -0
- package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
- package/src/db/migrations/0000_initial.sql +29 -0
- package/src/db/migrations/0001_uuid_validation.sql +35 -0
- package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
- package/src/db/migrations/drop.sql +3 -0
- package/src/db/migrations/meta/0000_snapshot.json +104 -0
- package/src/db/migrations/meta/0001_snapshot.json +155 -0
- package/src/db/migrations/meta/_journal.json +20 -0
- package/src/db/schema.ts +43 -0
- package/src/index.ts +434 -0
- package/src/middleware/config.ts +79 -0
- package/src/middleware/telemetry.ts +43 -0
- package/src/queue/index.ts +97 -0
- package/src/routes/access.ts +852 -0
- package/src/routes/docs.ts +63 -0
- package/src/routes/misc.ts +469 -0
- package/src/routes/packages.ts +2823 -0
- package/src/routes/ping.ts +39 -0
- package/src/routes/search.ts +131 -0
- package/src/routes/static.ts +74 -0
- package/src/routes/tokens.ts +259 -0
- package/src/routes/users.ts +68 -0
- package/src/utils/auth.ts +202 -0
- package/src/utils/cache.ts +587 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/database.ts +69 -0
- package/src/utils/docs.ts +146 -0
- package/src/utils/packages.ts +453 -0
- package/src/utils/response.ts +125 -0
- package/src/utils/routes.ts +64 -0
- package/src/utils/spa.ts +52 -0
- package/src/utils/tracing.ts +52 -0
- package/src/utils/upstream.ts +172 -0
- package/test/access.test.ts +705 -0
- package/test/audit.test.ts +828 -0
- package/test/dashboard.test.ts +693 -0
- package/test/dist-tags.test.ts +678 -0
- package/test/manifest.test.ts +436 -0
- package/test/packument.test.ts +530 -0
- package/test/ping.test.ts +41 -0
- package/test/search.test.ts +472 -0
- package/test/setup.ts +130 -0
- package/test/static.test.ts +646 -0
- package/test/tokens.test.ts +389 -0
- package/test/utils/auth.test.ts +214 -0
- package/test/utils/packages.test.ts +235 -0
- package/test/utils/response.test.ts +184 -0
- package/test/whoami.test.ts +119 -0
- package/tsconfig.json +16 -0
- package/tsconfig.worker.json +3 -0
- package/typedoc.mjs +2 -0
- package/types.ts +598 -0
- package/vitest.config.ts +25 -0
- package/vlt.json.example +56 -0
- package/wrangler.json +65 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { env } from 'cloudflare:test'
|
|
3
|
+
import { app } from '../src/index.ts'
|
|
4
|
+
|
|
5
|
+
describe('Security Audit Endpoints', () => {
|
|
6
|
+
describe('Root Registry Audit', () => {
|
|
7
|
+
describe('POST /-/npm/audit', () => {
|
|
8
|
+
it('should handle basic audit requests', async () => {
|
|
9
|
+
const res = await app.request(
|
|
10
|
+
'/-/npm/audit',
|
|
11
|
+
{
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
name: 'test-app',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
requires: {
|
|
20
|
+
lodash: '^4.17.21',
|
|
21
|
+
express: '^4.18.0',
|
|
22
|
+
},
|
|
23
|
+
dependencies: {
|
|
24
|
+
lodash: {
|
|
25
|
+
version: '4.17.21',
|
|
26
|
+
resolved:
|
|
27
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
28
|
+
},
|
|
29
|
+
express: {
|
|
30
|
+
version: '4.18.2',
|
|
31
|
+
resolved:
|
|
32
|
+
'https://registry.npmjs.org/express/-/express-4.18.2.tgz',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
env,
|
|
38
|
+
)
|
|
39
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
40
|
+
expect(res.headers.get('content-type')).toContain(
|
|
41
|
+
'application/json',
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should handle audit requests without dependencies', async () => {
|
|
46
|
+
const res = await app.request(
|
|
47
|
+
'/-/npm/audit',
|
|
48
|
+
{
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
name: 'empty-app',
|
|
55
|
+
version: '1.0.0',
|
|
56
|
+
requires: {},
|
|
57
|
+
dependencies: {},
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
env,
|
|
61
|
+
)
|
|
62
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should handle malformed audit requests', async () => {
|
|
66
|
+
const res = await app.request(
|
|
67
|
+
'/-/npm/audit',
|
|
68
|
+
{
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
},
|
|
73
|
+
body: 'invalid-json',
|
|
74
|
+
},
|
|
75
|
+
env,
|
|
76
|
+
)
|
|
77
|
+
expect([400, 501].includes(res.status)).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should handle missing required fields', async () => {
|
|
81
|
+
const res = await app.request(
|
|
82
|
+
'/-/npm/audit',
|
|
83
|
+
{
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({}),
|
|
89
|
+
},
|
|
90
|
+
env,
|
|
91
|
+
)
|
|
92
|
+
expect([400, 501].includes(res.status)).toBe(true)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('Audit Response Structure', () => {
|
|
97
|
+
it('should return proper audit response format', async () => {
|
|
98
|
+
const res = await app.request(
|
|
99
|
+
'/-/npm/audit',
|
|
100
|
+
{
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
'Content-Type': 'application/json',
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
name: 'test-app',
|
|
107
|
+
version: '1.0.0',
|
|
108
|
+
requires: {
|
|
109
|
+
lodash: '^4.17.21',
|
|
110
|
+
},
|
|
111
|
+
dependencies: {
|
|
112
|
+
lodash: {
|
|
113
|
+
version: '4.17.21',
|
|
114
|
+
resolved:
|
|
115
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
env,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if (res.status === 200) {
|
|
124
|
+
const data = (await res.json()) as any
|
|
125
|
+
expect(data).toBeDefined()
|
|
126
|
+
// Audit response should include vulnerabilities, metadata, etc.
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should include vulnerability counts in response', async () => {
|
|
131
|
+
const res = await app.request(
|
|
132
|
+
'/-/npm/audit',
|
|
133
|
+
{
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
name: 'test-app',
|
|
140
|
+
version: '1.0.0',
|
|
141
|
+
requires: {
|
|
142
|
+
'vulnerable-package': '^1.0.0',
|
|
143
|
+
},
|
|
144
|
+
dependencies: {
|
|
145
|
+
'vulnerable-package': {
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
resolved:
|
|
148
|
+
'https://registry.npmjs.org/vulnerable-package/-/vulnerable-package-1.0.0.tgz',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
env,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if (res.status === 200) {
|
|
157
|
+
const data = (await res.json()) as any
|
|
158
|
+
expect(data).toBeDefined()
|
|
159
|
+
// Would validate vulnerability counts based on implementation
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('should include advisory details for vulnerabilities', async () => {
|
|
164
|
+
const res = await app.request(
|
|
165
|
+
'/-/npm/audit',
|
|
166
|
+
{
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
name: 'test-app',
|
|
173
|
+
version: '1.0.0',
|
|
174
|
+
requires: {
|
|
175
|
+
'old-package': '^0.1.0',
|
|
176
|
+
},
|
|
177
|
+
dependencies: {
|
|
178
|
+
'old-package': {
|
|
179
|
+
version: '0.1.0',
|
|
180
|
+
resolved:
|
|
181
|
+
'https://registry.npmjs.org/old-package/-/old-package-0.1.0.tgz',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
},
|
|
186
|
+
env,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if (res.status === 200) {
|
|
190
|
+
const data = (await res.json()) as any
|
|
191
|
+
expect(data).toBeDefined()
|
|
192
|
+
// Would validate advisory structure based on implementation
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe('Upstream Registry Audit', () => {
|
|
199
|
+
describe('NPM Registry Audit', () => {
|
|
200
|
+
it('should handle npm upstream audit requests', async () => {
|
|
201
|
+
const res = await app.request(
|
|
202
|
+
'/npm/-/npm/audit',
|
|
203
|
+
{
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'application/json',
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
name: 'test-app',
|
|
210
|
+
version: '1.0.0',
|
|
211
|
+
requires: {
|
|
212
|
+
lodash: '^4.17.21',
|
|
213
|
+
},
|
|
214
|
+
dependencies: {
|
|
215
|
+
lodash: {
|
|
216
|
+
version: '4.17.21',
|
|
217
|
+
resolved:
|
|
218
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
env,
|
|
224
|
+
)
|
|
225
|
+
expect([200, 400, 501, 502].includes(res.status)).toBe(true)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should handle npm audit with dev dependencies', async () => {
|
|
229
|
+
const res = await app.request(
|
|
230
|
+
'/npm/-/npm/audit',
|
|
231
|
+
{
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: {
|
|
234
|
+
'Content-Type': 'application/json',
|
|
235
|
+
},
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
name: 'test-app',
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
requires: {
|
|
240
|
+
lodash: '^4.17.21',
|
|
241
|
+
},
|
|
242
|
+
dependencies: {
|
|
243
|
+
lodash: {
|
|
244
|
+
version: '4.17.21',
|
|
245
|
+
resolved:
|
|
246
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
247
|
+
dev: false,
|
|
248
|
+
},
|
|
249
|
+
jest: {
|
|
250
|
+
version: '29.0.0',
|
|
251
|
+
resolved:
|
|
252
|
+
'https://registry.npmjs.org/jest/-/jest-29.0.0.tgz',
|
|
253
|
+
dev: true,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
258
|
+
env,
|
|
259
|
+
)
|
|
260
|
+
expect([200, 400, 501, 502].includes(res.status)).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('JSR Registry Audit', () => {
|
|
265
|
+
it('should handle jsr upstream audit requests', async () => {
|
|
266
|
+
const res = await app.request(
|
|
267
|
+
'/jsr/-/npm/audit',
|
|
268
|
+
{
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: {
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
},
|
|
273
|
+
body: JSON.stringify({
|
|
274
|
+
name: 'test-app',
|
|
275
|
+
version: '1.0.0',
|
|
276
|
+
requires: {
|
|
277
|
+
'@std/fs': '^0.1.0',
|
|
278
|
+
},
|
|
279
|
+
dependencies: {
|
|
280
|
+
'@std/fs': {
|
|
281
|
+
version: '0.1.0',
|
|
282
|
+
resolved: 'https://jsr.io/@std/fs/0.1.0',
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
},
|
|
287
|
+
env,
|
|
288
|
+
)
|
|
289
|
+
expect([200, 400, 501, 502].includes(res.status)).toBe(true)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('Custom Registry Audit', () => {
|
|
294
|
+
it('should handle custom upstream audit requests', async () => {
|
|
295
|
+
const res = await app.request(
|
|
296
|
+
'/custom/-/npm/audit',
|
|
297
|
+
{
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: {
|
|
300
|
+
'Content-Type': 'application/json',
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
name: 'test-app',
|
|
304
|
+
version: '1.0.0',
|
|
305
|
+
requires: {
|
|
306
|
+
'custom-package': '^1.0.0',
|
|
307
|
+
},
|
|
308
|
+
dependencies: {
|
|
309
|
+
'custom-package': {
|
|
310
|
+
version: '1.0.0',
|
|
311
|
+
resolved:
|
|
312
|
+
'https://custom-registry.com/custom-package/-/custom-package-1.0.0.tgz',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
}),
|
|
316
|
+
},
|
|
317
|
+
env,
|
|
318
|
+
)
|
|
319
|
+
expect([200, 400, 501, 502].includes(res.status)).toBe(true)
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
describe('Legacy Audit Redirects', () => {
|
|
325
|
+
describe('NPM v1 API Compatibility', () => {
|
|
326
|
+
it('should redirect /-/npm/v1/security/audits/quick to /-/npm/audit', async () => {
|
|
327
|
+
const res = await app.request(
|
|
328
|
+
'/-/npm/v1/security/audits/quick',
|
|
329
|
+
{
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: {
|
|
332
|
+
'Content-Type': 'application/json',
|
|
333
|
+
},
|
|
334
|
+
body: JSON.stringify({
|
|
335
|
+
name: 'test-app',
|
|
336
|
+
version: '1.0.0',
|
|
337
|
+
}),
|
|
338
|
+
},
|
|
339
|
+
env,
|
|
340
|
+
)
|
|
341
|
+
expect(res.status).toBe(308)
|
|
342
|
+
expect(res.headers.get('location')).toBe('/-/npm/audit')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('should handle /-/npm/v1/security/advisories/bulk requests', async () => {
|
|
346
|
+
const res = await app.request(
|
|
347
|
+
'/-/npm/v1/security/advisories/bulk',
|
|
348
|
+
{
|
|
349
|
+
method: 'POST',
|
|
350
|
+
headers: {
|
|
351
|
+
'Content-Type': 'application/json',
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify({
|
|
354
|
+
name: 'test-app',
|
|
355
|
+
version: '1.0.0',
|
|
356
|
+
}),
|
|
357
|
+
},
|
|
358
|
+
env,
|
|
359
|
+
)
|
|
360
|
+
// Audit functionality is not implemented yet, expect error status codes
|
|
361
|
+
expect([400, 404, 500, 501].includes(res.status)).toBe(true)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe('Audit Request Validation', () => {
|
|
367
|
+
describe('Package Lock Format', () => {
|
|
368
|
+
it('should handle package-lock.json v1 format', async () => {
|
|
369
|
+
const res = await app.request(
|
|
370
|
+
'/-/npm/audit',
|
|
371
|
+
{
|
|
372
|
+
method: 'POST',
|
|
373
|
+
headers: {
|
|
374
|
+
'Content-Type': 'application/json',
|
|
375
|
+
},
|
|
376
|
+
body: JSON.stringify({
|
|
377
|
+
name: 'test-app',
|
|
378
|
+
version: '1.0.0',
|
|
379
|
+
lockfileVersion: 1,
|
|
380
|
+
requires: true,
|
|
381
|
+
dependencies: {
|
|
382
|
+
lodash: {
|
|
383
|
+
version: '4.17.21',
|
|
384
|
+
resolved:
|
|
385
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
386
|
+
integrity:
|
|
387
|
+
'sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
}),
|
|
391
|
+
},
|
|
392
|
+
env,
|
|
393
|
+
)
|
|
394
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should handle package-lock.json v2 format', async () => {
|
|
398
|
+
const res = await app.request(
|
|
399
|
+
'/-/npm/audit',
|
|
400
|
+
{
|
|
401
|
+
method: 'POST',
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-Type': 'application/json',
|
|
404
|
+
},
|
|
405
|
+
body: JSON.stringify({
|
|
406
|
+
name: 'test-app',
|
|
407
|
+
version: '1.0.0',
|
|
408
|
+
lockfileVersion: 2,
|
|
409
|
+
requires: true,
|
|
410
|
+
packages: {
|
|
411
|
+
'': {
|
|
412
|
+
name: 'test-app',
|
|
413
|
+
version: '1.0.0',
|
|
414
|
+
dependencies: {
|
|
415
|
+
lodash: '^4.17.21',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
'node_modules/lodash': {
|
|
419
|
+
version: '4.17.21',
|
|
420
|
+
resolved:
|
|
421
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
422
|
+
integrity:
|
|
423
|
+
'sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==',
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
dependencies: {
|
|
427
|
+
lodash: {
|
|
428
|
+
version: '4.17.21',
|
|
429
|
+
resolved:
|
|
430
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
431
|
+
integrity:
|
|
432
|
+
'sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==',
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
},
|
|
437
|
+
env,
|
|
438
|
+
)
|
|
439
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('should handle package-lock.json v3 format', async () => {
|
|
443
|
+
const res = await app.request(
|
|
444
|
+
'/-/npm/audit',
|
|
445
|
+
{
|
|
446
|
+
method: 'POST',
|
|
447
|
+
headers: {
|
|
448
|
+
'Content-Type': 'application/json',
|
|
449
|
+
},
|
|
450
|
+
body: JSON.stringify({
|
|
451
|
+
name: 'test-app',
|
|
452
|
+
version: '1.0.0',
|
|
453
|
+
lockfileVersion: 3,
|
|
454
|
+
requires: true,
|
|
455
|
+
packages: {
|
|
456
|
+
'': {
|
|
457
|
+
name: 'test-app',
|
|
458
|
+
version: '1.0.0',
|
|
459
|
+
dependencies: {
|
|
460
|
+
lodash: '^4.17.21',
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
'node_modules/lodash': {
|
|
464
|
+
version: '4.17.21',
|
|
465
|
+
resolved:
|
|
466
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
467
|
+
integrity:
|
|
468
|
+
'sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==',
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
}),
|
|
472
|
+
},
|
|
473
|
+
env,
|
|
474
|
+
)
|
|
475
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
476
|
+
})
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
describe('Dependency Tree Validation', () => {
|
|
480
|
+
it('should handle nested dependencies', async () => {
|
|
481
|
+
const res = await app.request(
|
|
482
|
+
'/-/npm/audit',
|
|
483
|
+
{
|
|
484
|
+
method: 'POST',
|
|
485
|
+
headers: {
|
|
486
|
+
'Content-Type': 'application/json',
|
|
487
|
+
},
|
|
488
|
+
body: JSON.stringify({
|
|
489
|
+
name: 'test-app',
|
|
490
|
+
version: '1.0.0',
|
|
491
|
+
requires: {
|
|
492
|
+
express: '^4.18.0',
|
|
493
|
+
},
|
|
494
|
+
dependencies: {
|
|
495
|
+
express: {
|
|
496
|
+
version: '4.18.2',
|
|
497
|
+
resolved:
|
|
498
|
+
'https://registry.npmjs.org/express/-/express-4.18.2.tgz',
|
|
499
|
+
requires: {
|
|
500
|
+
'body-parser': '^1.20.1',
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
'body-parser': {
|
|
504
|
+
version: '1.20.1',
|
|
505
|
+
resolved:
|
|
506
|
+
'https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz',
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
}),
|
|
510
|
+
},
|
|
511
|
+
env,
|
|
512
|
+
)
|
|
513
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('should handle peer dependencies', async () => {
|
|
517
|
+
const res = await app.request(
|
|
518
|
+
'/-/npm/audit',
|
|
519
|
+
{
|
|
520
|
+
method: 'POST',
|
|
521
|
+
headers: {
|
|
522
|
+
'Content-Type': 'application/json',
|
|
523
|
+
},
|
|
524
|
+
body: JSON.stringify({
|
|
525
|
+
name: 'test-app',
|
|
526
|
+
version: '1.0.0',
|
|
527
|
+
requires: {
|
|
528
|
+
react: '^18.0.0',
|
|
529
|
+
'react-dom': '^18.0.0',
|
|
530
|
+
},
|
|
531
|
+
dependencies: {
|
|
532
|
+
react: {
|
|
533
|
+
version: '18.2.0',
|
|
534
|
+
resolved:
|
|
535
|
+
'https://registry.npmjs.org/react/-/react-18.2.0.tgz',
|
|
536
|
+
},
|
|
537
|
+
'react-dom': {
|
|
538
|
+
version: '18.2.0',
|
|
539
|
+
resolved:
|
|
540
|
+
'https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz',
|
|
541
|
+
requires: {
|
|
542
|
+
react: '^18.2.0',
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
}),
|
|
547
|
+
},
|
|
548
|
+
env,
|
|
549
|
+
)
|
|
550
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should handle optional dependencies', async () => {
|
|
554
|
+
const res = await app.request(
|
|
555
|
+
'/-/npm/audit',
|
|
556
|
+
{
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: {
|
|
559
|
+
'Content-Type': 'application/json',
|
|
560
|
+
},
|
|
561
|
+
body: JSON.stringify({
|
|
562
|
+
name: 'test-app',
|
|
563
|
+
version: '1.0.0',
|
|
564
|
+
requires: {
|
|
565
|
+
'optional-package': '^1.0.0',
|
|
566
|
+
},
|
|
567
|
+
dependencies: {
|
|
568
|
+
'optional-package': {
|
|
569
|
+
version: '1.0.0',
|
|
570
|
+
resolved:
|
|
571
|
+
'https://registry.npmjs.org/optional-package/-/optional-package-1.0.0.tgz',
|
|
572
|
+
optional: true,
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
}),
|
|
576
|
+
},
|
|
577
|
+
env,
|
|
578
|
+
)
|
|
579
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
describe('Error Handling', () => {
|
|
585
|
+
describe('Request Validation Errors', () => {
|
|
586
|
+
it('should handle missing content-type header', async () => {
|
|
587
|
+
const res = await app.request(
|
|
588
|
+
'/-/npm/audit',
|
|
589
|
+
{
|
|
590
|
+
method: 'POST',
|
|
591
|
+
body: JSON.stringify({
|
|
592
|
+
name: 'test-app',
|
|
593
|
+
version: '1.0.0',
|
|
594
|
+
}),
|
|
595
|
+
},
|
|
596
|
+
env,
|
|
597
|
+
)
|
|
598
|
+
expect([400, 415, 501].includes(res.status)).toBe(true)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('should handle oversized audit requests', async () => {
|
|
602
|
+
const largeDependencies = {}
|
|
603
|
+
for (let i = 0; i < 1000; i++) {
|
|
604
|
+
largeDependencies[`package-${i}`] = {
|
|
605
|
+
version: '1.0.0',
|
|
606
|
+
resolved: `https://registry.npmjs.org/package-${i}/-/package-${i}-1.0.0.tgz`,
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const res = await app.request(
|
|
611
|
+
'/-/npm/audit',
|
|
612
|
+
{
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: {
|
|
615
|
+
'Content-Type': 'application/json',
|
|
616
|
+
},
|
|
617
|
+
body: JSON.stringify({
|
|
618
|
+
name: 'large-app',
|
|
619
|
+
version: '1.0.0',
|
|
620
|
+
requires: {},
|
|
621
|
+
dependencies: largeDependencies,
|
|
622
|
+
}),
|
|
623
|
+
},
|
|
624
|
+
env,
|
|
625
|
+
)
|
|
626
|
+
expect([200, 400, 413, 501].includes(res.status)).toBe(true)
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('should handle invalid lockfile versions', async () => {
|
|
630
|
+
const res = await app.request(
|
|
631
|
+
'/-/npm/audit',
|
|
632
|
+
{
|
|
633
|
+
method: 'POST',
|
|
634
|
+
headers: {
|
|
635
|
+
'Content-Type': 'application/json',
|
|
636
|
+
},
|
|
637
|
+
body: JSON.stringify({
|
|
638
|
+
name: 'test-app',
|
|
639
|
+
version: '1.0.0',
|
|
640
|
+
lockfileVersion: 999,
|
|
641
|
+
requires: {},
|
|
642
|
+
dependencies: {},
|
|
643
|
+
}),
|
|
644
|
+
},
|
|
645
|
+
env,
|
|
646
|
+
)
|
|
647
|
+
expect([400, 501].includes(res.status)).toBe(true)
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
describe('Upstream Registry Errors', () => {
|
|
652
|
+
it('should handle upstream registry connection errors', async () => {
|
|
653
|
+
const res = await app.request(
|
|
654
|
+
'/nonexistent/-/npm/audit',
|
|
655
|
+
{
|
|
656
|
+
method: 'POST',
|
|
657
|
+
headers: {
|
|
658
|
+
'Content-Type': 'application/json',
|
|
659
|
+
},
|
|
660
|
+
body: JSON.stringify({
|
|
661
|
+
name: 'test-app',
|
|
662
|
+
version: '1.0.0',
|
|
663
|
+
requires: {},
|
|
664
|
+
dependencies: {},
|
|
665
|
+
}),
|
|
666
|
+
},
|
|
667
|
+
env,
|
|
668
|
+
)
|
|
669
|
+
expect([404, 501, 502].includes(res.status)).toBe(true)
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
it('should handle upstream registry timeout errors', async () => {
|
|
673
|
+
const res = await app.request(
|
|
674
|
+
'/npm/-/npm/audit',
|
|
675
|
+
{
|
|
676
|
+
method: 'POST',
|
|
677
|
+
headers: {
|
|
678
|
+
'Content-Type': 'application/json',
|
|
679
|
+
},
|
|
680
|
+
body: JSON.stringify({
|
|
681
|
+
name: 'test-app',
|
|
682
|
+
version: '1.0.0',
|
|
683
|
+
requires: {
|
|
684
|
+
lodash: '^4.17.21',
|
|
685
|
+
},
|
|
686
|
+
dependencies: {
|
|
687
|
+
lodash: {
|
|
688
|
+
version: '4.17.21',
|
|
689
|
+
resolved:
|
|
690
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
},
|
|
695
|
+
env,
|
|
696
|
+
)
|
|
697
|
+
// May return 502 due to upstream connection issues in test environment
|
|
698
|
+
expect([200, 501, 502].includes(res.status)).toBe(true)
|
|
699
|
+
})
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
describe('Response Headers and Performance', () => {
|
|
704
|
+
describe('Content-Type Headers', () => {
|
|
705
|
+
it('should set appropriate content-type headers', async () => {
|
|
706
|
+
const res = await app.request(
|
|
707
|
+
'/-/npm/audit',
|
|
708
|
+
{
|
|
709
|
+
method: 'POST',
|
|
710
|
+
headers: {
|
|
711
|
+
'Content-Type': 'application/json',
|
|
712
|
+
},
|
|
713
|
+
body: JSON.stringify({
|
|
714
|
+
name: 'test-app',
|
|
715
|
+
version: '1.0.0',
|
|
716
|
+
requires: {},
|
|
717
|
+
dependencies: {},
|
|
718
|
+
}),
|
|
719
|
+
},
|
|
720
|
+
env,
|
|
721
|
+
)
|
|
722
|
+
if (res.status === 200) {
|
|
723
|
+
expect(res.headers.get('content-type')).toContain(
|
|
724
|
+
'application/json',
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
})
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
describe('Response Time', () => {
|
|
731
|
+
it('should respond within reasonable time limits', async () => {
|
|
732
|
+
const startTime = Date.now()
|
|
733
|
+
const res = await app.request(
|
|
734
|
+
'/-/npm/audit',
|
|
735
|
+
{
|
|
736
|
+
method: 'POST',
|
|
737
|
+
headers: {
|
|
738
|
+
'Content-Type': 'application/json',
|
|
739
|
+
},
|
|
740
|
+
body: JSON.stringify({
|
|
741
|
+
name: 'test-app',
|
|
742
|
+
version: '1.0.0',
|
|
743
|
+
requires: {
|
|
744
|
+
lodash: '^4.17.21',
|
|
745
|
+
},
|
|
746
|
+
dependencies: {
|
|
747
|
+
lodash: {
|
|
748
|
+
version: '4.17.21',
|
|
749
|
+
resolved:
|
|
750
|
+
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
}),
|
|
754
|
+
},
|
|
755
|
+
env,
|
|
756
|
+
)
|
|
757
|
+
const endTime = Date.now()
|
|
758
|
+
|
|
759
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
760
|
+
expect(endTime - startTime).toBeLessThan(10000) // 10 second timeout
|
|
761
|
+
})
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
describe('Security Headers', () => {
|
|
765
|
+
it('should include security headers in audit responses', async () => {
|
|
766
|
+
const res = await app.request(
|
|
767
|
+
'/-/npm/audit',
|
|
768
|
+
{
|
|
769
|
+
method: 'POST',
|
|
770
|
+
headers: {
|
|
771
|
+
'Content-Type': 'application/json',
|
|
772
|
+
},
|
|
773
|
+
body: JSON.stringify({
|
|
774
|
+
name: 'test-app',
|
|
775
|
+
version: '1.0.0',
|
|
776
|
+
requires: {},
|
|
777
|
+
dependencies: {},
|
|
778
|
+
}),
|
|
779
|
+
},
|
|
780
|
+
env,
|
|
781
|
+
)
|
|
782
|
+
// Security headers would be validated based on implementation
|
|
783
|
+
expect([200, 400, 501].includes(res.status)).toBe(true)
|
|
784
|
+
})
|
|
785
|
+
})
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
describe('Audit Implementation Status', () => {
|
|
789
|
+
describe('Feature Availability', () => {
|
|
790
|
+
it('should indicate audit feature status', async () => {
|
|
791
|
+
const res = await app.request(
|
|
792
|
+
'/-/npm/audit',
|
|
793
|
+
{
|
|
794
|
+
method: 'POST',
|
|
795
|
+
headers: {
|
|
796
|
+
'Content-Type': 'application/json',
|
|
797
|
+
},
|
|
798
|
+
body: JSON.stringify({
|
|
799
|
+
name: 'test-app',
|
|
800
|
+
version: '1.0.0',
|
|
801
|
+
requires: {},
|
|
802
|
+
dependencies: {},
|
|
803
|
+
}),
|
|
804
|
+
},
|
|
805
|
+
env,
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
// Audit may not be fully implemented, should return 501 Not Implemented
|
|
809
|
+
// or 200 with proper audit response
|
|
810
|
+
expect([200, 501].includes(res.status)).toBe(true)
|
|
811
|
+
|
|
812
|
+
if (res.status === 501) {
|
|
813
|
+
const data = (await res.json()) as any
|
|
814
|
+
expect(data).toBeDefined()
|
|
815
|
+
// Should indicate that audit is not implemented
|
|
816
|
+
}
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
it('should handle GET requests to audit endpoint', async () => {
|
|
820
|
+
const res = await app.request('/-/npm/audit', {}, env)
|
|
821
|
+
// Audit functionality is not implemented yet, expect error status codes
|
|
822
|
+
expect([400, 404, 405, 500, 501].includes(res.status)).toBe(
|
|
823
|
+
true,
|
|
824
|
+
)
|
|
825
|
+
})
|
|
826
|
+
})
|
|
827
|
+
})
|
|
828
|
+
})
|