426 lines
No EOL
12 KiB
JavaScript
426 lines
No EOL
12 KiB
JavaScript
import { jest } from '@jest/globals';
|
|
|
|
// Mock the logs module
|
|
jest.unstable_mockModule('../dist/utils/logs.js', () => ({
|
|
warn: jest.fn(),
|
|
error: jest.fn()
|
|
}));
|
|
|
|
// Import modules after mocking
|
|
const { getRequestURL, getRealIP } = await import('../dist/utils/network.js');
|
|
const logs = await import('../dist/utils/logs.js');
|
|
|
|
describe('Network utilities', () => {
|
|
|
|
describe('getRequestURL', () => {
|
|
test('should handle http URLs', () => {
|
|
const request = { url: 'http://example.com/path' };
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result).toBeInstanceOf(URL);
|
|
expect(result.href).toBe('http://example.com/path');
|
|
});
|
|
|
|
test('should handle https URLs', () => {
|
|
const request = { url: 'https://example.com/path' };
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result).toBeInstanceOf(URL);
|
|
expect(result.href).toBe('https://example.com/path');
|
|
});
|
|
|
|
test('should construct URL from path and host header', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: { host: 'example.com' }
|
|
};
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result).toBeInstanceOf(URL);
|
|
expect(result.href).toBe('http://example.com/path');
|
|
});
|
|
|
|
test('should use https when secure property is true', () => {
|
|
const request = {
|
|
url: '/path',
|
|
secure: true,
|
|
headers: { host: 'example.com' }
|
|
};
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result.href).toBe('https://example.com/path');
|
|
});
|
|
|
|
test('should use https when x-forwarded-proto is https', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
host: 'example.com',
|
|
'x-forwarded-proto': 'https'
|
|
}
|
|
};
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result.href).toBe('https://example.com/path');
|
|
});
|
|
|
|
test('should handle Fetch-style headers with get() method', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
get: jest.fn((name) => {
|
|
if (name === 'host') return 'example.com';
|
|
return null;
|
|
})
|
|
}
|
|
};
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result.href).toBe('http://example.com/path');
|
|
});
|
|
|
|
test('should return null for missing URL', () => {
|
|
const request = {};
|
|
const result = getRequestURL(request);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
test('should return null for invalid URL', () => {
|
|
const request = {
|
|
url: '/test',
|
|
headers: { host: 'localhost' }
|
|
};
|
|
|
|
// Mock URL constructor to throw
|
|
const originalURL = global.URL;
|
|
global.URL = function() {
|
|
throw new TypeError('Invalid URL');
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result).toBeNull();
|
|
expect(logs.warn).toHaveBeenCalled();
|
|
|
|
// Restore
|
|
global.URL = originalURL;
|
|
});
|
|
|
|
test('should handle non-Error objects in catch block', () => {
|
|
const request = {
|
|
url: '/test',
|
|
headers: { host: 'localhost' }
|
|
};
|
|
|
|
// Mock URL constructor to throw non-Error
|
|
const originalURL = global.URL;
|
|
global.URL = function() {
|
|
throw "String error instead of Error object";
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result).toBeNull();
|
|
|
|
// Restore
|
|
global.URL = originalURL;
|
|
});
|
|
|
|
test('should handle array host headers in Express requests', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: { host: ['example.com', 'backup.com'] } // Array host
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://example.com/path');
|
|
});
|
|
|
|
test('should handle empty array host headers', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: { host: [] } // Empty array
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://localhost/path'); // Falls back to localhost
|
|
});
|
|
|
|
test('should handle x-forwarded-host with Fetch-style headers', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
get: jest.fn((name) => {
|
|
if (name === 'host') return null;
|
|
if (name === 'x-forwarded-host') return 'forwarded.example.com';
|
|
return null;
|
|
})
|
|
}
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://forwarded.example.com/path');
|
|
});
|
|
|
|
test('should fallback to localhost for Fetch-style headers when all host headers are missing', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
get: jest.fn(() => null) // All headers return null
|
|
}
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://localhost/path');
|
|
});
|
|
|
|
test('should handle x-forwarded-host in Express headers', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
'x-forwarded-host': 'forwarded.example.com'
|
|
// No host header
|
|
}
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://forwarded.example.com/path');
|
|
});
|
|
|
|
test('should handle array x-forwarded-host headers', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
'x-forwarded-host': ['forwarded.example.com', 'backup.example.com']
|
|
}
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://forwarded.example.com/path');
|
|
});
|
|
|
|
test('should handle empty array x-forwarded-host headers', () => {
|
|
const request = {
|
|
url: '/path',
|
|
headers: {
|
|
'x-forwarded-host': []
|
|
}
|
|
};
|
|
|
|
const result = getRequestURL(request);
|
|
expect(result.href).toBe('http://localhost/path');
|
|
});
|
|
});
|
|
|
|
describe('getRealIP', () => {
|
|
test('should extract IP from x-forwarded-for header', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': '192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should extract first IP from comma-separated x-forwarded-for', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': '192.168.1.100, 10.0.0.1, 127.0.0.1' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use x-real-ip when x-forwarded-for is missing', () => {
|
|
const request = {
|
|
headers: { 'x-real-ip': '192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should handle Fetch-style headers using get() method', () => {
|
|
const request = {
|
|
headers: {
|
|
get: jest.fn((name) => {
|
|
if (name === 'x-forwarded-for') return '192.168.1.100';
|
|
return null;
|
|
})
|
|
}
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use server remoteAddress when headers are empty', () => {
|
|
const request = { headers: {} };
|
|
const server = { remoteAddress: '192.168.1.100' };
|
|
|
|
const result = getRealIP(request, server);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use connection.remoteAddress for Express requests', () => {
|
|
const request = {
|
|
headers: {},
|
|
connection: { remoteAddress: '192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use req.ip property when available', () => {
|
|
const request = {
|
|
headers: {},
|
|
ip: '192.168.1.100'
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should clean IPv6 mapped IPv4 addresses', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': '::ffff:192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should return 127.0.0.1 as ultimate fallback', () => {
|
|
const request = { headers: {} };
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
|
|
test('should prioritize x-forwarded-for over other sources', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': '192.168.1.100' },
|
|
connection: { remoteAddress: '10.0.0.1' },
|
|
ip: '172.16.0.1'
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should handle array x-forwarded-for headers', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': ['192.168.1.100', '10.0.0.1'] }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should handle array x-real-ip headers', () => {
|
|
const request = {
|
|
headers: { 'x-real-ip': ['192.168.1.100', '10.0.0.1'] }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use x-real-ip with Fetch-style headers when x-forwarded-for is missing', () => {
|
|
const request = {
|
|
headers: {
|
|
get: jest.fn((name) => {
|
|
if (name === 'x-forwarded-for') return null;
|
|
if (name === 'x-real-ip') return '192.168.1.100';
|
|
return null;
|
|
})
|
|
}
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should use socket.remoteAddress when connection.remoteAddress is missing', () => {
|
|
const request = {
|
|
headers: {},
|
|
connection: {}, // connection exists but no remoteAddress
|
|
socket: { remoteAddress: '192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should handle missing connection but present socket', () => {
|
|
const request = {
|
|
headers: {},
|
|
socket: { remoteAddress: '192.168.1.100' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
// Note: socket.remoteAddress is only checked within the connection block
|
|
// When no connection exists, it falls back to URL hostname/127.0.0.1
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
|
|
test('should handle whitespace in comma-separated IPs', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': ' 192.168.1.100 , 10.0.0.1 , 127.0.0.1 ' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('192.168.1.100');
|
|
});
|
|
|
|
test('should fallback to URL hostname when all IP sources fail', () => {
|
|
const request = {
|
|
headers: {},
|
|
url: '/test'
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
// getRequestURL constructs URL with localhost as default host, so hostname is 'localhost'
|
|
expect(result).toBe('localhost');
|
|
});
|
|
|
|
test('should handle undefined IP values gracefully', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': undefined, 'x-real-ip': undefined }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
|
|
test('should handle null IP values gracefully', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': null, 'x-real-ip': null }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
|
|
test('should handle empty string IP values', () => {
|
|
const request = {
|
|
headers: { 'x-forwarded-for': '', 'x-real-ip': '' }
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
|
|
test('should handle empty array header values', () => {
|
|
const request = {
|
|
headers: {
|
|
'x-forwarded-for': [],
|
|
'x-real-ip': []
|
|
}
|
|
};
|
|
|
|
const result = getRealIP(request);
|
|
expect(result).toBe('127.0.0.1');
|
|
});
|
|
});
|
|
});
|