Warning, /firebird/packages/firebird-ng/display/src/lib/services/root-file.service.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * RootFileService handles: each client works on its own handle id, closing a
0003 * handle touches only that handle, and overlapping opens show the latest.
0004 * The worker is replaced by a fake that records requests and answers on
0005 * demand.
0006 */
0007
0008 import { TestBed } from '@angular/core/testing';
0009 import { provideHttpClient } from '@angular/common/http';
0010 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
0011 import { ServerConfigService } from '@dexvis/app-features';
0012 import { RootFileService } from './root-file.service';
0013 import { withWorkers } from '@dexvis/firebird-ng/api';
0014 import type { RootFileRequest, RootFileResponse } from '@dexvis/firebird-ng/workers/root-file';
0015
0016 class FakeWorker {
0017 static instances: FakeWorker[] = [];
0018 readonly requests: RootFileRequest[] = [];
0019 onmessage: ((event: { data: RootFileResponse }) => void) | null = null;
0020 onerror: ((event: { message: string }) => void) | null = null;
0021 terminated = false;
0022
0023 constructor() {
0024 FakeWorker.instances.push(this);
0025 }
0026
0027 postMessage(request: RootFileRequest): void {
0028 this.requests.push(request);
0029 }
0030
0031 terminate(): void {
0032 this.terminated = true;
0033 }
0034
0035 /** Answers an open request. */
0036 opened(request: RootFileRequest, entryCount: number): void {
0037 this.onmessage?.({
0038 data: {
0039 type: 'opened',
0040 requestId: request.requestId,
0041 sourceName: `file-${request.requestId}`,
0042 model: 'edm4eic',
0043 entryCount,
0044 collectionGroups: ['tracker_hits'],
0045 },
0046 });
0047 }
0048 }
0049
0050 describe('RootFileService handles', () => {
0051 let service: RootFileService;
0052 const worker = () => FakeWorker.instances[0];
0053
0054 beforeEach(() => {
0055 FakeWorker.instances = [];
0056 vi.stubGlobal('Worker', FakeWorker);
0057 TestBed.resetTestingModule();
0058 const startFake = () => new FakeWorker() as unknown as Worker;
0059 TestBed.configureTestingModule({
0060 providers: [provideHttpClient(), ...withWorkers({ geometry: startFake, rootFile: startFake }).providers],
0061 });
0062 TestBed.inject(ServerConfigService).setUnitTestConfig({});
0063 service = TestBed.inject(RootFileService);
0064 });
0065
0066 afterEach(() => vi.unstubAllGlobals());
0067
0068 it('sends each handle its own id, and closing one rejects only its own requests', async () => {
0069 const picker = service.createHandle();
0070 const display = service.createHandle();
0071 const pickerOpen = picker.open('https://h/a.root');
0072 const displayOpen = display.open('https://h/b.root');
0073 const [openA, openB] = worker().requests;
0074 expect(openA).toEqual(expect.objectContaining({ type: 'open', handle: picker.id }));
0075 expect(openB).toEqual(expect.objectContaining({ type: 'open', handle: display.id }));
0076 expect(picker.id).not.toBe(display.id);
0077
0078 picker.close();
0079 await expect(pickerOpen).rejects.toThrow('The ROOT file was closed');
0080 expect(worker().requests[2]).toEqual(expect.objectContaining({ type: 'close', handle: picker.id }));
0081 expect(worker().terminated).toBe(false);
0082
0083 worker().opened(openB, 7);
0084 expect((await displayOpen).entryCount).toBe(7);
0085 expect(display.openedFile()?.entryCount).toBe(7);
0086 expect(display.busy()).toBe(false);
0087 });
0088
0089 it('sends the conversion options with the convert request', () => {
0090 const handle = service.createHandle();
0091 void handle.convert('0-2', { collections: ['mc_trajectories'], trajectoryExcludedCollections: ['PhotonHits'] });
0092 expect(worker().requests[0]).toEqual(expect.objectContaining({
0093 type: 'convert',
0094 handle: handle.id,
0095 entries: '0-2',
0096 collections: ['mc_trajectories'],
0097 trajectoryExcludedCollections: ['PhotonHits'],
0098 }));
0099 });
0100
0101 it('shows the latest open of a handle when an older one answers later', async () => {
0102 const handle = service.createHandle();
0103 const first = handle.open('https://h/old.root');
0104 const second = handle.open('https://h/new.root');
0105 const [openOld, openNew] = worker().requests;
0106 worker().opened(openNew, 2);
0107 await second;
0108 worker().opened(openOld, 99);
0109 await first;
0110 expect(handle.openedFile()?.entryCount).toBe(2);
0111 });
0112
0113 it('starts a fresh worker after the worker failed', async () => {
0114 const handle = service.createHandle();
0115 const open = handle.open('https://h/a.root');
0116 worker().onerror?.({ message: 'chunk failed to load' });
0117 await expect(open).rejects.toThrow('ROOT file worker error: chunk failed to load');
0118 expect(worker().terminated).toBe(true);
0119 void handle.open('https://h/a.root').catch(() => undefined);
0120 expect(FakeWorker.instances.length).toBe(2);
0121 });
0122
0123 it('names the missing feature when the application registered no workers', async () => {
0124 TestBed.resetTestingModule();
0125 TestBed.configureTestingModule({ providers: [provideHttpClient()] });
0126 TestBed.inject(ServerConfigService).setUnitTestConfig({});
0127 const bare = TestBed.inject(RootFileService);
0128 await expect(bare.probe('https://h/a.root')).rejects.toThrow('add withWorkers');
0129 });
0130 });