@vltpkg/vsr 0.2.0 → 0.2.1

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.
@@ -1,760 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { createContext } from './utils/test-helpers.js';
3
-
4
- // Mock the database client
5
- const mockDb = {
6
- getPackage: vi.fn(),
7
- upsertToken: vi.fn(),
8
- query: {
9
- tokens: {
10
- findMany: vi.fn()
11
- }
12
- }
13
- };
14
-
15
- // Mock auth utilities
16
- vi.mock('../src/utils/auth.js', async () => {
17
- const actual = await vi.importActual('../src/utils/auth.js');
18
- return {
19
- ...actual,
20
- verifyToken: vi.fn().mockImplementation(() => true),
21
- getTokenFromHeader: vi.fn().mockImplementation(() => 'test-token'),
22
- getAuthedUser: vi.fn()
23
- };
24
- });
25
-
26
- // Mock utils
27
- import { getTokenFromHeader, getAuthedUser, verifyToken, parseTokenAccess } from '../src/utils/auth.ts';
28
-
29
- // Import functions to test
30
- import {
31
- listPackagesAccess,
32
- getPackageAccessStatus,
33
- setPackageAccessStatus,
34
- grantPackageAccess,
35
- revokePackageAccess
36
- } from '../src/routes/access.js';
37
-
38
- describe('Access Management API', () => {
39
- beforeEach(() => {
40
- vi.clearAllMocks();
41
- });
42
-
43
- afterEach(() => {
44
- vi.resetAllMocks();
45
- });
46
-
47
- describe('listPackagesAccess', () => {
48
- it('should list packages a user has access to', async () => {
49
- // Mock authentication
50
- getAuthedUser.mockResolvedValue({
51
- uuid: 'test-user',
52
- scope: [
53
- {
54
- values: ['package-1', 'package-2'],
55
- types: { pkg: { read: true, write: true } }
56
- },
57
- {
58
- values: ['package-3'],
59
- types: { pkg: { read: true, write: false } }
60
- }
61
- ]
62
- });
63
-
64
- // Mock DB response
65
- mockDb.query.tokens.findMany.mockResolvedValue([
66
- {
67
- token: 'test-token',
68
- uuid: 'test-user',
69
- scope: JSON.stringify([
70
- {
71
- values: ['package-1', 'package-2'],
72
- types: { pkg: { read: true, write: true } }
73
- },
74
- {
75
- values: ['package-3'],
76
- types: { pkg: { read: true, write: false } }
77
- }
78
- ])
79
- }
80
- ]);
81
-
82
- // Create test context
83
- const c = createContext({
84
- req: {
85
- method: 'GET',
86
- query: new Map([['user', 'test-user']])
87
- },
88
- db: mockDb
89
- });
90
-
91
- // Execute function
92
- const response = await listPackagesAccess(c);
93
-
94
- // Verify mock calls
95
- expect(verifyToken).toHaveBeenCalledWith('test-token', expect.anything());
96
- expect(getAuthedUser).toHaveBeenCalledWith({ c, token: 'test-token' });
97
- expect(mockDb.query.tokens.findMany).toHaveBeenCalled();
98
-
99
- // Verify response
100
- expect(response.status).toBe(200);
101
- const body = await response.json();
102
- expect(body).toEqual({
103
- 'package-1': { read: true, write: true },
104
- 'package-2': { read: true, write: true },
105
- 'package-3': { read: true, write: false }
106
- });
107
- });
108
-
109
- it('should return unauthorized if not authenticated', async () => {
110
- // Mock failed authentication
111
- verifyToken.mockResolvedValue(false);
112
-
113
- // Create test context
114
- const c = createContext({
115
- req: { method: 'GET' },
116
- db: mockDb
117
- });
118
-
119
- // Execute function
120
- const response = await listPackagesAccess(c);
121
-
122
- // Verify response
123
- expect(response.status).toBe(401);
124
- const body = await response.json();
125
- expect(body).toEqual({ error: 'Unauthorized' });
126
- });
127
-
128
- it('should return empty result if user has no tokens', async () => {
129
- // Mock authentication
130
- getAuthedUser.mockResolvedValue({
131
- uuid: 'test-user',
132
- scope: []
133
- });
134
-
135
- // Mock DB response - no tokens
136
- mockDb.query.tokens.findMany.mockResolvedValue([]);
137
-
138
- // Create test context
139
- const c = createContext({
140
- req: { method: 'GET' },
141
- db: mockDb
142
- });
143
-
144
- // Execute function
145
- const response = await listPackagesAccess(c);
146
-
147
- // Verify response
148
- expect(response.status).toBe(200);
149
- const body = await response.json();
150
- expect(body).toEqual({});
151
- });
152
- });
153
-
154
- describe('getPackageAccessStatus', () => {
155
- it('should return private status for existing package', async () => {
156
- // Mock package data
157
- mockDb.getPackage.mockResolvedValue({
158
- name: 'test-package',
159
- tags: { latest: '1.0.0' }
160
- });
161
-
162
- // Create test context with package param
163
- const c = createContext({
164
- req: { method: 'GET' },
165
- db: mockDb,
166
- // Mock packageSpec functionality
167
- pkg: 'test-package'
168
- });
169
-
170
- // Execute function
171
- const response = await getPackageAccessStatus(c);
172
-
173
- // Verify mock calls
174
- expect(mockDb.getPackage).toHaveBeenCalledWith('test-package');
175
-
176
- // Verify response
177
- expect(response.status).toBe(200);
178
- const body = await response.json();
179
- expect(body).toEqual({ status: 'private' });
180
- });
181
-
182
- it('should return private status for non-existent package', async () => {
183
- // Mock package data - not found
184
- mockDb.getPackage.mockResolvedValue(null);
185
-
186
- // Create test context with package param
187
- const c = createContext({
188
- req: { method: 'GET' },
189
- db: mockDb,
190
- // Mock packageSpec functionality
191
- pkg: 'non-existent-package'
192
- });
193
-
194
- // Execute function
195
- const response = await getPackageAccessStatus(c);
196
-
197
- // Verify response
198
- expect(response.status).toBe(200);
199
- const body = await response.json();
200
- expect(body).toEqual({ status: 'private' });
201
- });
202
- });
203
-
204
- describe('setPackageAccessStatus', () => {
205
- it('should accept setting a package to private', async () => {
206
- // Mock package data
207
- mockDb.getPackage.mockResolvedValue({
208
- name: 'test-package',
209
- tags: { latest: '1.0.0' }
210
- });
211
-
212
- // Mock authentication with write access
213
- getAuthedUser.mockResolvedValue({
214
- uuid: 'test-user',
215
- scope: [
216
- {
217
- values: ['test-package'],
218
- types: { pkg: { read: true, write: true } }
219
- }
220
- ]
221
- });
222
-
223
- // Mock the token access parsing
224
- vi.mock('../src/utils/auth.js', async () => {
225
- const actual = await vi.importActual('../src/utils/auth.js');
226
- return {
227
- ...actual,
228
- verifyToken: vi.fn().mockImplementation(() => true),
229
- getTokenFromHeader: vi.fn().mockImplementation(() => 'test-token'),
230
- getAuthedUser: vi.fn(),
231
- parseTokenAccess: vi.fn().mockImplementation(() => ({
232
- readAccess: true,
233
- writeAccess: true
234
- }))
235
- };
236
- });
237
-
238
- // Create test context
239
- const c = createContext({
240
- req: {
241
- method: 'PUT',
242
- body: { status: 'private' }
243
- },
244
- db: mockDb,
245
- pkg: 'test-package'
246
- });
247
-
248
- // Execute function
249
- const response = await setPackageAccessStatus(c);
250
-
251
- // Verify response
252
- expect(response.status).toBe(200);
253
- const body = await response.json();
254
- expect(body.success).toBe(true);
255
- });
256
-
257
- it('should reject setting a package to public', async () => {
258
- // Mock package data
259
- mockDb.getPackage.mockResolvedValue({
260
- name: 'test-package',
261
- tags: { latest: '1.0.0' }
262
- });
263
-
264
- // Mock admin user with write access
265
- getAuthedUser.mockResolvedValue({
266
- uuid: 'admin',
267
- scope: [
268
- {
269
- values: ['*'],
270
- types: { pkg: { read: true, write: true } }
271
- }
272
- ]
273
- });
274
-
275
- // Create test context
276
- const c = createContext({
277
- req: {
278
- method: 'PUT',
279
- json: async () => ({ status: 'public' })
280
- },
281
- db: mockDb,
282
- // Mock packageSpec functionality
283
- pkg: 'test-package'
284
- });
285
-
286
- // Execute function
287
- const response = await setPackageAccessStatus(c);
288
-
289
- // Verify response
290
- expect(response.status).toBe(400);
291
- const body = await response.json();
292
- expect(body).toEqual({
293
- error: 'Public packages are not supported by this registry at this time'
294
- });
295
- });
296
-
297
- it('should reject if user lacks write access', async () => {
298
- // Mock package data
299
- mockDb.getPackage.mockResolvedValue({
300
- name: 'test-package',
301
- tags: { latest: '1.0.0' }
302
- });
303
-
304
- // Mock user with only read access
305
- getAuthedUser.mockResolvedValue({
306
- uuid: 'regular-user',
307
- scope: [
308
- {
309
- values: ['test-package'],
310
- types: { pkg: { read: true, write: false } }
311
- }
312
- ]
313
- });
314
-
315
- // Create test context
316
- const c = createContext({
317
- req: {
318
- method: 'PUT',
319
- json: async () => ({ status: 'private' })
320
- },
321
- db: mockDb,
322
- // Mock packageSpec functionality
323
- pkg: 'test-package'
324
- });
325
-
326
- // Execute function
327
- const response = await setPackageAccessStatus(c);
328
-
329
- // Verify response
330
- expect(response.status).toBe(403);
331
- const body = await response.json();
332
- expect(body).toEqual({
333
- error: 'Unauthorized - you do not have write access to this package'
334
- });
335
- });
336
- });
337
-
338
- describe('grantPackageAccess', () => {
339
- it('should grant read-only access to a user', async () => {
340
- // Mock package data
341
- mockDb.getPackage.mockResolvedValue({
342
- name: 'test-package',
343
- tags: { latest: '1.0.0' }
344
- });
345
-
346
- // Mock authentication
347
- getAuthedUser.mockResolvedValue({
348
- uuid: 'admin-user',
349
- scope: [
350
- {
351
- values: ['*'],
352
- types: {
353
- user: { read: true, write: true },
354
- pkg: { read: true, write: true }
355
- }
356
- }
357
- ]
358
- });
359
-
360
- // Create test context
361
- const c = createContext({
362
- req: {
363
- method: 'PUT',
364
- body: { permission: 'read-only' }
365
- },
366
- db: mockDb,
367
- pkg: 'test-package',
368
- username: 'test-user'
369
- });
370
-
371
- // Execute function
372
- const response = await grantPackageAccess(c);
373
-
374
- // Verify mock calls
375
- expect(mockDb.upsertToken).toHaveBeenCalled();
376
-
377
- // Verify the arguments manually since the deep comparison isn't working in tests
378
- const callArgs = mockDb.upsertToken.mock.calls[0];
379
- expect(callArgs[0]).toBe('user-token'); // First arg: token
380
- expect(callArgs[1]).toBe('test-user'); // Second arg: username
381
-
382
- // Third arg - scope object validation
383
- const scope = callArgs[2];
384
- expect(Array.isArray(scope)).toBe(true);
385
- expect(scope.length).toBe(1);
386
- expect(scope[0].values).toContain('test-package');
387
- expect(scope[0].types.pkg.read).toBe(true);
388
- expect(scope[0].types.pkg.write).toBe(false);
389
-
390
- // Verify response
391
- expect(response.status).toBe(200);
392
- const body = await response.json();
393
- expect(body.success).toBe(true);
394
- });
395
-
396
- it('should grant read-write access to a user', async () => {
397
- // Mock package data
398
- mockDb.getPackage.mockResolvedValue({
399
- name: 'test-package',
400
- tags: { latest: '1.0.0' }
401
- });
402
-
403
- // Mock admin user with write access to users and packages
404
- getAuthedUser.mockResolvedValue({
405
- uuid: 'admin',
406
- scope: [
407
- {
408
- values: ['*'],
409
- types: {
410
- pkg: { read: true, write: true },
411
- user: { read: true, write: true }
412
- }
413
- }
414
- ]
415
- });
416
-
417
- // Mock existing user tokens
418
- mockDb.query.tokens.findMany.mockResolvedValue([
419
- {
420
- token: 'user-token',
421
- uuid: 'test-user',
422
- scope: JSON.stringify([])
423
- }
424
- ]);
425
-
426
- // Create test context
427
- const c = createContext({
428
- req: {
429
- method: 'PUT',
430
- param: new Map([['username', 'test-user']]),
431
- json: async () => ({ permission: 'read-write' })
432
- },
433
- db: mockDb,
434
- // Mock packageSpec functionality
435
- pkg: 'test-package'
436
- });
437
-
438
- // Execute function
439
- const response = await grantPackageAccess(c);
440
-
441
- // Verify mock calls
442
- expect(mockDb.upsertToken).toHaveBeenCalledWith(
443
- 'user-token',
444
- 'test-user',
445
- [
446
- {
447
- values: ['test-package'],
448
- types: { pkg: { read: true, write: true } }
449
- }
450
- ],
451
- 'test-token'
452
- );
453
-
454
- // Verify response
455
- expect(response.status).toBe(200);
456
- const body = await response.json();
457
- expect(body).toEqual({
458
- success: true,
459
- message: 'Added read-write permission for test-user on test-package'
460
- });
461
- });
462
-
463
- it('should update existing package access', async () => {
464
- // Mock package data
465
- mockDb.getPackage.mockResolvedValue({
466
- name: 'test-package',
467
- tags: { latest: '1.0.0' }
468
- });
469
-
470
- // Mock admin user with write access to users and packages
471
- getAuthedUser.mockResolvedValue({
472
- uuid: 'admin',
473
- scope: [
474
- {
475
- values: ['*'],
476
- types: {
477
- pkg: { read: true, write: true },
478
- user: { read: true, write: true }
479
- }
480
- }
481
- ]
482
- });
483
-
484
- // Mock existing user tokens with existing access
485
- mockDb.query.tokens.findMany.mockResolvedValue([
486
- {
487
- token: 'user-token',
488
- uuid: 'test-user',
489
- scope: JSON.stringify([
490
- {
491
- values: ['test-package'],
492
- types: { pkg: { read: true, write: false } }
493
- }
494
- ])
495
- }
496
- ]);
497
-
498
- // Create test context
499
- const c = createContext({
500
- req: {
501
- method: 'PUT',
502
- param: new Map([['username', 'test-user']]),
503
- json: async () => ({ permission: 'read-write' })
504
- },
505
- db: mockDb,
506
- // Mock packageSpec functionality
507
- pkg: 'test-package'
508
- });
509
-
510
- // Execute function
511
- const response = await grantPackageAccess(c);
512
-
513
- // Verify mock calls - should update to read-write
514
- expect(mockDb.upsertToken).toHaveBeenCalledWith(
515
- 'user-token',
516
- 'test-user',
517
- [
518
- {
519
- values: ['test-package'],
520
- types: { pkg: { read: true, write: true } }
521
- }
522
- ],
523
- 'test-token'
524
- );
525
-
526
- // Verify response
527
- expect(response.status).toBe(200);
528
- });
529
-
530
- it('should reject if admin user lacks user write access', async () => {
531
- // Mock package data
532
- mockDb.getPackage.mockResolvedValue({
533
- name: 'test-package',
534
- tags: { latest: '1.0.0' }
535
- });
536
-
537
- // Mock user with only package write access but no user write access
538
- getAuthedUser.mockResolvedValue({
539
- uuid: 'package-admin',
540
- scope: [
541
- {
542
- values: ['*'],
543
- types: { pkg: { read: true, write: true } }
544
- },
545
- {
546
- values: ['*'],
547
- types: { user: { read: true, write: false } }
548
- }
549
- ]
550
- });
551
-
552
- // Create test context
553
- const c = createContext({
554
- req: {
555
- method: 'PUT',
556
- param: new Map([['username', 'test-user']]),
557
- json: async () => ({ permission: 'read-only' })
558
- },
559
- db: mockDb,
560
- // Mock packageSpec functionality
561
- pkg: 'test-package'
562
- });
563
-
564
- // Execute function
565
- const response = await grantPackageAccess(c);
566
-
567
- // Verify response
568
- expect(response.status).toBe(403);
569
- const body = await response.json();
570
- expect(body).toEqual({
571
- error: 'Unauthorized - you do not have permission to manage user access'
572
- });
573
- });
574
- });
575
-
576
- describe('revokePackageAccess', () => {
577
- it('should revoke access to a package', async () => {
578
- // Mock package data
579
- mockDb.getPackage.mockResolvedValue({
580
- name: 'test-package',
581
- tags: { latest: '1.0.0' }
582
- });
583
-
584
- // Mock admin user with write access to users and packages
585
- getAuthedUser.mockResolvedValue({
586
- uuid: 'admin',
587
- scope: [
588
- {
589
- values: ['*'],
590
- types: {
591
- pkg: { read: true, write: true },
592
- user: { read: true, write: true }
593
- }
594
- }
595
- ]
596
- });
597
-
598
- // Mock existing user tokens with access to be revoked
599
- mockDb.query.tokens.findMany.mockResolvedValue([
600
- {
601
- token: 'user-token',
602
- uuid: 'test-user',
603
- scope: JSON.stringify([
604
- {
605
- values: ['test-package'],
606
- types: { pkg: { read: true, write: true } }
607
- },
608
- {
609
- values: ['other-package'],
610
- types: { pkg: { read: true, write: false } }
611
- }
612
- ])
613
- }
614
- ]);
615
-
616
- // Create test context
617
- const c = createContext({
618
- req: {
619
- method: 'DELETE',
620
- param: new Map([['username', 'test-user']])
621
- },
622
- db: mockDb,
623
- // Mock packageSpec functionality
624
- pkg: 'test-package'
625
- });
626
-
627
- // Execute function
628
- const response = await revokePackageAccess(c);
629
-
630
- // Verify mock calls - should update to remove the package
631
- expect(mockDb.upsertToken).toHaveBeenCalledWith(
632
- 'user-token',
633
- 'test-user',
634
- [
635
- {
636
- values: ['other-package'],
637
- types: { pkg: { read: true, write: false } }
638
- }
639
- ],
640
- 'test-token'
641
- );
642
-
643
- // Verify response
644
- expect(response.status).toBe(200);
645
- const body = await response.json();
646
- expect(body).toEqual({
647
- success: true,
648
- message: 'Removed access for test-user to test-package'
649
- });
650
- });
651
-
652
- it('should handle when user has no access to revoke', async () => {
653
- // Mock package data
654
- mockDb.getPackage.mockResolvedValue({
655
- name: 'test-package',
656
- tags: { latest: '1.0.0' }
657
- });
658
-
659
- // Mock admin user with write access to users and packages
660
- getAuthedUser.mockResolvedValue({
661
- uuid: 'admin',
662
- scope: [
663
- {
664
- values: ['*'],
665
- types: {
666
- pkg: { read: true, write: true },
667
- user: { read: true, write: true }
668
- }
669
- }
670
- ]
671
- });
672
-
673
- // Mock existing user tokens with no access to the package
674
- mockDb.query.tokens.findMany.mockResolvedValue([
675
- {
676
- token: 'user-token',
677
- uuid: 'test-user',
678
- scope: JSON.stringify([
679
- {
680
- values: ['other-package'],
681
- types: { pkg: { read: true, write: false } }
682
- }
683
- ])
684
- }
685
- ]);
686
-
687
- // Create test context
688
- const c = createContext({
689
- req: {
690
- method: 'DELETE',
691
- param: new Map([['username', 'test-user']])
692
- },
693
- db: mockDb,
694
- // Mock packageSpec functionality
695
- pkg: 'test-package'
696
- });
697
-
698
- // Execute function
699
- const response = await revokePackageAccess(c);
700
-
701
- // Verify that token was not updated
702
- expect(mockDb.upsertToken).not.toHaveBeenCalled();
703
-
704
- // Verify response
705
- expect(response.status).toBe(200);
706
- const body = await response.json();
707
- expect(body).toEqual({
708
- success: true,
709
- message: 'User test-user did not have access to test-package'
710
- });
711
- });
712
-
713
- it('should handle when user has no tokens', async () => {
714
- // Mock package data
715
- mockDb.getPackage.mockResolvedValue({
716
- name: 'test-package',
717
- tags: { latest: '1.0.0' }
718
- });
719
-
720
- // Mock admin user with write access to users and packages
721
- getAuthedUser.mockResolvedValue({
722
- uuid: 'admin',
723
- scope: [
724
- {
725
- values: ['*'],
726
- types: {
727
- pkg: { read: true, write: true },
728
- user: { read: true, write: true }
729
- }
730
- }
731
- ]
732
- });
733
-
734
- // Mock no existing user tokens
735
- mockDb.query.tokens.findMany.mockResolvedValue([]);
736
-
737
- // Create test context
738
- const c = createContext({
739
- req: {
740
- method: 'DELETE',
741
- param: new Map([['username', 'test-user']])
742
- },
743
- db: mockDb,
744
- // Mock packageSpec functionality
745
- pkg: 'test-package'
746
- });
747
-
748
- // Execute function
749
- const response = await revokePackageAccess(c);
750
-
751
- // Verify response
752
- expect(response.status).toBe(200);
753
- const body = await response.json();
754
- expect(body).toEqual({
755
- success: true,
756
- message: 'User test-user did not have access to test-package'
757
- });
758
- });
759
- });
760
- });