/** * JavaScript tests for URL configuration * Run with: npm test tests/infrastructure_tests/test_urls.test.js */ // Mock the window object for testing global.window = {}; // Load the URLs configuration const urlsModule = require('../../src/local_deep_research/web/static/js/config/urls.js'); // Get the exported values from window (for browser environment tests) const { URLS, URLBuilder } = global.window; describe('URLs Configuration', () => { describe('URL Structure', () => { test('URLS object should have required sections', () => { expect(URLS).toBeDefined(); expect(URLS.API).toBeDefined(); expect(URLS.PAGES).toBeDefined(); expect(URLS.HISTORY_API).toBeDefined(); expect(URLS.SETTINGS_API).toBeDefined(); expect(URLS.METRICS_API).toBeDefined(); }); test('Critical API endpoints should be defined', () => { const criticalAPIs = [ 'START_RESEARCH', 'RESEARCH_STATUS', 'RESEARCH_DETAILS', 'RESEARCH_LOGS', 'RESEARCH_REPORT', 'TERMINATE_RESEARCH', 'DELETE_RESEARCH', 'HISTORY' ]; criticalAPIs.forEach(api => { expect(URLS.API[api]).toBeDefined(); expect(typeof URLS.API[api]).toBe('string'); }); }); test('Page routes should be defined', () => { const pages = ['HOME', 'PROGRESS', 'RESULTS', 'DETAILS', 'HISTORY', 'SETTINGS', 'METRICS', 'NEWS', 'NEWS_SUBSCRIPTIONS', 'BENCHMARK', 'BENCHMARK_RESULTS', 'EMBEDDING_SETTINGS']; pages.forEach(page => { expect(URLS.PAGES[page]).toBeDefined(); expect(typeof URLS.PAGES[page]).toBe('string'); }); }); }); describe('URLBuilder Functionality', () => { test('build() should replace {id} placeholders', () => { const template = '/api/research/{id}/status'; const result = URLBuilder.build(template, 123); expect(result).toBe('/api/research/123/status'); }); test('buildWithReplacements() should handle multiple placeholders', () => { const template = '/api/{type}/{id}/action'; const result = URLBuilder.buildWithReplacements(template, { type: 'research', id: 456 }); expect(result).toBe('/api/research/456/action'); }); test('Convenience methods should return correct URLs', () => { expect(URLBuilder.progressPage(123)).toBe('/progress/123'); expect(URLBuilder.resultsPage(456)).toBe('/results/456'); expect(URLBuilder.detailsPage(789)).toBe('/details/789'); expect(URLBuilder.researchStatus(111)).toBe('/api/research/111/status'); expect(URLBuilder.researchDetails(222)).toBe('/api/research/222'); expect(URLBuilder.researchLogs(333)).toBe('/api/research/333/logs'); expect(URLBuilder.researchReport(444)).toBe('/api/report/444'); expect(URLBuilder.terminateResearch(555)).toBe('/api/terminate/555'); expect(URLBuilder.deleteResearch(666)).toBe('/api/delete/666'); }); test('History API convenience methods should work', () => { expect(URLBuilder.historyStatus(123)).toBe('/history/status/123'); expect(URLBuilder.historyDetails(456)).toBe('/history/details/456'); expect(URLBuilder.historyLogs(789)).toBe('/history/logs/789'); expect(URLBuilder.historyReport(111)).toBe('/history/report/111'); expect(URLBuilder.markdownExport(222)).toBe('/api/markdown/222'); }); test('Settings API convenience methods should work', () => { expect(URLBuilder.getSetting('llm.model')).toBe('/settings/api/llm.model'); expect(URLBuilder.updateSetting('search.tool')).toBe('/settings/api/search.tool'); expect(URLBuilder.deleteSetting('custom.key')).toBe('/settings/api/custom.key'); }); test('Metrics API convenience methods should work', () => { expect(URLBuilder.researchMetrics(123)).toBe('/metrics/api/metrics/research/123'); expect(URLBuilder.researchTimelineMetrics(456)).toBe('/metrics/api/metrics/research/456/timeline'); expect(URLBuilder.researchSearchMetrics(789)).toBe('/metrics/api/metrics/research/789/search'); expect(URLBuilder.getRating(111)).toBe('/metrics/api/ratings/111'); expect(URLBuilder.saveRating(222)).toBe('/metrics/api/ratings/222'); expect(URLBuilder.researchCosts(333)).toBe('/metrics/api/research-costs/333'); }); }); describe('URL Pattern Extraction', () => { beforeEach(() => { // Mock window.location for testing delete global.window.location; global.window.location = { pathname: '/' }; }); test('extractResearchId() should extract ID from various patterns', () => { const testCases = [ { path: '/results/123', expected: '123' }, { path: '/details/456', expected: '456' }, { path: '/progress/789', expected: '789' }, { path: '/home', expected: null }, { path: '/settings/', expected: null } ]; testCases.forEach(({ path, expected }) => { global.window.location.pathname = path; expect(URLBuilder.extractResearchId()).toBe(expected); }); }); test('extractResearchIdFromPattern() should work for specific patterns', () => { global.window.location.pathname = '/results/999'; expect(URLBuilder.extractResearchIdFromPattern('results')).toBe('999'); global.window.location.pathname = '/details/888'; expect(URLBuilder.extractResearchIdFromPattern('details')).toBe('888'); expect(URLBuilder.extractResearchIdFromPattern('results')).toBe(null); }); test('getCurrentPageType() should identify page types', () => { const testCases = [ { path: '/', expected: 'home' }, { path: '/index', expected: 'home' }, { path: '/results/123', expected: 'results' }, { path: '/details/456', expected: 'details' }, { path: '/progress/789', expected: 'progress' }, { path: '/history', expected: 'history' }, { path: '/history/', expected: 'history' }, { path: '/settings', expected: 'settings' }, { path: '/settings/advanced', expected: 'settings' }, { path: '/metrics', expected: 'metrics' }, { path: '/metrics/costs', expected: 'metrics' }, { path: '/unknown/path', expected: 'unknown' } ]; testCases.forEach(({ path, expected }) => { global.window.location.pathname = path; expect(URLBuilder.getCurrentPageType()).toBe(expected); }); }); }); describe('URL Consistency Checks', () => { test('All URLs should start with /', () => { const checkUrls = (obj, path = '') => { Object.entries(obj).forEach(([key, value]) => { if (typeof value === 'string') { expect(value).toMatch(/^\//); } else if (typeof value === 'object' && value !== null) { checkUrls(value, `${path}.${key}`); } }); }; checkUrls(URLS); }); test('URLs with parameters should use consistent placeholder format', () => { const checkPlaceholders = (obj) => { Object.entries(obj).forEach(([_key, value]) => { if (typeof value === 'string') { // Check that placeholders use {name} format const placeholders = value.match(/\{[^}]+\}/g) || []; placeholders.forEach(placeholder => { expect(placeholder).toMatch(/^\{[a-z_]+\}$/i); }); } else if (typeof value === 'object' && value !== null) { checkPlaceholders(value); } }); }; checkPlaceholders(URLS); }); test('API URLs should follow RESTful conventions', () => { // URLs ending with ID should be for single resource operations const singleResourcePatterns = [ 'RESEARCH_STATUS', 'RESEARCH_DETAILS', 'RESEARCH_LOGS', 'RESEARCH_REPORT', 'TERMINATE_RESEARCH', 'DELETE_RESEARCH' ]; singleResourcePatterns.forEach(pattern => { expect(URLS.API[pattern]).toMatch(/\{id\}/); }); // Collection URLs should not have ID expect(URLS.API.HISTORY).not.toMatch(/\{id\}/); expect(URLS.API.CLEAR_HISTORY).not.toMatch(/\{id\}/); }); test('Settings API URLs should be consistent', () => { Object.entries(URLS.SETTINGS_API).forEach(([key, url]) => { if (key !== 'BASE') { expect(url).toMatch(/^\/settings\//); } }); }); test('Metrics API URLs should be consistent', () => { Object.entries(URLS.METRICS_API).forEach(([key, url]) => { if (key !== 'BASE') { expect(url).toMatch(/^\/metrics\//); } }); }); }); describe('Export Validation', () => { test('Module should export correctly for different environments', () => { // Browser environment (already tested via global.window) expect(global.window.URLS).toBeDefined(); expect(global.window.URLBuilder).toBeDefined(); // Node.js environment - check the imported module expect(urlsModule).toBeDefined(); expect(urlsModule.URLS).toBeDefined(); expect(urlsModule.URLBuilder).toBeDefined(); }); }); });