Files
xget/test/index.test.js
T

274 lines
9.9 KiB
JavaScript

import { SELF } from 'cloudflare:test';
import { describe, expect, it } from 'vitest';
describe('Xget Core Functionality', () => {
describe('Basic Request Handling', () => {
it('should redirect root path to homepage', async () => {
const response = await SELF.fetch('https://example.com/', { redirect: 'manual' });
expect(response.status).toBe(302);
expect(response.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');
});
it('should redirect platform prefix without path to homepage', async () => {
// Test with /gh (no trailing slash)
const response1 = await SELF.fetch('https://example.com/gh', { redirect: 'manual' });
expect(response1.status).toBe(302);
expect(response1.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');
// Test with /gh/ (with trailing slash)
const response2 = await SELF.fetch('https://example.com/gh/', { redirect: 'manual' });
expect(response2.status).toBe(302);
expect(response2.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');
// Test with multi-part platform prefix (e.g., /ip/openai)
const response3 = await SELF.fetch('https://example.com/ip/openai', { redirect: 'manual' });
expect(response3.status).toBe(302);
expect(response3.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');
});
it('should redirect invalid platform prefix to homepage', async () => {
const response = await SELF.fetch('https://example.com/invalid/test', { redirect: 'manual' });
expect(response.status).toBe(302);
expect(response.headers.get('Location')).toBe('https://github.com/xixu-me/Xget');
});
it('should include security headers in all responses', async () => {
const response = await SELF.fetch('https://example.com/', { redirect: 'manual' });
expect(response.headers.get('Strict-Transport-Security')).toBeTruthy();
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
expect(response.headers.get('X-XSS-Protection')).toBe('1; mode=block');
expect(response.headers.get('Content-Security-Policy')).toBeTruthy();
expect(response.headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');
});
});
describe('Platform URL Transformation', () => {
it('should handle GitHub URLs correctly', async () => {
const testUrl = 'https://example.com/gh/microsoft/vscode/archive/refs/heads/main.zip';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy to GitHub
expect(response.status).not.toBe(400);
});
it('should handle GitLab URLs correctly', async () => {
const testUrl = 'https://example.com/gl/gitlab-org/gitlab/-/archive/master/gitlab-master.zip';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy to GitLab
expect(response.status).not.toBe(400);
});
it('should handle Hugging Face URLs correctly', async () => {
const testUrl = 'https://example.com/hf/microsoft/DialoGPT-medium/resolve/main/config.json';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy to Hugging Face
expect(response.status).not.toBe(400);
});
it('should handle npm URLs correctly', async () => {
const testUrl = 'https://example.com/npm/react/-/react-18.2.0.tgz';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy to npm
expect(response.status).not.toBe(400);
});
it('should handle PyPI URLs correctly', async () => {
const testUrl = 'https://example.com/pypi/packages/source/r/requests/requests-2.31.0.tar.gz';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy to PyPI
expect(response.status).not.toBe(400);
});
it('should handle conda URLs correctly', async () => {
const testUrl =
'https://example.com/conda/pkgs/main/linux-64/numpy-1.24.3-py311h08b1b3b_1.conda';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// 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', () => {
it('should allow GET requests', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'GET'
});
expect(response.status).not.toBe(405);
});
it('should allow HEAD requests', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'HEAD'
});
expect(response.status).not.toBe(405);
});
it('should reject PUT requests for non-Git operations', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'PUT'
});
expect(response.status).toBe(405);
});
it('should reject DELETE requests', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'DELETE'
});
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', () => {
it('should allow POST for Git upload-pack', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo.git/git-upload-pack', {
method: 'POST',
headers: {
'Content-Type': 'application/x-git-upload-pack-request',
'User-Agent': 'git/2.34.1'
}
});
expect(response.status).not.toBe(405);
});
it('should allow POST for Git receive-pack', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo.git/git-receive-pack', {
method: 'POST',
headers: {
'Content-Type': 'application/x-git-receive-pack-request',
'User-Agent': 'git/2.34.1'
}
});
expect(response.status).not.toBe(405);
});
it('should handle Git info/refs requests', async () => {
const response = await SELF.fetch(
'https://example.com/gh/test/repo.git/info/refs?service=git-upload-pack',
{
method: 'GET',
headers: {
'User-Agent': 'git/2.34.1'
}
}
);
expect(response.status).not.toBe(405);
});
});
describe('Path Length Validation', () => {
it('should reject extremely long paths', async () => {
const longPath = `/gh/${'a'.repeat(3000)}`;
const response = await SELF.fetch(`https://example.com${longPath}`);
expect(response.status).toBe(414);
});
it('should accept normal length paths', async () => {
const normalPath = '/gh/microsoft/vscode/archive/refs/heads/main.zip';
const response = await SELF.fetch(`https://example.com${normalPath}`, { method: 'HEAD' });
expect(response.status).not.toBe(414);
});
});
describe('Performance Headers', () => {
it('should include performance metrics in response headers', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'HEAD'
});
expect(response.headers.get('X-Performance-Metrics')).toBeTruthy();
});
it('should include valid JSON in performance metrics', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', {
method: 'HEAD'
});
const metricsHeader = response.headers.get('X-Performance-Metrics');
expect(metricsHeader).toBeTruthy();
expect(() => JSON.parse(metricsHeader || '')).not.toThrow();
});
});
describe('URL Rewriting', () => {
it('should rewrite npm registry URLs in JSON responses', async () => {
// Mock npm package metadata request
const testUrl = 'https://example.com/npm/lodash';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// This test would need actual npm registry response mocking
// For now, just verify the request doesn't fail
expect([200, 301, 302, 404, 500]).toContain(response.status);
});
it('should preserve npm tarball URL structure', async () => {
// Test that npm tarball URLs follow the correct pattern
const testUrl = 'https://example.com/npm/react/-/react-18.2.0.tgz';
const response = await SELF.fetch(testUrl, { method: 'HEAD' });
// Should attempt to proxy correctly
expect(response.status).not.toBe(400);
});
it('should correctly rewrite npm URLs to preserve package names', () => {
// Test the regex replacement logic directly
const mockOriginalText = JSON.stringify({
name: 'npm',
versions: {
'11.5.1': {
dist: {
tarball: 'https://registry.npmjs.org/npm/-/npm-11.5.1.tgz'
}
}
}
});
// 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'
);
const rewrittenData = JSON.parse(rewrittenText);
// Verify the URL is correctly rewritten with package name preserved
expect(rewrittenData.versions['11.5.1'].dist.tarball).toBe(
'https://xget.xi-xu.me/npm/npm/-/npm-11.5.1.tgz'
);
});
});
});