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

158 lines
5.7 KiB
JavaScript

import { describe, expect, it } from 'vitest';
import { createConfig } from '../../src/config/index.js';
import { isGitLFSRequest, isGitRequest } from '../../src/protocols/git.js';
import {
addSecurityHeaders,
createErrorResponse,
resolveAllowedOrigin
} from '../../src/utils/security.js';
import { getAllowedMethods, validateRequest } from '../../src/utils/validation.js';
describe('Utility Functions', () => {
describe('isGitRequest', () => {
it('should identify Git info/refs requests', () => {
const request = new Request('https://example.com/repo.git/info/refs');
const url = new URL(request.url);
expect(isGitRequest(request, url)).toBe(true);
});
it('should identify Git requests by User-Agent', () => {
const request = new Request('https://example.com/repo.git', {
headers: { 'User-Agent': 'git/2.34.1' }
});
const url = new URL(request.url);
expect(isGitRequest(request, url)).toBe(true);
});
it('should not identify regular file requests as Git', () => {
const request = new Request('https://example.com/repo/file.txt');
const url = new URL(request.url);
expect(isGitRequest(request, url)).toBe(false);
});
});
describe('isGitLFSRequest', () => {
it('should identify LFS batch API requests', () => {
const request = new Request('https://example.com/repo.git/objects/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/vnd.git-lfs+json' }
});
const url = new URL(request.url);
expect(isGitLFSRequest(request, url)).toBe(true);
});
it('should identify LFS requests by User-Agent', () => {
const request = new Request('https://example.com/repo.git', {
headers: { 'User-Agent': 'git-lfs/3.0.0 (GitHub; darwin amd64; go 1.17.2)' }
});
const url = new URL(request.url);
expect(isGitLFSRequest(request, url)).toBe(true);
});
it('should not identify regular file requests as LFS', () => {
const request = new Request('https://example.com/repo/file.txt');
const url = new URL(request.url);
expect(isGitLFSRequest(request, url)).toBe(false);
});
});
describe('validateRequest', () => {
it('should allow GET requests', () => {
const request = new Request('https://example.com/test', { method: 'GET' });
const url = new URL(request.url);
const result = validateRequest(request, url, createConfig());
expect(result.valid).toBe(true);
});
it('should allow POST requests for Git operations', () => {
const request = new Request('https://example.com/repo.git/git-upload-pack', {
method: 'POST',
headers: { 'User-Agent': 'git/2.34.1' }
});
const url = new URL(request.url);
const result = validateRequest(request, url, createConfig());
expect(result.valid).toBe(true);
});
it('should reject encoded traversal attempts against the production validator', () => {
const request = new Request('https://example.com/gh/user/repo/%2e%2e%2fsecret');
const url = new URL(request.url);
const result = validateRequest(request, url, createConfig());
expect(result.valid).toBe(false);
expect(result.status).toBe(400);
});
});
describe('getAllowedMethods', () => {
it('should respect configured methods for regular requests', () => {
const config = createConfig({ ALLOWED_METHODS: 'GET,HEAD,POST' });
const request = new Request('https://example.com/gh/test/repo/issues', { method: 'POST' });
const url = new URL(request.url);
expect(getAllowedMethods(request, url, config)).toEqual(['GET', 'HEAD', 'POST']);
});
});
describe('addSecurityHeaders', () => {
it('should add all required security headers', () => {
const headers = new Headers();
const result = addSecurityHeaders(headers);
expect(result.get('Strict-Transport-Security')).toContain('max-age=31536000');
expect(result.get('X-Frame-Options')).toBe('DENY');
expect(result.get('X-XSS-Protection')).toBe('1; mode=block');
expect(result.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');
expect(result.get('Content-Security-Policy')).toContain("default-src 'none'");
expect(result.get('Permissions-Policy')).toContain('interest-cohort=()');
});
it('should return the same Headers object', () => {
const headers = new Headers();
const result = addSecurityHeaders(headers);
expect(result).toBe(headers);
});
});
describe('resolveAllowedOrigin', () => {
it('should return the matching origin from the production config', () => {
const config = createConfig({ ALLOWED_ORIGINS: 'https://app.example.com' });
const request = new Request('https://example.com/gh/test/repo', {
headers: { Origin: 'https://app.example.com' }
});
expect(resolveAllowedOrigin(request, config)).toBe('https://app.example.com');
});
it('should reject origins that are not configured', () => {
const config = createConfig({ ALLOWED_ORIGINS: 'https://app.example.com' });
const request = new Request('https://example.com/gh/test/repo', {
headers: { Origin: 'https://evil.example.com' }
});
expect(resolveAllowedOrigin(request, config)).toBeNull();
});
});
describe('createErrorResponse', () => {
it('should create a plain-text error response with security headers', async () => {
const response = createErrorResponse('Bad Request', 400);
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('text/plain');
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
expect(await response.text()).toBe('Bad Request');
});
});
});