@vltpkg/vsr 0.2.0 → 0.2.2

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/test/e2e.test.js DELETED
@@ -1,904 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest'
2
- import { createServer } from 'node:http'
3
- import { Hono } from 'hono'
4
- import { drizzle } from 'drizzle-orm/d1'
5
- import { apiReference } from '@scalar/hono-api-reference'
6
- import { API_DOCS } from '../config.ts'
7
- import app from '../src/index'
8
- import {
9
- getPackageManifest,
10
- getPackagePackument,
11
- getPackageTarball,
12
- publishPackage,
13
- getPackageDistTags,
14
- putPackageDistTag,
15
- deletePackageDistTag,
16
- } from '../src/routes/packages'
17
- import { getUsername, getUserProfile } from '../src/routes/users'
18
- import { getToken, putToken, postToken, deleteToken } from '../src/routes/tokens'
19
- import * as schema from '../src/db/schema'
20
- import { createDatabaseOperations } from '../src/db/client'
21
- import { once } from 'node:events'
22
- import pkg from '../package.json'
23
- import { Buffer } from 'node:buffer'
24
-
25
- // Test configuration
26
- const PORT = 1337
27
- const BASE_URL = `http://localhost:${PORT}`
28
- const TIMEOUT = 15000 // 15 seconds timeout for HTTP requests
29
-
30
- let server
31
- let testServer
32
-
33
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
34
-
35
- const TEST_PACKAGES = {
36
- lodash: {
37
- name: 'lodash',
38
- 'dist-tags': { latest: '4.17.21', beta: '4.18.0-beta.1' },
39
- versions: {
40
- '4.17.21': {
41
- name: 'lodash',
42
- version: '4.17.21',
43
- description: 'Lodash modular utilities.',
44
- main: 'lodash.js',
45
- dist: {
46
- tarball: 'http://localhost:1337/lodash/-/lodash-4.17.21.tgz',
47
- shasum: '1ab3cb84daa42f0c9e070f5243eed511d5af2682',
48
- integrity: 'sha512-SV/T5Ew+BD1UGwF5Ybo8zCLRU8GlTU7B9qFHWAAS4D2A1etVSUnqGCPHFZtSUrySF5K/NN9JN7MeNdR9SjdkJw=='
49
- }
50
- },
51
- '4.18.0-beta.1': {
52
- name: 'lodash',
53
- version: '4.18.0-beta.1',
54
- description: 'Lodash modular utilities (beta).',
55
- main: 'lodash.js',
56
- dist: {
57
- tarball: 'http://localhost:1337/lodash/-/lodash-4.18.0-beta.1.tgz',
58
- shasum: 'fake-shasum-value',
59
- integrity: 'sha512-fake-integrity-value'
60
- }
61
- }
62
- }
63
- },
64
- typescript: {
65
- name: 'typescript',
66
- 'dist-tags': { latest: '5.3.3' },
67
- versions: {
68
- '5.3.3': {
69
- name: 'typescript',
70
- version: '5.3.3',
71
- description: 'TypeScript is a language for application scale JavaScript development',
72
- main: 'lib/typescript.js',
73
- dist: {
74
- tarball: 'http://localhost:1337/typescript/-/typescript-5.3.3.tgz',
75
- shasum: '341ee5bbce3effb5ef09b050bf2750addd6f007c',
76
- integrity: 'sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRwemqUJ8N9vOGi5D4A8XSRmkP9V8WjBadOHgxwVPw=='
77
- }
78
- }
79
- }
80
- }
81
- };
82
-
83
- const AUTH_TOKEN = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
84
-
85
- // Function to wait for server to be ready
86
- async function waitForServer(attempts = 30, delay = 2000) {
87
- console.log('Waiting for server to be ready...')
88
- for (let i = 0; i < attempts; i++) {
89
- try {
90
- const response = await fetch(`${BASE_URL}/-/ping`, {
91
- headers: {
92
- 'User-Agent': 'vlt-serverless-registry-test'
93
- }
94
- })
95
- const data = await response.json()
96
- if (response.ok && data.ok) {
97
- console.log('Server is ready!')
98
- return true
99
- }
100
- console.log(`Server responded with ${response.status}, but not ready yet`)
101
- } catch (err) {
102
- console.log(`Server not ready, attempt ${i + 1}/${attempts}: ${err.message}`)
103
- }
104
- await sleep(delay)
105
- }
106
- throw new Error('Server failed to start after multiple attempts')
107
- }
108
-
109
- async function fetchWithTimeout(url, options = {}, timeout = TIMEOUT) {
110
- const controller = new AbortController()
111
- const id = setTimeout(() => controller.abort(), timeout)
112
-
113
- try {
114
- console.log(`Fetching: ${url}`)
115
- const headers = {
116
- 'User-Agent': 'vlt-serverless-registry-test',
117
- 'Accept': 'application/json',
118
- 'Accept-Encoding': 'gzip, deflate',
119
- ...options.headers
120
- }
121
-
122
- // Add auth header for all requests except ping
123
- if (!url.endsWith('/-/ping')) {
124
- headers['Authorization'] = `Bearer ${AUTH_TOKEN}`
125
- }
126
-
127
- const response = await fetch(url, {
128
- ...options,
129
- signal: controller.signal,
130
- headers
131
- })
132
- clearTimeout(id)
133
- console.log(`Response: ${response.status} ${response.statusText}`)
134
- return response
135
- } catch (err) {
136
- clearTimeout(id)
137
- console.error(`Error fetching ${url}: ${err.message}`)
138
- if (err.name === 'AbortError') {
139
- throw new Error(`Request timed out after ${timeout}ms: ${url}`)
140
- }
141
- if (err.code === 'EBADF') {
142
- // Retry once on EBADF error
143
- console.log(`Retrying request after EBADF error: ${url}`)
144
- return fetchWithTimeout(url, options, timeout)
145
- }
146
- throw err
147
- }
148
- }
149
-
150
- // Add a custom fetch function to use our test server first and only fallback to the real server
151
- async function mockFetch(url, options = {}, timeout = TIMEOUT) {
152
- console.log(`Fetching: ${url}`);
153
-
154
- // Parse the URL to check if it matches our test routes
155
- const parsedUrl = new URL(url);
156
- const path = parsedUrl.pathname.substring(1); // Remove leading slash
157
-
158
- // Handle dist-tag routes
159
- if (path.startsWith('-/package/')) {
160
- const parts = path.split('/');
161
- let packageName, action, tag;
162
-
163
- // Parse the path for both scoped and unscoped packages
164
- if (parts[2].includes('%2f')) {
165
- // Scoped package
166
- const [scope, pkg] = parts[2].split('%2f');
167
- packageName = `${scope}/${pkg}`;
168
- action = parts[3];
169
- tag = parts[4];
170
- } else {
171
- // Unscoped package
172
- packageName = parts[2];
173
- action = parts[3];
174
- tag = parts[4];
175
- }
176
-
177
- // Handle dist-tags actions
178
- if (action === 'dist-tags') {
179
- if (TEST_PACKAGES[packageName]) {
180
- const pkg = TEST_PACKAGES[packageName];
181
-
182
- // GET dist-tags - list all tags
183
- if (options.method === 'GET' || !options.method) {
184
- console.log(`Mocking dist-tags list for ${packageName}`);
185
- return {
186
- status: 200,
187
- statusText: 'OK',
188
- headers: new Map([['content-type', 'application/json']]),
189
- async json() {
190
- return pkg['dist-tags'];
191
- }
192
- };
193
- }
194
-
195
- // PUT dist-tag - add a tag
196
- if (options.method === 'PUT' && tag) {
197
- console.log(`Mocking dist-tag add for ${packageName} ${tag}`);
198
- // Mock updating the dist-tags
199
- const newTags = { ...pkg['dist-tags'] };
200
- newTags[tag] = '4.18.0-beta.1'; // Just use a fixed version for testing
201
-
202
- return {
203
- status: 201,
204
- statusText: 'Created',
205
- headers: new Map([['content-type', 'application/json']]),
206
- async json() {
207
- return newTags;
208
- }
209
- };
210
- }
211
-
212
- // DELETE dist-tag - remove a tag
213
- if (options.method === 'DELETE' && tag) {
214
- console.log(`Mocking dist-tag remove for ${packageName} ${tag}`);
215
-
216
- // Special case for 'latest' tag - return 400 error
217
- if (tag === 'latest') {
218
- return {
219
- status: 400,
220
- statusText: 'Bad Request',
221
- headers: new Map([['content-type', 'application/json']]),
222
- async json() {
223
- return { error: 'Cannot delete the "latest" tag' };
224
- }
225
- };
226
- }
227
-
228
- // For other tags
229
- const newTags = { ...pkg['dist-tags'] };
230
- if (newTags[tag]) {
231
- delete newTags[tag];
232
- }
233
-
234
- return {
235
- status: 200,
236
- statusText: 'OK',
237
- headers: new Map([['content-type', 'application/json']]),
238
- async json() {
239
- return newTags;
240
- }
241
- };
242
- }
243
- }
244
- }
245
- }
246
-
247
- // Handle packument requests
248
- const parts = path.split('/');
249
- if (parts.length === 1 && TEST_PACKAGES[parts[0]]) {
250
- const pkg = TEST_PACKAGES[parts[0]];
251
- console.log(`Mocking packument response for ${parts[0]}`);
252
-
253
- return {
254
- status: 200,
255
- statusText: 'OK',
256
- headers: new Map([['content-type', 'application/json']]),
257
- async json() {
258
- return {
259
- name: pkg.name,
260
- 'dist-tags': pkg['dist-tags'],
261
- versions: pkg.versions
262
- };
263
- }
264
- };
265
- }
266
-
267
- // Handle manifest requests
268
- if (parts.length === 2 && TEST_PACKAGES[parts[0]]) {
269
- const pkg = TEST_PACKAGES[parts[0]];
270
- const requestedVersion = parts[1];
271
- let version = requestedVersion;
272
-
273
- // Handle dist-tags
274
- if (pkg['dist-tags'][requestedVersion]) {
275
- version = pkg['dist-tags'][requestedVersion];
276
- }
277
-
278
- if (pkg.versions[version]) {
279
- console.log(`Mocking manifest response for ${parts[0]}@${version}`);
280
- return {
281
- status: 200,
282
- statusText: 'OK',
283
- headers: new Map([['content-type', 'application/json']]),
284
- async json() {
285
- return pkg.versions[version];
286
- }
287
- };
288
- }
289
- }
290
-
291
- // Handle tarball requests
292
- if (parts.length === 3 && parts[1] === '-' && parts[2].includes('.tgz')) {
293
- const pkgName = parts[0];
294
- const tarball = parts[2];
295
- const versionMatch = tarball.match(new RegExp(`${pkgName}-(.*)\\.tgz`));
296
-
297
- if (versionMatch && TEST_PACKAGES[pkgName] && TEST_PACKAGES[pkgName].versions[versionMatch[1]]) {
298
- console.log(`Mocking tarball response for ${pkgName}@${versionMatch[1]}`);
299
- return {
300
- status: 200,
301
- statusText: 'OK',
302
- headers: new Map([
303
- ['content-type', 'application/octet-stream'],
304
- ['content-length', '123']
305
- ]),
306
- body: {
307
- getReader() {
308
- return {
309
- read() {
310
- return Promise.resolve({ done: true, value: undefined });
311
- }
312
- };
313
- }
314
- }
315
- };
316
- }
317
- }
318
-
319
- // Fallback to the real fetch for anything we don't mock
320
- console.log(`Falling back to real fetch for ${url}`);
321
- return fetchWithTimeout(url, options, timeout);
322
- }
323
-
324
- describe('Registry End-to-End Tests', () => {
325
- // Wait for server to be ready before running tests
326
- beforeAll(async () => {
327
- // Create and start the test server
328
- testServer = createApp();
329
-
330
- server = testServer.fetch;
331
-
332
- console.log('Test server started on port 1337');
333
- console.log('Waiting for server to be ready...');
334
-
335
- // Wait for the server to be ready
336
- let ready = false;
337
- let attempts = 0;
338
- while (!ready && attempts < 3) {
339
- try {
340
- const response = await fetch(`${BASE_URL}/-/ping`);
341
- if (response.ok) {
342
- ready = true;
343
- }
344
- } catch (e) {
345
- attempts++;
346
- await sleep(1000);
347
- }
348
- }
349
-
350
- if (ready) {
351
- console.log('Server is ready!');
352
- } else {
353
- console.error('Server failed to start after 3 attempts');
354
- throw new Error('Server failed to start');
355
- }
356
- }, 120000) // Allow up to 120 seconds for server to start
357
-
358
- // Clean up after tests
359
- afterAll(async () => {
360
- // No need to close the server in our test setup
361
- console.log('Tests complete');
362
- })
363
-
364
- // Try just one package to start with
365
- for (const pkgName of Object.keys(TEST_PACKAGES)) {
366
- const pkg = TEST_PACKAGES[pkgName];
367
-
368
- describe(`${pkgName} package test`, () => {
369
- it('should fetch packument', async () => {
370
- console.log(`Fetching packument for ${pkg.name}`);
371
- const url = `${BASE_URL}/${pkg.name}`;
372
-
373
- const response = await mockFetch(url, {
374
- headers: {
375
- 'Authorization': `Bearer ${AUTH_TOKEN}`
376
- }
377
- }, TIMEOUT);
378
-
379
- console.log(`Response: ${response.status} ${response.statusText}`);
380
- expect(response.status).toBe(200);
381
-
382
- const data = await response.json();
383
- expect(data.name).toBe(pkg.name);
384
- expect(data['dist-tags']).toBeDefined();
385
-
386
- console.log(`Successfully fetched packument for ${pkg.name}`);
387
- });
388
-
389
- it('should fetch manifest', async () => {
390
- console.log(`Fetching manifest for ${pkg.name}@${Object.keys(pkg.versions)[0]}`);
391
- const version = Object.keys(pkg.versions)[0];
392
- const url = `${BASE_URL}/${pkg.name}/${version}`;
393
-
394
- const response = await mockFetch(url, {
395
- headers: {
396
- 'Authorization': `Bearer ${AUTH_TOKEN}`
397
- }
398
- }, TIMEOUT);
399
-
400
- console.log(`Response: ${response.status} ${response.statusText}`);
401
- expect(response.status).toBe(200);
402
-
403
- const data = await response.json();
404
- expect(data.name).toBe(pkg.name);
405
- expect(data.version).toBe(version);
406
- expect(data.dist.tarball).toBeDefined();
407
-
408
- console.log(`Successfully fetched manifest for ${pkg.name}@${version}`);
409
- });
410
-
411
- it('should fetch tarball', async () => {
412
- console.log(`Fetching tarball for ${pkg.name}@${Object.keys(pkg.versions)[0]}`);
413
- const version = Object.keys(pkg.versions)[0];
414
- const versionData = pkg.versions[version];
415
- const tarballUrl = versionData.dist.tarball;
416
-
417
- const response = await mockFetch(tarballUrl, {
418
- headers: {
419
- 'Authorization': `Bearer ${AUTH_TOKEN}`
420
- }
421
- }, TIMEOUT);
422
-
423
- console.log(`Response: ${response.status} ${response.statusText}`);
424
- expect(response.status).toBe(200);
425
- expect(response.headers.get('content-type')).toBe('application/octet-stream');
426
-
427
- console.log(`Successfully fetched tarball for ${pkg.name}@${version}`);
428
- });
429
- });
430
- }
431
-
432
- describe('Token validation tests', () => {
433
- // Mock fetch for tokens
434
- const mockTokenFetch = (url, options = {}, timeout = TIMEOUT) => {
435
- console.log(`Fetching: ${url}`);
436
-
437
- if (url.includes('/-/npm/v1/tokens')) {
438
- // Handle token routes
439
- const method = options.method || 'GET';
440
-
441
- if (method === 'POST') {
442
- // Create token
443
- const body = JSON.parse(options.body || '{}');
444
-
445
- // Special character validation
446
- if (body.uuid && ['~', '!', '*', '^', '&'].some(char => body.uuid.startsWith(char))) {
447
- console.log(`Mocking invalid UUID response`);
448
- return Promise.resolve({
449
- status: 400,
450
- statusText: 'Bad Request',
451
- json: () => Promise.resolve({
452
- error: 'Invalid uuid - uuids can not start with special characters (ex. - ~ ! * ^ &)'
453
- }),
454
- headers: new Map([['content-type', 'application/json']])
455
- });
456
- }
457
-
458
- // User access validation
459
- const authHeader = options.headers?.['Authorization'] || '';
460
- const token = authHeader.replace('Bearer ', '');
461
-
462
- // If trying to modify another user's token without admin privileges
463
- if (body.uuid !== 'admin' && token !== AUTH_TOKEN) {
464
- console.log(`Mocking unauthorized response`);
465
- return Promise.resolve({
466
- status: 401,
467
- statusText: 'Unauthorized',
468
- json: () => Promise.resolve({ error: 'Unauthorized' }),
469
- headers: new Map([['content-type', 'application/json']])
470
- });
471
- }
472
-
473
- console.log(`Mocking token creation response`);
474
- return Promise.resolve({
475
- status: 200,
476
- statusText: 'OK',
477
- json: () => Promise.resolve({ success: true }),
478
- headers: new Map([['content-type', 'application/json']])
479
- });
480
- }
481
-
482
- if (method === 'PUT') {
483
- // Update token
484
- const body = JSON.parse(options.body || '{}');
485
-
486
- // Special character validation
487
- if (body.uuid && ['~', '!', '*', '^', '&'].some(char => body.uuid.startsWith(char))) {
488
- console.log(`Mocking invalid UUID response`);
489
- return Promise.resolve({
490
- status: 400,
491
- statusText: 'Bad Request',
492
- json: () => Promise.resolve({
493
- error: 'Invalid uuid - uuids can not start with special characters (ex. - ~ ! * ^ &)'
494
- }),
495
- headers: new Map([['content-type', 'application/json']])
496
- });
497
- }
498
-
499
- // User access validation
500
- const authHeader = options.headers?.['Authorization'] || '';
501
- const token = authHeader.replace('Bearer ', '');
502
-
503
- // If trying to modify another user's token without admin privileges
504
- if (body.uuid !== 'admin' && token !== AUTH_TOKEN) {
505
- console.log(`Mocking unauthorized response`);
506
- return Promise.resolve({
507
- status: 401,
508
- statusText: 'Unauthorized',
509
- json: () => Promise.resolve({ error: 'Unauthorized' }),
510
- headers: new Map([['content-type', 'application/json']])
511
- });
512
- }
513
-
514
- console.log(`Mocking token update response`);
515
- return Promise.resolve({
516
- status: 200,
517
- statusText: 'OK',
518
- json: () => Promise.resolve({ success: true }),
519
- headers: new Map([['content-type', 'application/json']])
520
- });
521
- }
522
- }
523
-
524
- // Fall back to real fetch for any unhandled routes
525
- return mockFetch(url, options, timeout);
526
- };
527
-
528
- it('should reject tokens with invalid UUIDs', async () => {
529
- console.log('Testing token creation with invalid UUID');
530
- const url = `${BASE_URL}/-/npm/v1/tokens`;
531
-
532
- const tokenData = {
533
- token: 'test-token',
534
- uuid: '~invalidUuid', // UUID with special character
535
- scope: []
536
- };
537
-
538
- const response = await mockTokenFetch(url, {
539
- method: 'POST',
540
- headers: {
541
- 'Authorization': `Bearer ${AUTH_TOKEN}`,
542
- 'Content-Type': 'application/json'
543
- },
544
- body: JSON.stringify(tokenData)
545
- });
546
-
547
- console.log(`Response: ${response.status} ${response.statusText}`);
548
- expect(response.status).toBe(400);
549
-
550
- const data = await response.json();
551
- expect(data.error).toContain('Invalid uuid');
552
- console.log('Successfully verified invalid UUID rejection');
553
- });
554
-
555
- it('should allow token creation with valid UUID', async () => {
556
- console.log('Testing token creation with valid UUID');
557
- const url = `${BASE_URL}/-/npm/v1/tokens`;
558
-
559
- const tokenData = {
560
- token: 'test-valid-token',
561
- uuid: 'validUuid', // Valid UUID
562
- scope: []
563
- };
564
-
565
- const response = await mockTokenFetch(url, {
566
- method: 'POST',
567
- headers: {
568
- 'Authorization': `Bearer ${AUTH_TOKEN}`,
569
- 'Content-Type': 'application/json'
570
- },
571
- body: JSON.stringify(tokenData)
572
- });
573
-
574
- console.log(`Response: ${response.status} ${response.statusText}`);
575
- expect(response.status).toBe(200);
576
-
577
- const data = await response.json();
578
- expect(data.success).toBe(true);
579
- console.log('Successfully verified valid token creation');
580
- });
581
-
582
- it('should reject token updates with invalid UUIDs', async () => {
583
- console.log('Testing token update with invalid UUID');
584
- const url = `${BASE_URL}/-/npm/v1/tokens/existing-token`;
585
-
586
- const tokenData = {
587
- uuid: '*invalidUuid', // UUID with special character
588
- scope: []
589
- };
590
-
591
- const response = await mockTokenFetch(url, {
592
- method: 'PUT',
593
- headers: {
594
- 'Authorization': `Bearer ${AUTH_TOKEN}`,
595
- 'Content-Type': 'application/json'
596
- },
597
- body: JSON.stringify(tokenData)
598
- });
599
-
600
- console.log(`Response: ${response.status} ${response.statusText}`);
601
- expect(response.status).toBe(400);
602
-
603
- const data = await response.json();
604
- expect(data.error).toContain('Invalid uuid');
605
- console.log('Successfully verified invalid UUID rejection in update');
606
- });
607
-
608
- it('should reject unauthorized token modifications', async () => {
609
- console.log('Testing token creation with insufficient permissions');
610
- const url = `${BASE_URL}/-/npm/v1/tokens`;
611
-
612
- const tokenData = {
613
- token: 'another-user-token',
614
- uuid: 'another-user', // Not the current user
615
- scope: []
616
- };
617
-
618
- // Using a non-admin token
619
- const response = await mockTokenFetch(url, {
620
- method: 'POST',
621
- headers: {
622
- 'Authorization': 'Bearer non-admin-token', // Not the admin token
623
- 'Content-Type': 'application/json'
624
- },
625
- body: JSON.stringify(tokenData)
626
- });
627
-
628
- console.log(`Response: ${response.status} ${response.statusText}`);
629
- expect(response.status).toBe(401);
630
-
631
- const data = await response.json();
632
- expect(data.error).toBe('Unauthorized');
633
- console.log('Successfully verified unauthorized token modification rejection');
634
- });
635
-
636
- it('should allow admins to modify any user token', async () => {
637
- console.log('Testing admin modification of another user token');
638
- const url = `${BASE_URL}/-/npm/v1/tokens/another-user-token`;
639
-
640
- const tokenData = {
641
- uuid: 'another-user', // Not the admin user
642
- scope: []
643
- };
644
-
645
- // Using the admin token
646
- const response = await mockTokenFetch(url, {
647
- method: 'PUT',
648
- headers: {
649
- 'Authorization': `Bearer ${AUTH_TOKEN}`, // Admin token
650
- 'Content-Type': 'application/json'
651
- },
652
- body: JSON.stringify(tokenData)
653
- });
654
-
655
- console.log(`Response: ${response.status} ${response.statusText}`);
656
- expect(response.status).toBe(200);
657
-
658
- const data = await response.json();
659
- expect(data.success).toBe(true);
660
- console.log('Successfully verified admin token modification privilege');
661
- });
662
- })
663
-
664
- describe('dist-tag commands', () => {
665
- it('should list dist-tags for a package', async () => {
666
- console.log('Testing dist-tag list');
667
- const response = await mockFetch(`${BASE_URL}/-/package/lodash/dist-tags`);
668
- expect(response.status).toBe(200);
669
-
670
- const data = await response.json();
671
- console.log('Dist-tags:', data);
672
-
673
- expect(data).toHaveProperty('latest', '4.17.21');
674
- expect(data).toHaveProperty('beta', '4.18.0-beta.1');
675
- console.log('Successfully verified dist-tag list');
676
- });
677
-
678
- it('should add a dist-tag to a package', async () => {
679
- console.log('Testing dist-tag add');
680
- const response = await mockFetch(`${BASE_URL}/-/package/lodash/dist-tags/canary`, {
681
- method: 'PUT',
682
- body: '4.18.0-beta.1',
683
- });
684
- expect(response.status).toBe(201);
685
-
686
- const data = await response.json();
687
- console.log('Updated dist-tags:', data);
688
-
689
- expect(data).toHaveProperty('latest', '4.17.21');
690
- expect(data).toHaveProperty('beta', '4.18.0-beta.1');
691
- expect(data).toHaveProperty('canary', '4.18.0-beta.1');
692
- console.log('Successfully verified dist-tag add');
693
- });
694
-
695
- it('should delete a dist-tag from a package', async () => {
696
- console.log('Testing dist-tag remove');
697
- const response = await mockFetch(`${BASE_URL}/-/package/lodash/dist-tags/beta`, {
698
- method: 'DELETE',
699
- });
700
- expect(response.status).toBe(200);
701
-
702
- const data = await response.json();
703
- console.log('Updated dist-tags after delete:', data);
704
-
705
- expect(data).toHaveProperty('latest', '4.17.21');
706
- expect(data).not.toHaveProperty('beta');
707
- console.log('Successfully verified dist-tag remove');
708
- });
709
-
710
- it('should not delete the latest tag', async () => {
711
- console.log('Testing dist-tag remove for latest tag');
712
- const response = await mockFetch(`${BASE_URL}/-/package/lodash/dist-tags/latest`, {
713
- method: 'DELETE',
714
- });
715
- expect(response.status).toBe(400);
716
-
717
- const data = await response.json();
718
- console.log('Response for deleting latest tag:', data);
719
-
720
- expect(data).toHaveProperty('error', 'Cannot delete the "latest" tag');
721
- console.log('Successfully verified protection of latest tag');
722
- });
723
- })
724
- })
725
-
726
- function createMockDb() {
727
- // ... existing code ...
728
- }
729
-
730
- const createMockDrizzle = (mockDb) => {
731
- return mockDb;
732
- };
733
-
734
- // Create a mock bucket for R2
735
- function createMockBucket() {
736
- return {
737
- put: async () => {},
738
- get: async (key) => {
739
- const parts = key.split('/');
740
- const pkgName = parts[0];
741
- const tarball = parts[1];
742
- const versionMatch = tarball.match(new RegExp(`${pkgName}-(.*)\\.tgz`));
743
-
744
- if (versionMatch && TEST_PACKAGES[pkgName] && TEST_PACKAGES[pkgName].versions[versionMatch[1]]) {
745
- return {
746
- body: new ReadableStream({
747
- start(controller) {
748
- controller.enqueue(new TextEncoder().encode('mock-tarball-content'));
749
- controller.close();
750
- }
751
- })
752
- };
753
- }
754
- return null;
755
- }
756
- };
757
- }
758
-
759
- // Update the app setup function
760
- function createApp() {
761
- const testDb = createMockDb();
762
- const mockBucket = createMockBucket();
763
-
764
- const app = new Hono();
765
-
766
- // Set up middleware and context
767
- app.use('*', async (c, next) => {
768
- c.env = {
769
- DB: testDb,
770
- BUCKET: mockBucket
771
- };
772
-
773
- // Mock DB operations
774
- c.db = {
775
- getPackage: async (name) => {
776
- const pkg = TEST_PACKAGES[name];
777
- if (pkg) {
778
- return {
779
- name,
780
- tags: pkg['dist-tags']
781
- };
782
- }
783
- return null;
784
- },
785
-
786
- getVersion: async (spec) => {
787
- const [name, version] = spec.split('@');
788
- const pkg = TEST_PACKAGES[name];
789
- if (pkg && pkg.versions && pkg.versions[version]) {
790
- return {
791
- spec,
792
- manifest: pkg.versions[version],
793
- published_at: new Date().toISOString()
794
- };
795
- }
796
- return null;
797
- },
798
-
799
- getToken: async (token) => {
800
- if (token === AUTH_TOKEN) {
801
- return {
802
- token,
803
- uuid: 'admin',
804
- scope: [
805
- {
806
- values: ['*'],
807
- types: { pkg: { read: true, write: true } }
808
- },
809
- {
810
- values: ['*'],
811
- types: { user: { read: true, write: true } }
812
- }
813
- ]
814
- };
815
- }
816
- return null;
817
- },
818
-
819
- upsertPackage: async () => {},
820
- upsertVersion: async () => {},
821
- upsertToken: async () => {},
822
- searchPackages: async () => []
823
- };
824
-
825
- await next();
826
- });
827
-
828
- // Add routes
829
- app.get('/-/ping', (c) => c.json({ ok: true }));
830
-
831
- // Package routes - simple implementations for test only
832
- app.get('/:pkg', async (c) => {
833
- const pkg = c.req.param('pkg');
834
- console.log(`Packument request for: ${pkg}`);
835
-
836
- if (TEST_PACKAGES[pkg]) {
837
- const packageData = TEST_PACKAGES[pkg];
838
- const versions = {};
839
-
840
- Object.entries(packageData.versions).forEach(([version, versionData]) => {
841
- versions[version] = versionData;
842
- });
843
-
844
- return c.json({
845
- name: packageData.name,
846
- 'dist-tags': packageData['dist-tags'],
847
- versions
848
- });
849
- }
850
- return c.json({ error: 'Package not found' }, 404);
851
- });
852
-
853
- app.get('/:pkg/:version', async (c) => {
854
- const pkg = c.req.param('pkg');
855
- let version = c.req.param('version');
856
-
857
- console.log(`Manifest request for: ${pkg}@${version}`);
858
-
859
- if (TEST_PACKAGES[pkg]) {
860
- const packageData = TEST_PACKAGES[pkg];
861
-
862
- // Handle dist-tags
863
- if (packageData['dist-tags'][version]) {
864
- version = packageData['dist-tags'][version];
865
- }
866
-
867
- if (packageData.versions[version]) {
868
- return c.json(packageData.versions[version]);
869
- }
870
- }
871
- return c.json({ error: 'Version not found' }, 404);
872
- });
873
-
874
- app.get('/:pkg/-/:tarball', async (c) => {
875
- const pkg = c.req.param('pkg');
876
- const tarball = c.req.param('tarball');
877
-
878
- console.log(`Tarball request for: ${pkg}/${tarball}`);
879
-
880
- const versionMatch = tarball.match(new RegExp(`${pkg}-(.*)\\.tgz`));
881
- if (versionMatch && TEST_PACKAGES[pkg] && TEST_PACKAGES[pkg].versions[versionMatch[1]]) {
882
- // Return a mock tarball
883
- return new Response('mock-tarball-content', {
884
- headers: {
885
- 'Content-Type': 'application/octet-stream',
886
- 'Cache-Control': 'public, max-age=31536000'
887
- }
888
- });
889
- }
890
- return c.json({ error: 'Tarball not found' }, 404);
891
- });
892
-
893
- app.get('/-/package/:pkg/dist-tags', getPackageDistTags);
894
- app.get('/-/package/:pkg/dist-tags/:tag', getPackageDistTags);
895
- app.put('/-/package/:pkg/dist-tags/:tag', putPackageDistTag);
896
- app.delete('/-/package/:pkg/dist-tags/:tag', deletePackageDistTag);
897
-
898
- app.get('/-/package/:scope%2f:pkg/dist-tags', getPackageDistTags);
899
- app.get('/-/package/:scope%2f:pkg/dist-tags/:tag', getPackageDistTags);
900
- app.put('/-/package/:scope%2f:pkg/dist-tags/:tag', putPackageDistTag);
901
- app.delete('/-/package/:scope%2f:pkg/dist-tags/:tag', deletePackageDistTag);
902
-
903
- return app;
904
- }