Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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\//);
}
});
});