@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,705 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { env } from 'cloudflare:test'
|
|
3
|
+
import { app } from '../src/index.ts'
|
|
4
|
+
|
|
5
|
+
describe('Access Control Endpoints', () => {
|
|
6
|
+
describe('Package Access Status', () => {
|
|
7
|
+
describe('Unscoped Packages', () => {
|
|
8
|
+
it('should get access status for unscoped packages', async () => {
|
|
9
|
+
const res = await app.request(
|
|
10
|
+
'/-/package/lodash/access',
|
|
11
|
+
{},
|
|
12
|
+
env,
|
|
13
|
+
)
|
|
14
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
15
|
+
true,
|
|
16
|
+
)
|
|
17
|
+
// Content-type depends on response status
|
|
18
|
+
if (res.status === 200) {
|
|
19
|
+
expect(res.headers.get('content-type')).toContain(
|
|
20
|
+
'application/json',
|
|
21
|
+
)
|
|
22
|
+
} else if (res.status === 500) {
|
|
23
|
+
expect(res.headers.get('content-type')).toContain(
|
|
24
|
+
'text/plain',
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should set access status for unscoped packages', async () => {
|
|
30
|
+
const res = await app.request(
|
|
31
|
+
'/-/package/lodash/access',
|
|
32
|
+
{
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
access: 'public',
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
env,
|
|
42
|
+
)
|
|
43
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
44
|
+
true,
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should handle private access setting', async () => {
|
|
49
|
+
const res = await app.request(
|
|
50
|
+
'/-/package/my-private-package/access',
|
|
51
|
+
{
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
access: 'restricted',
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
env,
|
|
61
|
+
)
|
|
62
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
63
|
+
true,
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('Scoped Packages', () => {
|
|
69
|
+
it('should get access status for scoped packages', async () => {
|
|
70
|
+
const res = await app.request(
|
|
71
|
+
'/-/package/@types%2Fnode/access',
|
|
72
|
+
{},
|
|
73
|
+
env,
|
|
74
|
+
)
|
|
75
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
76
|
+
true,
|
|
77
|
+
)
|
|
78
|
+
// Content-type depends on response status
|
|
79
|
+
if (res.status === 200) {
|
|
80
|
+
expect(res.headers.get('content-type')).toContain(
|
|
81
|
+
'application/json',
|
|
82
|
+
)
|
|
83
|
+
} else if (res.status === 500) {
|
|
84
|
+
expect(res.headers.get('content-type')).toContain(
|
|
85
|
+
'text/plain',
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should set access status for scoped packages', async () => {
|
|
91
|
+
const res = await app.request(
|
|
92
|
+
'/-/package/@myorg%2Fmypackage/access',
|
|
93
|
+
{
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
access: 'public',
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
env,
|
|
103
|
+
)
|
|
104
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
105
|
+
true,
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should handle URL-encoded scoped package names', async () => {
|
|
110
|
+
const res = await app.request(
|
|
111
|
+
'/-/package/@scope%2Fpackage/access',
|
|
112
|
+
{},
|
|
113
|
+
env,
|
|
114
|
+
)
|
|
115
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
116
|
+
true,
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('Access Status Response Structure', () => {
|
|
122
|
+
it('should return proper JSON structure for access status', async () => {
|
|
123
|
+
const res = await app.request(
|
|
124
|
+
'/-/package/lodash/access',
|
|
125
|
+
{},
|
|
126
|
+
env,
|
|
127
|
+
)
|
|
128
|
+
if (res.status === 200) {
|
|
129
|
+
const data = (await res.json()) as any
|
|
130
|
+
expect(data).toBeDefined()
|
|
131
|
+
// Access status structure would be validated based on implementation
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should include access level in response', async () => {
|
|
136
|
+
const res = await app.request(
|
|
137
|
+
'/-/package/lodash/access',
|
|
138
|
+
{},
|
|
139
|
+
env,
|
|
140
|
+
)
|
|
141
|
+
if (res.status === 200) {
|
|
142
|
+
const data = (await res.json()) as any
|
|
143
|
+
expect(data).toBeDefined()
|
|
144
|
+
// Would validate access field based on implementation
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('Package Collaborators', () => {
|
|
151
|
+
describe('Unscoped Package Collaborators', () => {
|
|
152
|
+
it('should grant access to unscoped package collaborators', async () => {
|
|
153
|
+
const res = await app.request(
|
|
154
|
+
'/-/package/lodash/collaborators/testuser',
|
|
155
|
+
{
|
|
156
|
+
method: 'PUT',
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
permissions: ['read', 'write'],
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
env,
|
|
165
|
+
)
|
|
166
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
167
|
+
true,
|
|
168
|
+
)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should revoke access from unscoped package collaborators', async () => {
|
|
172
|
+
const res = await app.request(
|
|
173
|
+
'/-/package/lodash/collaborators/testuser',
|
|
174
|
+
{
|
|
175
|
+
method: 'DELETE',
|
|
176
|
+
},
|
|
177
|
+
env,
|
|
178
|
+
)
|
|
179
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
180
|
+
true,
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('should handle different permission levels', async () => {
|
|
185
|
+
const res = await app.request(
|
|
186
|
+
'/-/package/lodash/collaborators/testuser',
|
|
187
|
+
{
|
|
188
|
+
method: 'PUT',
|
|
189
|
+
headers: {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify({
|
|
193
|
+
permissions: ['read'],
|
|
194
|
+
}),
|
|
195
|
+
},
|
|
196
|
+
env,
|
|
197
|
+
)
|
|
198
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
199
|
+
true,
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe('Scoped Package Collaborators', () => {
|
|
205
|
+
it('should grant access to scoped package collaborators', async () => {
|
|
206
|
+
const res = await app.request(
|
|
207
|
+
'/-/package/@myorg%2Fmypackage/collaborators/testuser',
|
|
208
|
+
{
|
|
209
|
+
method: 'PUT',
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
permissions: ['read', 'write'],
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
env,
|
|
218
|
+
)
|
|
219
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
220
|
+
true,
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should revoke access from scoped package collaborators', async () => {
|
|
225
|
+
const res = await app.request(
|
|
226
|
+
'/-/package/@myorg%2Fmypackage/collaborators/testuser',
|
|
227
|
+
{
|
|
228
|
+
method: 'DELETE',
|
|
229
|
+
},
|
|
230
|
+
env,
|
|
231
|
+
)
|
|
232
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
233
|
+
true,
|
|
234
|
+
)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should handle multiple collaborators for scoped packages', async () => {
|
|
238
|
+
const res = await app.request(
|
|
239
|
+
'/-/package/@myorg%2Fmypackage/collaborators/user1',
|
|
240
|
+
{
|
|
241
|
+
method: 'PUT',
|
|
242
|
+
headers: {
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
},
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
permissions: ['read', 'write', 'admin'],
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
env,
|
|
250
|
+
)
|
|
251
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
252
|
+
true,
|
|
253
|
+
)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('Collaborator Management', () => {
|
|
258
|
+
it('should handle admin permissions for collaborators', async () => {
|
|
259
|
+
const res = await app.request(
|
|
260
|
+
'/-/package/mypackage/collaborators/admin',
|
|
261
|
+
{
|
|
262
|
+
method: 'PUT',
|
|
263
|
+
headers: {
|
|
264
|
+
'Content-Type': 'application/json',
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({
|
|
267
|
+
permissions: ['read', 'write', 'admin'],
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
env,
|
|
271
|
+
)
|
|
272
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
273
|
+
true,
|
|
274
|
+
)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should validate permission types', async () => {
|
|
278
|
+
const res = await app.request(
|
|
279
|
+
'/-/package/mypackage/collaborators/testuser',
|
|
280
|
+
{
|
|
281
|
+
method: 'PUT',
|
|
282
|
+
headers: {
|
|
283
|
+
'Content-Type': 'application/json',
|
|
284
|
+
},
|
|
285
|
+
body: JSON.stringify({
|
|
286
|
+
permissions: ['invalid-permission'],
|
|
287
|
+
}),
|
|
288
|
+
},
|
|
289
|
+
env,
|
|
290
|
+
)
|
|
291
|
+
expect([400, 401, 404, 500].includes(res.status)).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should handle empty permissions array', async () => {
|
|
295
|
+
const res = await app.request(
|
|
296
|
+
'/-/package/mypackage/collaborators/testuser',
|
|
297
|
+
{
|
|
298
|
+
method: 'PUT',
|
|
299
|
+
headers: {
|
|
300
|
+
'Content-Type': 'application/json',
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
permissions: [],
|
|
304
|
+
}),
|
|
305
|
+
},
|
|
306
|
+
env,
|
|
307
|
+
)
|
|
308
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
309
|
+
true,
|
|
310
|
+
)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
describe('Package List Access', () => {
|
|
316
|
+
describe('List All Packages', () => {
|
|
317
|
+
it('should list packages with access information', async () => {
|
|
318
|
+
const res = await app.request('/-/package/list', {}, env)
|
|
319
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
320
|
+
true,
|
|
321
|
+
)
|
|
322
|
+
// Content-type depends on response status
|
|
323
|
+
if (res.status === 200) {
|
|
324
|
+
expect(res.headers.get('content-type')).toContain(
|
|
325
|
+
'application/json',
|
|
326
|
+
)
|
|
327
|
+
} else if (res.status === 500) {
|
|
328
|
+
expect(res.headers.get('content-type')).toContain(
|
|
329
|
+
'text/plain',
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('should handle authenticated package listing', async () => {
|
|
335
|
+
const res = await app.request(
|
|
336
|
+
'/-/package/list',
|
|
337
|
+
{
|
|
338
|
+
headers: {
|
|
339
|
+
Authorization: 'Bearer test-admin-token-12345',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
env,
|
|
343
|
+
)
|
|
344
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
345
|
+
true,
|
|
346
|
+
)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should return proper structure for package list', async () => {
|
|
350
|
+
const res = await app.request('/-/package/list', {}, env)
|
|
351
|
+
if (res.status === 200) {
|
|
352
|
+
const data = (await res.json()) as any
|
|
353
|
+
expect(data).toBeDefined()
|
|
354
|
+
// Package list structure would be validated based on implementation
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe('Filtered Package Lists', () => {
|
|
360
|
+
it('should handle package list filtering by access level', async () => {
|
|
361
|
+
const res = await app.request(
|
|
362
|
+
'/-/package/list?access=public',
|
|
363
|
+
{},
|
|
364
|
+
env,
|
|
365
|
+
)
|
|
366
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
367
|
+
true,
|
|
368
|
+
)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('should handle package list filtering by user', async () => {
|
|
372
|
+
const res = await app.request(
|
|
373
|
+
'/-/package/list?user=testuser',
|
|
374
|
+
{},
|
|
375
|
+
env,
|
|
376
|
+
)
|
|
377
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
378
|
+
true,
|
|
379
|
+
)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('should handle pagination for package lists', async () => {
|
|
383
|
+
const res = await app.request(
|
|
384
|
+
'/-/package/list?limit=10&offset=0',
|
|
385
|
+
{},
|
|
386
|
+
env,
|
|
387
|
+
)
|
|
388
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
389
|
+
true,
|
|
390
|
+
)
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
describe('Authentication and Authorization', () => {
|
|
396
|
+
describe('Authentication Requirements', () => {
|
|
397
|
+
it('should require authentication for access modification', async () => {
|
|
398
|
+
const res = await app.request(
|
|
399
|
+
'/-/package/mypackage/access',
|
|
400
|
+
{
|
|
401
|
+
method: 'POST',
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-Type': 'application/json',
|
|
404
|
+
},
|
|
405
|
+
body: JSON.stringify({
|
|
406
|
+
access: 'public',
|
|
407
|
+
}),
|
|
408
|
+
},
|
|
409
|
+
env,
|
|
410
|
+
)
|
|
411
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
412
|
+
true,
|
|
413
|
+
)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should require authentication for collaborator management', async () => {
|
|
417
|
+
const res = await app.request(
|
|
418
|
+
'/-/package/mypackage/collaborators/testuser',
|
|
419
|
+
{
|
|
420
|
+
method: 'PUT',
|
|
421
|
+
headers: {
|
|
422
|
+
'Content-Type': 'application/json',
|
|
423
|
+
},
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
permissions: ['read'],
|
|
426
|
+
}),
|
|
427
|
+
},
|
|
428
|
+
env,
|
|
429
|
+
)
|
|
430
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
431
|
+
true,
|
|
432
|
+
)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should handle valid authentication tokens', async () => {
|
|
436
|
+
const res = await app.request(
|
|
437
|
+
'/-/package/mypackage/access',
|
|
438
|
+
{
|
|
439
|
+
method: 'POST',
|
|
440
|
+
headers: {
|
|
441
|
+
Authorization: 'Bearer test-admin-token-12345',
|
|
442
|
+
'Content-Type': 'application/json',
|
|
443
|
+
},
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
access: 'public',
|
|
446
|
+
}),
|
|
447
|
+
},
|
|
448
|
+
env,
|
|
449
|
+
)
|
|
450
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
451
|
+
true,
|
|
452
|
+
)
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
describe('Authorization Levels', () => {
|
|
457
|
+
it('should check package ownership for access changes', async () => {
|
|
458
|
+
const res = await app.request(
|
|
459
|
+
'/-/package/someone-elses-package/access',
|
|
460
|
+
{
|
|
461
|
+
method: 'POST',
|
|
462
|
+
headers: {
|
|
463
|
+
Authorization: 'Bearer test-admin-token-12345',
|
|
464
|
+
'Content-Type': 'application/json',
|
|
465
|
+
},
|
|
466
|
+
body: JSON.stringify({
|
|
467
|
+
access: 'public',
|
|
468
|
+
}),
|
|
469
|
+
},
|
|
470
|
+
env,
|
|
471
|
+
)
|
|
472
|
+
expect(
|
|
473
|
+
[200, 400, 401, 403, 404, 500].includes(res.status),
|
|
474
|
+
).toBe(true)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('should check admin permissions for collaborator management', async () => {
|
|
478
|
+
const res = await app.request(
|
|
479
|
+
'/-/package/mypackage/collaborators/testuser',
|
|
480
|
+
{
|
|
481
|
+
method: 'PUT',
|
|
482
|
+
headers: {
|
|
483
|
+
Authorization: 'Bearer test-admin-token-12345',
|
|
484
|
+
'Content-Type': 'application/json',
|
|
485
|
+
},
|
|
486
|
+
body: JSON.stringify({
|
|
487
|
+
permissions: ['read'],
|
|
488
|
+
}),
|
|
489
|
+
},
|
|
490
|
+
env,
|
|
491
|
+
)
|
|
492
|
+
expect(
|
|
493
|
+
[200, 400, 401, 403, 404, 500].includes(res.status),
|
|
494
|
+
).toBe(true)
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
describe('Error Handling', () => {
|
|
500
|
+
describe('Invalid Package Names', () => {
|
|
501
|
+
it('should handle invalid package names in access requests', async () => {
|
|
502
|
+
const res = await app.request(
|
|
503
|
+
'/-/package/invalid..package/access',
|
|
504
|
+
env,
|
|
505
|
+
)
|
|
506
|
+
expect([400, 404, 500].includes(res.status)).toBe(true)
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
it('should handle malformed scoped package names', async () => {
|
|
510
|
+
const res = await app.request(
|
|
511
|
+
'/-/package/@invalid/access',
|
|
512
|
+
{},
|
|
513
|
+
env,
|
|
514
|
+
)
|
|
515
|
+
expect([400, 404, 500].includes(res.status)).toBe(true)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
it('should handle packages starting with dots', async () => {
|
|
519
|
+
const res = await app.request(
|
|
520
|
+
'/-/package/.hidden-package/access',
|
|
521
|
+
env,
|
|
522
|
+
)
|
|
523
|
+
expect([400, 404, 500].includes(res.status)).toBe(true)
|
|
524
|
+
})
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
describe('Invalid Request Bodies', () => {
|
|
528
|
+
it('should handle malformed JSON in access requests', async () => {
|
|
529
|
+
const res = await app.request(
|
|
530
|
+
'/-/package/mypackage/access',
|
|
531
|
+
{
|
|
532
|
+
method: 'POST',
|
|
533
|
+
headers: {
|
|
534
|
+
'Content-Type': 'application/json',
|
|
535
|
+
},
|
|
536
|
+
body: 'invalid-json',
|
|
537
|
+
},
|
|
538
|
+
env,
|
|
539
|
+
)
|
|
540
|
+
expect([400, 401, 500].includes(res.status)).toBe(true)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
it('should handle missing required fields', async () => {
|
|
544
|
+
const res = await app.request(
|
|
545
|
+
'/-/package/mypackage/access',
|
|
546
|
+
{
|
|
547
|
+
method: 'POST',
|
|
548
|
+
headers: {
|
|
549
|
+
'Content-Type': 'application/json',
|
|
550
|
+
},
|
|
551
|
+
body: JSON.stringify({}),
|
|
552
|
+
},
|
|
553
|
+
env,
|
|
554
|
+
)
|
|
555
|
+
expect([400, 401, 500].includes(res.status)).toBe(true)
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
it('should handle invalid access levels', async () => {
|
|
559
|
+
const res = await app.request(
|
|
560
|
+
'/-/package/mypackage/access',
|
|
561
|
+
{
|
|
562
|
+
method: 'POST',
|
|
563
|
+
headers: {
|
|
564
|
+
'Content-Type': 'application/json',
|
|
565
|
+
},
|
|
566
|
+
body: JSON.stringify({
|
|
567
|
+
access: 'invalid-access-level',
|
|
568
|
+
}),
|
|
569
|
+
},
|
|
570
|
+
env,
|
|
571
|
+
)
|
|
572
|
+
expect([400, 401, 500].includes(res.status)).toBe(true)
|
|
573
|
+
})
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
describe('Non-existent Resources', () => {
|
|
577
|
+
it('should handle non-existent packages', async () => {
|
|
578
|
+
const res = await app.request(
|
|
579
|
+
'/-/package/nonexistent-package-12345/access',
|
|
580
|
+
{},
|
|
581
|
+
env,
|
|
582
|
+
)
|
|
583
|
+
console.log(res.status)
|
|
584
|
+
expect([400, 404, 401, 500].includes(res.status)).toBe(true)
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it('should handle non-existent users in collaborator requests', async () => {
|
|
588
|
+
const res = await app.request(
|
|
589
|
+
'/-/package/mypackage/collaborators/nonexistent-user',
|
|
590
|
+
{
|
|
591
|
+
method: 'PUT',
|
|
592
|
+
headers: {
|
|
593
|
+
'Content-Type': 'application/json',
|
|
594
|
+
},
|
|
595
|
+
body: JSON.stringify({
|
|
596
|
+
permissions: ['read'],
|
|
597
|
+
}),
|
|
598
|
+
},
|
|
599
|
+
env,
|
|
600
|
+
)
|
|
601
|
+
expect([400, 401, 404, 500].includes(res.status)).toBe(true)
|
|
602
|
+
})
|
|
603
|
+
})
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
describe('Response Headers and Security', () => {
|
|
607
|
+
describe('Content-Type Headers', () => {
|
|
608
|
+
it('should set appropriate content-type headers', async () => {
|
|
609
|
+
const res = await app.request(
|
|
610
|
+
'/-/package/lodash/access',
|
|
611
|
+
{},
|
|
612
|
+
env,
|
|
613
|
+
)
|
|
614
|
+
if (res.status === 200) {
|
|
615
|
+
expect(res.headers.get('content-type')).toContain(
|
|
616
|
+
'application/json',
|
|
617
|
+
)
|
|
618
|
+
}
|
|
619
|
+
})
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
describe('Security Headers', () => {
|
|
623
|
+
it('should include security headers in access responses', async () => {
|
|
624
|
+
const res = await app.request(
|
|
625
|
+
'/-/package/lodash/access',
|
|
626
|
+
{},
|
|
627
|
+
env,
|
|
628
|
+
)
|
|
629
|
+
// Security headers would be validated based on implementation
|
|
630
|
+
expect(res.status).toBeDefined()
|
|
631
|
+
})
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
describe('CORS Headers', () => {
|
|
635
|
+
it('should handle CORS headers for access requests', async () => {
|
|
636
|
+
const res = await app.request(
|
|
637
|
+
'/-/package/lodash/access',
|
|
638
|
+
{
|
|
639
|
+
headers: {
|
|
640
|
+
Origin: 'https://example.com',
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
env,
|
|
644
|
+
)
|
|
645
|
+
// CORS headers would be validated based on implementation
|
|
646
|
+
expect(res.status).toBeDefined()
|
|
647
|
+
})
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
describe('Special Access Cases', () => {
|
|
652
|
+
describe('Organization Packages', () => {
|
|
653
|
+
it('should handle organization-scoped packages', async () => {
|
|
654
|
+
const res = await app.request(
|
|
655
|
+
'/-/package/@myorg%2Fpackage/access',
|
|
656
|
+
{},
|
|
657
|
+
env,
|
|
658
|
+
)
|
|
659
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
660
|
+
true,
|
|
661
|
+
)
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it('should handle organization member permissions', async () => {
|
|
665
|
+
const res = await app.request(
|
|
666
|
+
'/-/package/@myorg%2Fpackage/collaborators/orgmember',
|
|
667
|
+
{
|
|
668
|
+
method: 'PUT',
|
|
669
|
+
headers: {
|
|
670
|
+
'Content-Type': 'application/json',
|
|
671
|
+
},
|
|
672
|
+
body: JSON.stringify({
|
|
673
|
+
permissions: ['read', 'write'],
|
|
674
|
+
}),
|
|
675
|
+
},
|
|
676
|
+
env,
|
|
677
|
+
)
|
|
678
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
679
|
+
true,
|
|
680
|
+
)
|
|
681
|
+
})
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
describe('Team-based Access', () => {
|
|
685
|
+
it('should handle team-based access control', async () => {
|
|
686
|
+
const res = await app.request(
|
|
687
|
+
'/-/package/mypackage/collaborators/team:developers',
|
|
688
|
+
{
|
|
689
|
+
method: 'PUT',
|
|
690
|
+
headers: {
|
|
691
|
+
'Content-Type': 'application/json',
|
|
692
|
+
},
|
|
693
|
+
body: JSON.stringify({
|
|
694
|
+
permissions: ['read', 'write'],
|
|
695
|
+
}),
|
|
696
|
+
},
|
|
697
|
+
env,
|
|
698
|
+
)
|
|
699
|
+
expect([200, 400, 401, 404, 500].includes(res.status)).toBe(
|
|
700
|
+
true,
|
|
701
|
+
)
|
|
702
|
+
})
|
|
703
|
+
})
|
|
704
|
+
})
|
|
705
|
+
})
|