7a0da7932b
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
191 lines
7.5 KiB
JavaScript
191 lines
7.5 KiB
JavaScript
/**
|
|
* Tests for config/urls.js — URLBuilder behavior
|
|
*
|
|
* Focuses on the URLBuilder logic (placeholder substitution, ID extraction,
|
|
* page-type detection). Does NOT test that URL constants equal specific
|
|
* literals — those tests would just assert the source against itself.
|
|
*/
|
|
|
|
import '@js/config/urls.js';
|
|
|
|
const { URLS, URLBuilder } = window;
|
|
|
|
describe('URLBuilder', () => {
|
|
describe('build', () => {
|
|
it('replaces {id} placeholder', () => {
|
|
expect(URLBuilder.build('/api/research/{id}', 42)).toBe('/api/research/42');
|
|
});
|
|
|
|
it('handles string IDs', () => {
|
|
expect(URLBuilder.build('/api/research/{id}', 'abc-123')).toBe('/api/research/abc-123');
|
|
});
|
|
|
|
it('only replaces first {id} occurrence', () => {
|
|
expect(URLBuilder.build('/api/{id}/{id}', 5)).toBe('/api/5/{id}');
|
|
});
|
|
|
|
it('returns template unchanged when no {id} placeholder', () => {
|
|
expect(URLBuilder.build('/api/static', 5)).toBe('/api/static');
|
|
});
|
|
});
|
|
|
|
describe('buildWithReplacements', () => {
|
|
it('replaces multiple distinct placeholders', () => {
|
|
const result = URLBuilder.buildWithReplacements(
|
|
'/api/collection/{collectionId}/document/{documentId}',
|
|
{ collectionId: '10', documentId: '20' }
|
|
);
|
|
expect(result).toBe('/api/collection/10/document/20');
|
|
});
|
|
|
|
it('handles missing placeholder gracefully', () => {
|
|
const result = URLBuilder.buildWithReplacements(
|
|
'/api/{a}/{b}',
|
|
{ a: '1' }
|
|
);
|
|
expect(result).toBe('/api/1/{b}');
|
|
});
|
|
|
|
it('handles empty replacements object', () => {
|
|
expect(URLBuilder.buildWithReplacements('/api/{id}', {})).toBe('/api/{id}');
|
|
});
|
|
});
|
|
|
|
describe('convenience methods build URLs from URLS constants', () => {
|
|
// These test that the convenience methods correctly compose URLBuilder.build
|
|
// with the right URLS constant — not what those constants are.
|
|
|
|
it('progressPage substitutes ID into PAGES.PROGRESS', () => {
|
|
expect(URLBuilder.progressPage(42)).toBe(URLS.PAGES.PROGRESS.replace('{id}', '42'));
|
|
});
|
|
|
|
it('researchStatus substitutes ID into API.RESEARCH_STATUS', () => {
|
|
expect(URLBuilder.researchStatus(42)).toBe(URLS.API.RESEARCH_STATUS.replace('{id}', '42'));
|
|
});
|
|
|
|
it('terminateResearch substitutes ID into API.TERMINATE_RESEARCH', () => {
|
|
expect(URLBuilder.terminateResearch(42)).toBe(URLS.API.TERMINATE_RESEARCH.replace('{id}', '42'));
|
|
});
|
|
|
|
it('deleteResearch substitutes ID into API.DELETE_RESEARCH', () => {
|
|
expect(URLBuilder.deleteResearch(42)).toBe(URLS.API.DELETE_RESEARCH.replace('{id}', '42'));
|
|
});
|
|
|
|
it('historyStatus substitutes ID into HISTORY_API.STATUS', () => {
|
|
expect(URLBuilder.historyStatus(42)).toBe(URLS.HISTORY_API.STATUS.replace('{id}', '42'));
|
|
});
|
|
|
|
it('researchLogs returns the bare URL when no limit passed', () => {
|
|
const url = URLBuilder.researchLogs(42);
|
|
expect(url).toBe(URLS.API.RESEARCH_LOGS.replace('{id}', '42'));
|
|
expect(url).not.toContain('?');
|
|
});
|
|
|
|
it('researchLogs appends ?limit=N when limit passed', () => {
|
|
const url = URLBuilder.researchLogs(42, 500);
|
|
expect(url).toContain('limit=500');
|
|
});
|
|
|
|
it('researchLogs encodes the limit value', () => {
|
|
// Sanity: limit should be safe to pass directly, but encodeURIComponent
|
|
// means anything unexpected stays escaped.
|
|
const url = URLBuilder.researchLogs('abc-123', 5000);
|
|
expect(url).toBe(`${URLS.API.RESEARCH_LOGS.replace('{id}', 'abc-123')}?limit=5000`);
|
|
});
|
|
|
|
it('getSetting substitutes key into SETTINGS_API.GET_SETTING', () => {
|
|
const url = URLBuilder.getSetting('llm.model');
|
|
expect(url).toContain('llm.model');
|
|
expect(url).toBe(URLS.SETTINGS_API.GET_SETTING.replace('{key}', 'llm.model'));
|
|
});
|
|
|
|
it('researchMetrics substitutes ID into METRICS_API.RESEARCH', () => {
|
|
expect(URLBuilder.researchMetrics(42)).toBe(URLS.METRICS_API.RESEARCH.replace('{id}', '42'));
|
|
});
|
|
|
|
it('journalQualityPage scopes the dashboard to an encoded research ID', () => {
|
|
expect(URLBuilder.journalQualityPage('abc 123')).toBe(
|
|
`${URLS.PAGES.JOURNAL_QUALITY}?research_id=abc%20123`
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('extractResearchIdFromPattern', () => {
|
|
// We can't easily change window.location.pathname in happy-dom,
|
|
// but we can verify the regex pattern construction works for the
|
|
// current path (which is "/" by default → no match).
|
|
it('returns null when pattern not in current path', () => {
|
|
expect(URLBuilder.extractResearchIdFromPattern('results')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('extractResearchId', () => {
|
|
it('returns null when no ID pattern matches current path', () => {
|
|
// Default happy-dom path is "/"
|
|
expect(URLBuilder.extractResearchId()).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('getCurrentPageType', () => {
|
|
it('returns a valid page type for the current path', () => {
|
|
const validTypes = ['home', 'results', 'details', 'progress',
|
|
'history', 'settings', 'metrics', 'unknown'];
|
|
expect(validTypes).toContain(URLBuilder.getCurrentPageType());
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('URLS constants — structural sanity (not literal values)', () => {
|
|
// Lightweight invariants that catch typos without asserting exact strings.
|
|
|
|
it('all URL templates start with /', () => {
|
|
const collectGroup = (group) => Object.values(group);
|
|
const allUrls = [
|
|
...collectGroup(URLS.API),
|
|
...collectGroup(URLS.PAGES),
|
|
...collectGroup(URLS.HISTORY_API),
|
|
...collectGroup(URLS.SETTINGS_API),
|
|
...collectGroup(URLS.METRICS_API),
|
|
...collectGroup(URLS.LIBRARY_API),
|
|
];
|
|
for (const url of allUrls) {
|
|
expect(url).toMatch(/^\//);
|
|
}
|
|
});
|
|
|
|
it('no URLs contain accidental double slashes', () => {
|
|
const collectGroup = (group) => Object.values(group);
|
|
const allUrls = [
|
|
...collectGroup(URLS.API),
|
|
...collectGroup(URLS.PAGES),
|
|
...collectGroup(URLS.HISTORY_API),
|
|
...collectGroup(URLS.SETTINGS_API),
|
|
];
|
|
for (const url of allUrls) {
|
|
expect(url).not.toMatch(/\/\//);
|
|
}
|
|
});
|
|
|
|
it('settings API routes are namespaced under /settings/', () => {
|
|
for (const [key, url] of Object.entries(URLS.SETTINGS_API)) {
|
|
expect(url, `SETTINGS_API.${key}`).toMatch(/^\/settings\//);
|
|
}
|
|
});
|
|
|
|
it('metrics API routes are namespaced under /metrics/', () => {
|
|
for (const [key, url] of Object.entries(URLS.METRICS_API)) {
|
|
expect(url, `METRICS_API.${key}`).toMatch(/^\/metrics\//);
|
|
}
|
|
});
|
|
|
|
it('journal quality page is served from the metrics blueprint', () => {
|
|
expect(URLS.PAGES.JOURNAL_QUALITY).toMatch(/^\/metrics\//);
|
|
});
|
|
|
|
it('library API routes are namespaced under /library/', () => {
|
|
for (const [key, url] of Object.entries(URLS.LIBRARY_API)) {
|
|
expect(url, `LIBRARY_API.${key}`).toMatch(/^\/library\//);
|
|
}
|
|
});
|
|
});
|