@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.
@@ -1,141 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
-
3
- /**
4
- * This test demonstrates the waitUntil behavior in Cloudflare Workers
5
- * in the most simplified form possible, avoiding the issues with immediate execution.
6
- */
7
- describe('Cloudflare waitUntil Behavior', () => {
8
- // Simple test that shows the waitUntil pattern
9
- it('should demonstrate the waitUntil pattern correctly', async () => {
10
- let mainComplete = false;
11
- let bgTaskExecuted = false;
12
-
13
- // This mimics how Cloudflare Workers actually handles waitUntil
14
- // It stores the promises but doesn't block on them
15
- function simulateCloudflareWorker(handler) {
16
- const bgTasks = [];
17
- const executionCtx = {
18
- waitUntil: (promise) => {
19
- bgTasks.push(promise);
20
- }
21
- };
22
-
23
- return async () => {
24
- // Run the main handler first to completion
25
- const response = await handler(executionCtx);
26
-
27
- // Mark main function as complete
28
- mainComplete = true;
29
-
30
- // Then run background tasks
31
- for (const task of bgTasks) {
32
- await task;
33
- }
34
-
35
- return response;
36
- };
37
- }
38
-
39
- // Sample worker code using waitUntil
40
- async function workerHandler(ctx) {
41
- // Return response immediately
42
- const response = { message: "Fast response!" };
43
-
44
- // Queue background work
45
- ctx.waitUntil((async () => {
46
- // Simulate slow background work
47
- await new Promise(resolve => setTimeout(resolve, 100));
48
- bgTaskExecuted = true;
49
- })());
50
-
51
- return response;
52
- }
53
-
54
- // Wrap the handler to simulate worker runtime
55
- const simulatedWorker = simulateCloudflareWorker(workerHandler);
56
-
57
- // Execute the simulated worker
58
- const response = await simulatedWorker();
59
-
60
- // Verify the response returned before background work completed
61
- expect(response).toEqual({ message: "Fast response!" });
62
-
63
- // Verify main function completed before background task
64
- expect(mainComplete).toBe(true);
65
-
66
- // Verify background task eventually completed
67
- expect(bgTaskExecuted).toBe(true);
68
- });
69
-
70
- // Test with stale cache pattern
71
- it('should demonstrate the stale-while-revalidate pattern', async () => {
72
- const cache = {
73
- data: { key: 'test', value: 'stale-data', timestamp: Date.now() - 3600000 }, // 1 hour old
74
- get: vi.fn(function(key) { return this.data; }),
75
- set: vi.fn(function(key, value) { this.data = value; })
76
- };
77
-
78
- const fetchMock = vi.fn().mockResolvedValue('fresh-data');
79
-
80
- let cacheRefreshed = false;
81
-
82
- // Simplified runtime simulation
83
- function simulateCloudflareWorker(handler) {
84
- const bgTasks = [];
85
- const executionCtx = {
86
- waitUntil: (promise) => {
87
- bgTasks.push(promise);
88
- }
89
- };
90
-
91
- return async () => {
92
- // Run the main handler to completion first
93
- const response = await handler(executionCtx);
94
-
95
- // Then execute background tasks
96
- for (const task of bgTasks) {
97
- await task;
98
- }
99
-
100
- return response;
101
- };
102
- }
103
-
104
- // Worker handler using stale-while-revalidate pattern
105
- async function cacheHandler(ctx) {
106
- // Get from cache
107
- const cached = cache.get('test-key');
108
-
109
- // Check if stale
110
- const isStale = Date.now() - cached.timestamp > 5 * 60 * 1000; // 5 minutes
111
-
112
- if (isStale) {
113
- // Background refresh
114
- ctx.waitUntil((async () => {
115
- const fresh = await fetchMock('test-key');
116
- cache.set('test-key', {
117
- key: 'test',
118
- value: fresh,
119
- timestamp: Date.now()
120
- });
121
- cacheRefreshed = true;
122
- })());
123
- }
124
-
125
- // Return cached data immediately
126
- return cached.value;
127
- }
128
-
129
- // Wrap the handler
130
- const simulatedWorker = simulateCloudflareWorker(cacheHandler);
131
-
132
- // Execute - we should get stale data first, then background refresh
133
- const result = await simulatedWorker();
134
-
135
- // Verify
136
- expect(result).toBe('stale-data'); // Got stale data immediately
137
- expect(fetchMock).toHaveBeenCalledWith('test-key'); // Fetch happened in background
138
- expect(cacheRefreshed).toBe(true); // Cache was eventually refreshed
139
- expect(cache.set).toHaveBeenCalled(); // Cache was updated with fresh data
140
- });
141
- });
package/test/db.test.js DELETED
@@ -1,447 +0,0 @@
1
- import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
2
- import { drizzle } from 'drizzle-orm/d1';
3
- import { createDatabaseOperations } from '../src/db/client';
4
-
5
- describe('Database Operations', () => {
6
- let dbOps;
7
- let mockData;
8
-
9
- beforeAll(() => {
10
- // Create a mock D1 database that implements the required interface
11
- const testDb = {
12
- prepare: () => ({
13
- bind: () => ({
14
- first: () => Promise.resolve(null),
15
- all: () => Promise.resolve([]),
16
- raw: () => Promise.resolve([]),
17
- run: () => Promise.resolve({ success: true }),
18
- values: () => Promise.resolve([]),
19
- }),
20
- }),
21
- batch: (statements) => Promise.resolve(statements.map(() => ({ success: true }))),
22
- exec: () => Promise.resolve({ success: true }),
23
- };
24
-
25
- // Create database operations with the mock
26
- dbOps = createDatabaseOperations(drizzle(testDb));
27
- });
28
-
29
- beforeEach(() => {
30
- // Reset mock data before each test
31
- mockData = {
32
- packages: new Map(),
33
- tokens: new Map(),
34
- versions: new Map(),
35
- };
36
-
37
- // Override database operations with mock implementations
38
- dbOps.getPackage = async (name) => {
39
- return mockData.packages.get(name) || null;
40
- };
41
-
42
- dbOps.upsertPackage = async (name, tags) => {
43
- const pkg = { name, tags };
44
- mockData.packages.set(name, pkg);
45
- return pkg;
46
- };
47
-
48
- dbOps.getToken = async (token) => {
49
- return mockData.tokens.get(token) || null;
50
- };
51
-
52
- // Implement validation for upsertToken
53
- dbOps.upsertToken = async (token, uuid, scope, authToken) => {
54
- // Validate UUID doesn't start with special characters
55
- const specialChars = ['~', '!', '*', '^', '&'];
56
- if (uuid && specialChars.some(char => uuid.startsWith(char))) {
57
- throw new Error('Invalid uuid - uuids can not start with special characters (ex. - ~ ! * ^ &)');
58
- }
59
-
60
- // If authToken is provided, validate access permissions
61
- if (authToken) {
62
- const authUser = mockData.tokens.get(authToken);
63
- if (!authUser) {
64
- throw new Error('Unauthorized');
65
- }
66
-
67
- // Allow users to modify their own tokens
68
- if (authUser.uuid !== uuid) {
69
- // Check if has global user permissions
70
- let hasPermission = false;
71
- if (Array.isArray(authUser.scope)) {
72
- for (const s of authUser.scope) {
73
- if (s.types?.user &&
74
- s.values.includes('*') &&
75
- s.types.user.write) {
76
- hasPermission = true;
77
- break;
78
- }
79
- }
80
- }
81
-
82
- if (!hasPermission) {
83
- throw new Error('Unauthorized');
84
- }
85
- }
86
- }
87
-
88
- const tokenData = { token, uuid, scope };
89
- mockData.tokens.set(token, tokenData);
90
- return tokenData;
91
- };
92
-
93
- dbOps.deleteToken = async (token, authToken) => {
94
- if (authToken) {
95
- const tokenData = mockData.tokens.get(token);
96
- const authUser = mockData.tokens.get(authToken);
97
-
98
- if (!authUser) {
99
- throw new Error('Unauthorized');
100
- }
101
-
102
- if (tokenData && authUser.uuid !== tokenData.uuid) {
103
- // Check if has global user permissions
104
- let hasPermission = false;
105
- if (Array.isArray(authUser.scope)) {
106
- for (const s of authUser.scope) {
107
- if (s.types?.user &&
108
- s.values.includes('*') &&
109
- s.types.user.write) {
110
- hasPermission = true;
111
- break;
112
- }
113
- }
114
- }
115
-
116
- if (!hasPermission) {
117
- throw new Error('Unauthorized');
118
- }
119
- }
120
- }
121
-
122
- mockData.tokens.delete(token);
123
- return true;
124
- };
125
-
126
- dbOps.getVersion = async (spec) => {
127
- return mockData.versions.get(spec) || null;
128
- };
129
-
130
- dbOps.upsertVersion = async (spec, manifest, published_at) => {
131
- const version = { spec, manifest, published_at };
132
- mockData.versions.set(spec, version);
133
- return version;
134
- };
135
-
136
- dbOps.searchPackages = async (query, scope) => {
137
- const results = Array.from(mockData.packages.values()).filter(pkg => {
138
- if (scope) {
139
- return pkg.name.startsWith(scope + '/') && pkg.name.toLowerCase().includes(query.toLowerCase());
140
- }
141
- return pkg.name.toLowerCase().includes(query.toLowerCase());
142
- });
143
- return results;
144
- };
145
- });
146
-
147
- describe('Packages', () => {
148
- const testPackage = {
149
- name: 'test-package',
150
- tags: { latest: '1.0.0' },
151
- };
152
-
153
- it('should insert and retrieve a package', async () => {
154
- await dbOps.upsertPackage(testPackage.name, testPackage.tags);
155
- const retrieved = await dbOps.getPackage(testPackage.name);
156
- expect(retrieved).toEqual({
157
- name: testPackage.name,
158
- tags: testPackage.tags,
159
- });
160
- });
161
-
162
- it('should update an existing package', async () => {
163
- const updatedTags = { latest: '2.0.0' };
164
- await dbOps.upsertPackage(testPackage.name, updatedTags);
165
- const retrieved = await dbOps.getPackage(testPackage.name);
166
- expect(retrieved).toEqual({
167
- name: testPackage.name,
168
- tags: updatedTags,
169
- });
170
- });
171
- });
172
-
173
- describe('Tokens', () => {
174
- const testToken = {
175
- token: 'test-token',
176
- uuid: 'test-uuid',
177
- scope: [
178
- {
179
- values: ['*'],
180
- types: {
181
- pkg: { read: true, write: true },
182
- },
183
- },
184
- ],
185
- };
186
-
187
- it('should insert and retrieve a token', async () => {
188
- await dbOps.upsertToken(testToken.token, testToken.uuid, testToken.scope);
189
- const retrieved = await dbOps.getToken(testToken.token);
190
- expect(retrieved).toEqual({
191
- token: testToken.token,
192
- uuid: testToken.uuid,
193
- scope: testToken.scope,
194
- });
195
- });
196
-
197
- it('should update an existing token', async () => {
198
- const updatedScope = [
199
- {
200
- values: ['@test'],
201
- types: {
202
- pkg: { read: true, write: false },
203
- },
204
- },
205
- ];
206
- await dbOps.upsertToken(testToken.token, testToken.uuid, updatedScope);
207
- const retrieved = await dbOps.getToken(testToken.token);
208
- expect(retrieved).toEqual({
209
- token: testToken.token,
210
- uuid: testToken.uuid,
211
- scope: updatedScope,
212
- });
213
- });
214
-
215
- it('should reject UUIDs with special characters', async () => {
216
- const invalidUuid = '~invalidUuid';
217
-
218
- await expect(
219
- dbOps.upsertToken('invalid-token', invalidUuid, [])
220
- ).rejects.toThrow('Invalid uuid');
221
- });
222
-
223
- it('should validate user access permissions', async () => {
224
- // Setup test tokens
225
- const adminToken = 'admin-token';
226
- const userAToken = 'user-a-token';
227
-
228
- // Setup mock tokens
229
- await dbOps.upsertToken(adminToken, 'admin', [
230
- {
231
- values: ['*'],
232
- types: { user: { read: true, write: true } }
233
- }
234
- ]);
235
-
236
- await dbOps.upsertToken(userAToken, 'user-a', [
237
- {
238
- values: ['~user-a'],
239
- types: { user: { read: true, write: true } }
240
- }
241
- ]);
242
-
243
- // Case 1: User can modify their own token
244
- await expect(
245
- dbOps.upsertToken('new-token', 'user-a', [], userAToken)
246
- ).resolves.not.toThrow();
247
-
248
- // Case 2: Admin can modify any user's token
249
- await expect(
250
- dbOps.upsertToken('other-token', 'user-b', [], adminToken)
251
- ).resolves.not.toThrow();
252
-
253
- // Case 3: User cannot modify another user's token
254
- await expect(
255
- dbOps.upsertToken('other-token', 'user-b', [], userAToken)
256
- ).rejects.toThrow('Unauthorized');
257
- });
258
-
259
- it('should validate user access permissions for token operations', async () => {
260
- // Import token functions
261
- const { postToken, putToken } = await import('../src/routes/tokens.js');
262
-
263
- // Test case 1: User trying to modify their own token (authorized)
264
- const selfContext = {
265
- req: {
266
- json: async () => ({ token: 'own-token', uuid: 'user-a', scope: [] }),
267
- param: () => 'own-token',
268
- header: () => 'Bearer auth-token'
269
- },
270
- json: (response, statusCode) => ({ response, statusCode }),
271
- db: {
272
- ...dbOps,
273
- getToken: async (token) => {
274
- if (token === 'auth-token') {
275
- return {
276
- token: 'auth-token',
277
- uuid: 'user-a',
278
- scope: []
279
- };
280
- }
281
- return null;
282
- },
283
- upsertToken: async (token, uuid, scope, authToken) => {
284
- if (authToken) {
285
- const authUser = await dbOps.getToken(authToken);
286
- if (authUser && authUser.uuid !== uuid) {
287
- throw new Error('Unauthorized');
288
- }
289
- }
290
- return true;
291
- }
292
- }
293
- };
294
-
295
- // User modifying their own token should be authorized
296
- const selfPostResult = await postToken(selfContext);
297
- expect(selfPostResult.statusCode).not.toBe(401);
298
-
299
- // Test case 2: User trying to modify another user's token without permission
300
- const unauthorizedContext = {
301
- req: {
302
- json: async () => ({ token: 'other-token', uuid: 'user-b', scope: [] }),
303
- param: () => 'other-token',
304
- header: () => 'Bearer restricted-token'
305
- },
306
- json: (response, statusCode) => ({ response, statusCode }),
307
- db: {
308
- ...dbOps,
309
- getToken: async (token) => {
310
- if (token === 'restricted-token') {
311
- return {
312
- token: 'restricted-token',
313
- uuid: 'user-a',
314
- scope: [
315
- {
316
- values: ['~user-a'],
317
- types: {
318
- user: { read: true, write: true }
319
- }
320
- }
321
- ]
322
- };
323
- }
324
- return null;
325
- },
326
- upsertToken: async (token, uuid, scope, authToken) => {
327
- if (authToken === 'restricted-token' && uuid === 'user-b') {
328
- throw new Error('Unauthorized');
329
- }
330
- return true;
331
- }
332
- }
333
- };
334
-
335
- // User without permission to modify another user's token should be unauthorized
336
- const unauthorizedPostResult = await postToken(unauthorizedContext);
337
- expect(unauthorizedPostResult.statusCode).toBe(401);
338
- expect(unauthorizedPostResult.response.error).toBe('Unauthorized');
339
-
340
- // Test case 3: Admin user with global permissions modifying another user's token
341
- const adminContext = {
342
- req: {
343
- json: async () => ({ token: 'other-token', uuid: 'user-b', scope: [] }),
344
- param: () => 'other-token',
345
- header: () => 'Bearer admin-token'
346
- },
347
- json: (response, statusCode) => ({ response, statusCode }),
348
- db: {
349
- ...dbOps,
350
- getToken: async (token) => {
351
- if (token === 'admin-token') {
352
- return {
353
- token: 'admin-token',
354
- uuid: 'admin',
355
- scope: [
356
- {
357
- values: ['*'],
358
- types: {
359
- user: { read: true, write: true }
360
- }
361
- }
362
- ]
363
- };
364
- }
365
- return null;
366
- },
367
- upsertToken: async () => true
368
- }
369
- };
370
-
371
- // Admin with proper permissions should be authorized
372
- const adminPostResult = await postToken(adminContext);
373
- expect(adminPostResult.statusCode).not.toBe(401);
374
- });
375
- });
376
-
377
- describe('Versions', () => {
378
- const testVersion = {
379
- spec: 'test-package@1.0.0',
380
- manifest: {
381
- name: 'test-package',
382
- version: '1.0.0',
383
- description: 'Test package',
384
- },
385
- published_at: '2025-03-25T15:33:21.971Z',
386
- };
387
-
388
- it('should insert and retrieve a version', async () => {
389
- await dbOps.upsertVersion(testVersion.spec, testVersion.manifest, testVersion.published_at);
390
- const retrieved = await dbOps.getVersion(testVersion.spec);
391
- expect(retrieved).toEqual({
392
- spec: testVersion.spec,
393
- manifest: testVersion.manifest,
394
- published_at: testVersion.published_at,
395
- });
396
- });
397
-
398
- it('should update an existing version', async () => {
399
- const updatedManifest = {
400
- name: 'test-package',
401
- version: '1.0.0',
402
- description: 'Updated test package',
403
- };
404
- await dbOps.upsertVersion(testVersion.spec, updatedManifest, testVersion.published_at);
405
- const retrieved = await dbOps.getVersion(testVersion.spec);
406
- expect(retrieved).toEqual({
407
- spec: testVersion.spec,
408
- manifest: updatedManifest,
409
- published_at: testVersion.published_at,
410
- });
411
- });
412
- });
413
-
414
- describe('Search Operations', () => {
415
- it('should search packages by name', async () => {
416
- const testPackages = [
417
- { name: 'test-package-1', tags: { latest: '1.0.0' } },
418
- { name: 'test-package-2', tags: { latest: '1.0.0' } },
419
- { name: 'other-package', tags: { latest: '1.0.0' } },
420
- ];
421
-
422
- for (const pkg of testPackages) {
423
- await dbOps.upsertPackage(pkg.name, pkg.tags);
424
- }
425
-
426
- const results = await dbOps.searchPackages('test');
427
- expect(results).toHaveLength(2);
428
- expect(results.map(p => p.name)).toEqual(['test-package-1', 'test-package-2']);
429
- });
430
-
431
- it('should search packages by scope', async () => {
432
- const testPackages = [
433
- { name: '@test/package-1', tags: { latest: '1.0.0' } },
434
- { name: '@test/package-2', tags: { latest: '1.0.0' } },
435
- { name: '@other/package', tags: { latest: '1.0.0' } },
436
- ];
437
-
438
- for (const pkg of testPackages) {
439
- await dbOps.upsertPackage(pkg.name, pkg.tags);
440
- }
441
-
442
- const results = await dbOps.searchPackages('package', '@test');
443
- expect(results).toHaveLength(2);
444
- expect(results.map(p => p.name)).toEqual(['@test/package-1', '@test/package-2']);
445
- });
446
- });
447
- });