7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
141 lines
5.3 KiB
JavaScript
141 lines
5.3 KiB
JavaScript
/**
|
|
* JavaScript tests for safe-fetch.js
|
|
* Run with: npm test tests/infrastructure_tests/test_safe_fetch.test.js
|
|
*
|
|
* Tests the safeFetch function which wraps fetch() with URL validation.
|
|
*/
|
|
|
|
// Mock fetch
|
|
global.fetch = jest.fn(() => Promise.resolve({ ok: true }));
|
|
|
|
// Mock SafeLogger (used by url-validator.js)
|
|
global.SafeLogger = {
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
info: jest.fn(),
|
|
debug: jest.fn()
|
|
};
|
|
|
|
// Mock window.location for URL parsing
|
|
global.window = {
|
|
location: {
|
|
href: 'http://localhost:5000/',
|
|
origin: 'http://localhost:5000'
|
|
}
|
|
};
|
|
|
|
// Load the URL validator first (dependency) and expose as global
|
|
const URLValidator = require('../../src/local_deep_research/web/static/js/security/url-validator.js');
|
|
global.URLValidator = URLValidator;
|
|
|
|
// Load safe-fetch module and expose as global
|
|
const { safeFetch } = require('../../src/local_deep_research/web/static/js/security/safe-fetch.js');
|
|
global.safeFetch = safeFetch;
|
|
|
|
describe('safeFetch', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
global.fetch.mockClear();
|
|
});
|
|
|
|
describe('Internal URLs (starting with /)', () => {
|
|
test('should allow internal API URLs without validation', async () => {
|
|
await safeFetch('/api/settings');
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/settings', {});
|
|
});
|
|
|
|
test('should allow internal page URLs', async () => {
|
|
await safeFetch('/history');
|
|
expect(global.fetch).toHaveBeenCalledWith('/history', {});
|
|
});
|
|
|
|
test('should pass options through for internal URLs', async () => {
|
|
const options = { method: 'POST', body: JSON.stringify({ key: 'value' }) };
|
|
await safeFetch('/api/data', options);
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/data', options);
|
|
});
|
|
|
|
test('should allow deep internal paths', async () => {
|
|
await safeFetch('/api/v1/research/123/status');
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/v1/research/123/status', {});
|
|
});
|
|
});
|
|
|
|
describe('External URLs with safe schemes', () => {
|
|
test('should allow https URLs', async () => {
|
|
await safeFetch('https://example.com/api');
|
|
expect(global.fetch).toHaveBeenCalledWith('https://example.com/api', {});
|
|
});
|
|
|
|
test('should allow http URLs', async () => {
|
|
await safeFetch('http://example.com/api');
|
|
expect(global.fetch).toHaveBeenCalledWith('http://example.com/api', {});
|
|
});
|
|
});
|
|
|
|
describe('Unsafe URLs', () => {
|
|
test('should block javascript: URLs', async () => {
|
|
await expect(safeFetch('javascript:alert(1)')).rejects.toThrow('Blocked unsafe URL');
|
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('should block data: URLs', async () => {
|
|
await expect(safeFetch('data:text/html,<script>alert(1)</script>')).rejects.toThrow('Blocked unsafe URL');
|
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('should block vbscript: URLs', async () => {
|
|
await expect(safeFetch('vbscript:msgbox(1)')).rejects.toThrow('Blocked unsafe URL');
|
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('should block javascript: with mixed case', async () => {
|
|
await expect(safeFetch('JavaScript:alert(1)')).rejects.toThrow('Blocked unsafe URL');
|
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('should block javascript: with leading whitespace', async () => {
|
|
await expect(safeFetch(' javascript:alert(1)')).rejects.toThrow('Blocked unsafe URL');
|
|
expect(global.fetch).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Edge cases', () => {
|
|
test('should handle empty options object', async () => {
|
|
await safeFetch('/api/test', {});
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/test', {});
|
|
});
|
|
|
|
test('should handle URLs with query parameters', async () => {
|
|
await safeFetch('/api/search?q=test&limit=10');
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/search?q=test&limit=10', {});
|
|
});
|
|
|
|
test('should handle URLs with fragments', async () => {
|
|
await safeFetch('/page#section');
|
|
expect(global.fetch).toHaveBeenCalledWith('/page#section', {});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('URLValidator integration', () => {
|
|
test('URLValidator should be available globally', () => {
|
|
expect(global.URLValidator).toBeDefined();
|
|
expect(typeof global.URLValidator.isSafeUrl).toBe('function');
|
|
});
|
|
|
|
test('URLValidator.isSafeUrl should validate https URLs', () => {
|
|
expect(global.URLValidator.isSafeUrl('https://example.com')).toBe(true);
|
|
});
|
|
|
|
test('URLValidator.isSafeUrl should reject javascript URLs', () => {
|
|
expect(global.URLValidator.isSafeUrl('javascript:alert(1)')).toBe(false);
|
|
});
|
|
|
|
test('URLValidator.isUnsafeScheme should detect unsafe schemes', () => {
|
|
expect(global.URLValidator.isUnsafeScheme('javascript:void(0)')).toBe(true);
|
|
expect(global.URLValidator.isUnsafeScheme('data:text/html,test')).toBe(true);
|
|
expect(global.URLValidator.isUnsafeScheme('https://example.com')).toBe(false);
|
|
});
|
|
});
|