Warning, /firebird/packages/firebird-ng/api/src/lib/services/url.service.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * UrlService against the real ConfigService: the backend keys the config
0003 * page writes (`server.useApi`, `server.url`) are the keys this service
0004 * reads, through every config source.
0005 */
0006 import { TestBed } from '@angular/core/testing';
0007 import { provideZonelessChangeDetection, signal, type WritableSignal } from '@angular/core';
0008 import { ConfigService, ServerConfigService, URL_ALIASES } from '@dexvis/app-features';
0009 import { UrlService } from './url.service';
0010 import { BACKEND_URL_CONFIG, BACKEND_USE_API_CONFIG } from '../config-keys';
0011 import { defaultFirebirdConfig, type ServerConfig } from './server-config';
0012
0013 describe('UrlService', () => {
0014 let serverConfig: WritableSignal<ServerConfig>;
0015 let config: ConfigService;
0016
0017 /** Creates the service; config sources set before this call act as pending layers. */
0018 function createService(): UrlService {
0019 return TestBed.inject(UrlService);
0020 }
0021
0022 /** What the config page's controls do: a runtime write of the declared key. */
0023 function useBackend(url?: string): void {
0024 config.declare(BACKEND_USE_API_CONFIG).value = true;
0025 if (url !== undefined) config.declare(BACKEND_URL_CONFIG).value = url;
0026 }
0027
0028 beforeEach(() => {
0029 localStorage.clear();
0030 serverConfig = signal<ServerConfig>({ ...defaultFirebirdConfig });
0031 TestBed.configureTestingModule({
0032 providers: [
0033 provideZonelessChangeDetection(),
0034 // The real ConfigService (root); the server config is a plain signal holder
0035 { provide: ServerConfigService, useValue: { configSignal: serverConfig } },
0036 // Aliases are DI-contributed (withUrlAlias feature)
0037 { provide: URL_ALIASES, useValue: { prefix: 'exp://', base: 'https://data.example.org/artifacts/' }, multi: true },
0038 ],
0039 });
0040 config = TestBed.inject(ConfigService);
0041 });
0042
0043 describe('backend selection', () => {
0044 it('has no backend on a static deployment by default', () => {
0045 const service = createService();
0046 expect(service.isBackendAvailable()).toBe(false);
0047 expect(service.serverAddress()).toBe('');
0048 });
0049
0050 it('reads the keys the config page writes, with the page default URL', () => {
0051 const service = createService();
0052 useBackend();
0053 expect(service.isBackendAvailable()).toBe(true);
0054 expect(service.serverAddress()).toBe('http://localhost:5454');
0055 // One canonical property per key: the page and the service share it
0056 expect(config.getConfig('server.useApi')).toBe(config.declare(BACKEND_USE_API_CONFIG));
0057 });
0058
0059 it('follows a changed backend URL and drops a trailing slash', () => {
0060 const service = createService();
0061 useBackend('http://customserver:1234/');
0062 expect(service.serverAddress()).toBe('http://customserver:1234');
0063 });
0064
0065 it('takes the backend from a deep link (?config.server.useApi=true) before anything declared it', () => {
0066 config.applySessionValue('server.useApi', 'true');
0067 config.applySessionValue('server.url', 'http://linked:5454');
0068 const service = createService();
0069 expect(service.isBackendAvailable()).toBe(true);
0070 expect(service.serverAddress()).toBe('http://linked:5454');
0071 expect(localStorage.getItem('server.useApi')).toBeNull();
0072 });
0073
0074 it('keeps a saved choice across sessions', () => {
0075 localStorage.setItem('server.useApi', 'true');
0076 localStorage.setItem('server.url', 'http://saved:5454');
0077 const service = createService();
0078 expect(service.serverAddress()).toBe('http://saved:5454');
0079 });
0080
0081 it('prefers the serving pyrobird over a configured backend, once its config arrives', () => {
0082 const service = createService();
0083 useBackend('http://other:1234');
0084 serverConfig.set({ ...defaultFirebirdConfig, servedByPyrobird: true, apiAvailable: true, apiBaseUrl: 'http://served:5454' });
0085 expect(service.isBackendAvailable()).toBe(true);
0086 expect(service.serverAddress()).toBe('http://served:5454');
0087 });
0088
0089 it('ignores a blob: backend URL', () => {
0090 localStorage.setItem('server.useApi', 'true');
0091 localStorage.setItem('server.url', 'blob:http://localhost/5b1c');
0092 const service = createService();
0093 expect(service.serverAddress()).toBe(BACKEND_URL_CONFIG.default);
0094 });
0095 });
0096
0097 describe('resolveDownloadUrl', () => {
0098 it('uses the download endpoint for a plain path when a backend is configured (Case 1.2)', () => {
0099 const service = createService();
0100 useBackend();
0101 expect(service.resolveDownloadUrl('/path/to/file.root'))
0102 .toBe('http://localhost:5454/api/v1/download?f=%2Fpath%2Fto%2Ffile.root');
0103 });
0104
0105 it('resolves local:// through the download endpoint (Case 1.2)', () => {
0106 const service = createService();
0107 useBackend();
0108 expect(service.resolveDownloadUrl('local://subdir/file.root'))
0109 .toBe('http://localhost:5454/api/v1/download?f=subdir%2Ffile.root');
0110 });
0111
0112 it('passes a local:// path through unresolved when no backend is available', () => {
0113 const service = createService();
0114 expect(service.resolveDownloadUrl('local://file.root')).toBe('file.root');
0115 });
0116
0117 it('encodes the input URL in the download endpoint', () => {
0118 const service = createService();
0119 useBackend();
0120 expect(service.resolveDownloadUrl('/path with spaces/file.root'))
0121 .toBe('http://localhost:5454/api/v1/download?f=%2Fpath%20with%20spaces%2Ffile.root');
0122 });
0123
0124 it('leaves absolute URLs as they are (Case 1.1)', () => {
0125 const service = createService();
0126 useBackend();
0127 expect(service.resolveDownloadUrl('https://example.com/file.root')).toBe('https://example.com/file.root');
0128 });
0129 });
0130
0131 describe('resolveConvertUrl', () => {
0132 it('builds the convert URL when a backend is configured', () => {
0133 const service = createService();
0134 useBackend();
0135 const inputUrl = 'https://example.com/file.root';
0136 expect(service.resolveConvertUrl(inputUrl, 'edm4eic', 'all'))
0137 .toBe(`http://localhost:5454/api/v1/convert/edm4eic/all?f=${encodeURIComponent(inputUrl)}`);
0138 });
0139
0140 it('resolves asset:// before converting', () => {
0141 const service = createService();
0142 useBackend();
0143 const baseUri = document.baseURI.endsWith('/') ? document.baseURI : `${document.baseURI}/`;
0144 const resolvedAssetUrl = `${baseUri}assets/data/sample.dat`;
0145 expect(service.resolveConvertUrl('asset://data/sample.dat', 'edm4eic', 'all'))
0146 .toBe(`http://localhost:5454/api/v1/convert/edm4eic/all?f=${encodeURIComponent(resolvedAssetUrl)}`);
0147 });
0148
0149 it('resolves local:// before converting', () => {
0150 const service = createService();
0151 useBackend();
0152 expect(service.resolveConvertUrl('local://sim.edm4eic.root', 'edm4eic', '0'))
0153 .toBe('http://localhost:5454/api/v1/convert/edm4eic/0?f=sim.edm4eic.root');
0154 });
0155
0156 it('resolves a pack alias before converting', () => {
0157 const service = createService();
0158 useBackend();
0159 const resolvedAliasUrl = 'https://data.example.org/artifacts/some/path/file.root';
0160 expect(service.resolveConvertUrl('exp://some/path/file.root', 'edm4eic', 'all'))
0161 .toBe(`http://localhost:5454/api/v1/convert/edm4eic/all?f=${encodeURIComponent(resolvedAliasUrl)}`);
0162 });
0163
0164 it('adds the collection groups', () => {
0165 const service = createService();
0166 useBackend();
0167 expect(service.resolveConvertUrl('local://a.root', 'auto', '0', ['tracks', 'mc_particles']))
0168 .toBe('http://localhost:5454/api/v1/convert/auto/0?f=a.root&collections=tracks%2Cmc_particles');
0169 });
0170
0171 it('throws when no backend is available', () => {
0172 const service = createService();
0173 expect(() => service.resolveConvertUrl('local://a.root', 'auto', '0')).toThrowError(/Backend is not available/);
0174 });
0175 });
0176 });