feat: update dependencies and improve request handling

This commit is contained in:
xixu-me committed 2026-03-09 17:37:27 +08:00
1 parent 3957f78d9d
commit 929337c494
16 files changed
+185 -563

No files matched your search

+1 -1
View File
@@ -104,7 +104,7 @@ describe('Security Features', () => {
expect(response.status).not.toBe(500);
}
}
}, 45000);
}, 30000);
it('should reject extremely long paths', async () => {
const longPath = `/gh/${'a'.repeat(3000)}`;
-19
View File
@@ -92,13 +92,6 @@ describe('Xget Core Functionality', () => {
// Should attempt to proxy to conda
expect(response.status).not.toBe(400);
});
it('should not treat nested /v2/ path segments as container registry requests', async () => {
const testUrl = 'https://example.com/gh/microsoft/vscode/releases/download/v2/file.tar.gz';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
expect(response.status).not.toBe(400);
});
});
describe('HTTP Method Validation', () => {
@@ -133,18 +126,6 @@ describe('Xget Core Functionality', () => {
expect(response.status).toBe(405);
});
it('should reject AI-like POST requests outside /ip providers', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: 'test' })
});
expect(response.status).toBe(405);
});
});
describe('Git Protocol Support', () => {
+8 -6
View File
@@ -26,7 +26,7 @@ describe('Integration Tests', () => {
const testUrl = 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
expect([200, 301, 302, 404, 408]).toContain(response.status);
expect([200, 301, 302, 404]).toContain(response.status);
}, 60000);
it('should proxy GitLab file requests correctly', async () => {
@@ -152,16 +152,18 @@ describe('Integration Tests', () => {
it('should include performance metrics in all responses', async () => {
const testUrls = [
'https://example.com/gh/test/repo/file.txt',
'https://example.com/gl/test/repo/file.txt',
'https://example.com/hf/test/model/config.json',
'https://example.com/npm/test-package',
'https://example.com/pypi/simple/test/'
'https://example.com/pypi/simple/test/',
'https://example.com/conda/pkgs/main/test.json'
];
const responses = await Promise.all(testUrls.map(url => SELF.fetch(url, { method: 'HEAD' })));
for (const response of responses) {
for (const url of testUrls) {
const response = await SELF.fetch(url, { method: 'HEAD' });
expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();
}
}, 20000);
}, 10000);
});
describe('Content Type Handling', () => {
+36
View File
@@ -235,6 +235,42 @@ describe('Container Registry Support', () => {
});
});
describe('Container Registry Platform Support', () => {
const containerRegistries = [
{ name: 'Docker Hub', prefix: 'cr/docker', expectedStatus: [200, 301, 302, 401, 404, 429] },
{ name: 'Quay.io', prefix: 'cr/quay', expectedStatus: [200, 301, 302, 401, 404, 429] },
{
name: 'Google Container Registry',
prefix: 'cr/gcr',
expectedStatus: [200, 301, 302, 401, 404, 429]
},
{
name: 'Microsoft Container Registry',
prefix: 'cr/mcr',
expectedStatus: [200, 301, 302, 401, 404, 429]
},
{
name: 'GitHub Container Registry',
prefix: 'cr/ghcr',
expectedStatus: [200, 301, 302, 401, 404, 429]
},
{
name: 'Amazon ECR Public',
prefix: 'cr/ecr',
expectedStatus: [200, 301, 302, 401, 404, 429]
}
];
containerRegistries.forEach(({ name, prefix, expectedStatus }) => {
it(`should support ${name} registry`, async () => {
const testUrl = `https://example.com/${prefix}/v2/test/image/manifests/latest`;
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
expect(expectedStatus).toContain(response.status);
}, 10000);
});
});
describe('Docker Hub Specific Tests', () => {
it('should handle Docker Hub official images (single-name images)', async () => {
// Official images like nginx, redis are stored as library/nginx in Docker Hub
-138
View File
@@ -1,138 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import worker from '../../src/index.js';
/** @type {ExecutionContext} */
const executionContext = {
waitUntil() {},
passThroughOnException() {}
};
describe('CORS and Proxy Request Options', () => {
beforeEach(() => {
vi.stubGlobal('caches', {
default: {
match: vi.fn(async () => null),
put: vi.fn(async () => undefined)
}
});
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it('does not send a synthetic Origin header upstream', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('ok', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
})
);
const response = await worker.fetch(
new Request('https://example.com/gh/test/repo/index.html'),
{},
executionContext
);
expect(response.status).toBe(200);
const upstreamHeaders = new Headers(fetchSpy.mock.calls[0][1]?.headers);
expect(upstreamHeaders.has('Origin')).toBe(false);
});
it('does not enable Cloudflare minification for proxied responses', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('<html>ok</html>', {
status: 200,
headers: { 'Content-Type': 'text/html' }
})
);
await worker.fetch(
new Request('https://example.com/gh/test/repo/index.html'),
{},
executionContext
);
const fetchOptions = /** @type {RequestInit & { cf?: Record<string, unknown> }} */ (
fetchSpy.mock.calls[0][1] || {}
);
expect(fetchOptions.cf).toEqual(
expect.objectContaining({
http3: true,
cacheEverything: true,
preconnect: true
})
);
expect(fetchOptions.cf).not.toHaveProperty('minify');
});
it('responds to preflight requests for allowed origins', async () => {
const response = await worker.fetch(
new Request('https://example.com/gh/test/repo', {
method: 'OPTIONS',
headers: {
Origin: 'https://app.example.com',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'X-Custom-Header'
}
}),
{
ALLOWED_ORIGINS: 'https://app.example.com'
},
executionContext
);
expect(response.status).toBe(204);
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://app.example.com');
expect(response.headers.get('Access-Control-Allow-Methods')).toContain('GET');
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('X-Custom-Header');
});
it('rejects preflight requests for disallowed origins', async () => {
const response = await worker.fetch(
new Request('https://example.com/gh/test/repo', {
method: 'OPTIONS',
headers: {
Origin: 'https://evil.example.com',
'Access-Control-Request-Method': 'GET'
}
}),
{
ALLOWED_ORIGINS: 'https://app.example.com'
},
executionContext
);
expect(response.status).toBe(403);
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
});
it('adds CORS headers to normal responses for allowed origins', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('ok', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
})
);
const response = await worker.fetch(
new Request('https://example.com/gh/test/repo/file.txt', {
headers: {
Origin: 'https://app.example.com'
}
}),
{
ALLOWED_ORIGINS: 'https://app.example.com'
},
executionContext
);
expect(response.status).toBe(200);
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://app.example.com');
expect(response.headers.get('Vary')).toContain('Origin');
});
});
-15
View File
@@ -1,15 +0,0 @@
import { createRequire } from 'node:module';
import { describe, expect, it } from 'vitest';
describe('Package manifest', () => {
it('does not depend on itself', () => {
const require = createRequire(import.meta.url);
const packageJson = require('../../package.json');
const { dependencies } = packageJson;
const typedDependencies = /** @type {Record<string, string> | undefined} */ (dependencies);
expect(packageJson.name).toBe('xget');
expect(typedDependencies?.xget).toBeUndefined();
});
});
-4
View File
@@ -330,10 +330,6 @@ describe('Platform Configuration', () => {
});
});
it('should use the correct Amazon ECR Public base URL', () => {
expect(PLATFORMS['cr-ecr']).toBe('https://public.ecr.aws');
});
it('should transform all container registry paths correctly', () => {
const containerRegistries = [
'cr-quay',
-135
View File
@@ -1,135 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import worker from '../../src/index.js';
import { CONFIG } from '../../src/config/index.js';
import { isAIInferenceRequest } from '../../src/protocols/ai.js';
import { handleDockerAuth } from '../../src/protocols/docker.js';
import { isDockerRequest } from '../../src/utils/validation.js';
/** @type {ExecutionContext} */
const executionContext = {
waitUntil() {},
passThroughOnException() {}
};
describe('Protocol Detection', () => {
it('only treats /ip-prefixed paths as AI inference requests', () => {
const request = new Request('https://example.com/gh/user/repo/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}'
});
const url = new URL(request.url);
expect(isAIInferenceRequest(request, url)).toBe(false);
});
it('does not treat nested /v2/ segments in regular paths as Docker requests', () => {
const request = new Request(
'https://example.com/gh/user/repo/releases/download/v2/file.tar.gz'
);
const url = new URL(request.url);
expect(isDockerRequest(request, url)).toBe(false);
});
});
describe('Docker Authentication', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('normalizes Docker Hub official image scopes during auth proxying', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {
const url = String(input);
if (url === 'https://registry-1.docker.io/v2/') {
return new Response('', {
status: 401,
headers: {
'WWW-Authenticate':
'Bearer realm="https://auth.docker.io/token",service="registry.docker.io"'
}
});
}
return new Response(JSON.stringify({ token: 'token' }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
});
const request = new Request(
'https://example.com/cr/docker/v2/auth?scope=repository:cr/docker/nginx:pull&service=Xget'
);
const response = await handleDockerAuth(request, new URL(request.url), CONFIG);
expect(response.status).toBe(200);
expect(String(fetchSpy.mock.calls[1][0])).toContain(
'scope=repository%3Alibrary%2Fnginx%3Apull'
);
});
it('routes platform-prefixed auth endpoints without duplicating /v2', async () => {
/** @type {string[]} */
const upstreamCalls = [];
vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {
upstreamCalls.push(String(input));
if (String(input) === 'https://ghcr.io/v2/') {
return new Response('', {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer realm="https://ghcr.io/token",service="ghcr.io"'
}
});
}
return new Response(JSON.stringify({ token: 'token' }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
});
const request = new Request('https://example.com/cr/ghcr/v2/auth?service=Xget');
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(upstreamCalls[0]).toBe('https://ghcr.io/v2/');
});
});
describe('Protocol Header Configuration', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('does not send Git user-agent for AI inference requests', async () => {
/** @type {{ url: string, userAgent: string | null }[]} */
const observed = [];
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
const headers = new Headers(init?.headers);
observed.push({
url: String(input),
userAgent: headers.get('User-Agent')
});
return new Response('{}', {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
});
const request = new Request('https://example.com/ip/openai/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}'
});
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(observed[0]).toEqual({
url: 'https://api.openai.com/v1/chat/completions',
userAgent: 'Xget-AI-Proxy/1.0'
});
});
});