Files
xget/test/unit/protocols.test.js
T

324 lines
10 KiB
JavaScript

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 { getScopeFromUrl, 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('derives scoped pull access from /cr-prefixed registry requests', () => {
const url = new URL('https://example.com/cr/docker/v2/nginx/manifests/latest');
expect(getScopeFromUrl(url, url.pathname, 'cr-docker')).toBe('repository:library/nginx:pull');
});
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/');
});
it('routes registry manifests without duplicating /v2', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('', {
status: 200,
headers: { 'Content-Length': '0' }
})
);
const request = new Request(
'https://example.com/cr/ghcr/v2/nginxinc/nginx-unprivileged/manifests/latest',
{
method: 'HEAD'
}
);
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(String(fetchSpy.mock.calls[0][0])).toBe(
'https://ghcr.io/v2/nginxinc/nginx-unprivileged/manifests/latest'
);
});
it('routes host-style registry manifests through the upstream v2 API', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('', {
status: 200,
headers: { 'Content-Length': '0' }
})
);
const request = new Request('https://example.com/v2/cr/ghcr/xixu-me/xget/manifests/latest', {
method: 'HEAD'
});
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(String(fetchSpy.mock.calls[0][0])).toBe(
'https://ghcr.io/v2/xixu-me/xget/manifests/latest'
);
});
it('normalizes Docker Hub official image paths during proxying', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('', {
status: 200,
headers: { 'Content-Length': '0' }
})
);
const request = new Request('https://example.com/cr/docker/v2/nginx/manifests/latest', {
headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }
});
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(String(fetchSpy.mock.calls[0][0])).toBe(
'https://registry-1.docker.io/v2/library/nginx/manifests/latest'
);
});
it('preserves platform-specific Docker auth challenges', async () => {
let callCount = 0;
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
callCount++;
if (callCount === 1) {
return new Response('', {
status: 401,
headers: {
'WWW-Authenticate': 'Bearer realm="https://ghcr.io/token",service="ghcr.io"'
}
});
}
return new Response('denied', { status: 401 });
});
const request = new Request('https://example.com/cr/ghcr/v2/private/repo/manifests/latest', {
headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' }
});
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(401);
expect(response.headers.get('WWW-Authenticate')).toBe(
'Bearer realm="https://example.com/cr/ghcr/v2/auth",service="Xget"'
);
expect(await response.text()).toContain('UNAUTHORIZED');
});
it('follows 303 redirects for Docker registry responses without forwarding auth headers', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
const headers = new Headers(init?.headers);
const url = String(input);
if (url === 'https://ghcr.io/v2/xixu-me/xget/manifests/latest') {
expect(headers.get('Authorization')).toBe('Bearer token123');
return new Response(null, {
status: 303,
headers: {
Location: 'https://pkg-containers.githubusercontent.com/manifest'
}
});
}
if (url === 'https://pkg-containers.githubusercontent.com/manifest') {
expect(headers.get('Authorization')).toBeNull();
return new Response('', {
status: 200,
headers: { 'Content-Length': '0' }
});
}
throw new Error(`Unexpected fetch URL: ${url}`);
});
const request = new Request('https://example.com/v2/cr/ghcr/xixu-me/xget/manifests/latest', {
headers: { Authorization: 'Bearer token123' }
});
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
it('accepts standard repository scopes on platform-prefixed auth endpoints', async () => {
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async input => {
const url = String(input);
if (url === '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?scope=repository:private/repo:pull&service=Xget'
);
const response = await worker.fetch(request, {}, executionContext);
expect(response.status).toBe(200);
expect(String(fetchSpy.mock.calls[1][0])).toContain('scope=repository%3Aprivate%2Frepo%3Apull');
});
});
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'
});
});
it('updates Content-Length after rewriting npm metadata', async () => {
const upstreamBody = JSON.stringify({
dist: {
tarball: 'https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz'
}
});
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(upstreamBody, {
status: 200,
headers: {
'Content-Type': 'application/json',
'Content-Length': String(upstreamBody.length)
}
})
);
const response = await worker.fetch(
new Request('https://example.com/npm/pkg'),
{},
executionContext
);
const body = await response.text();
expect(body).toContain('https://example.com/npm/pkg/-/pkg-1.0.0.tgz');
expect(response.headers.get('Content-Length')).toBe(
String(new TextEncoder().encode(body).byteLength)
);
});
});