1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
3 import { ActivatedRoute } from '@angular/router';
4 import { BehaviorSubject, of, throwError } from 'rxjs';
6 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
7 import { RouterTestingModule } from '@angular/router/testing';
9 import { CephModule } from '~/app/ceph/ceph.module';
10 import { CephSharedModule } from '~/app/ceph/shared/ceph-shared.module';
11 import { CoreModule } from '~/app/core/core.module';
12 import { HostService } from '~/app/shared/api/host.service';
13 import { NvmeofService } from '~/app/shared/api/nvmeof.service';
14 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
15 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
16 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
17 import { HostStatus } from '~/app/shared/enum/host-status.enum';
18 import { Permissions } from '~/app/shared/models/permissions';
19 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
20 import { SharedModule } from '~/app/shared/shared.module';
21 import { configureTestBed } from '~/testing/unit-test-helper';
22 import { TagModule } from 'carbon-components-angular';
23 import { NvmeofGatewayNodeComponent } from './nvmeof-gateway-node.component';
25 describe('NvmeofGatewayNodeComponent', () => {
26 let component: NvmeofGatewayNodeComponent;
27 let fixture: ComponentFixture<NvmeofGatewayNodeComponent>;
28 let hostService: HostService;
29 let orchService: OrchestratorService;
30 let nvmeofService: NvmeofService;
32 const fakeAuthStorageService = {
33 getPermissions: () => {
34 return new Permissions({ nvmeof: ['read', 'update', 'create', 'delete'] });
38 const mockGatewayNodes = [
40 hostname: 'gateway-node-1',
42 status: HostStatus.AVAILABLE,
43 labels: ['nvmeof', 'gateway'],
50 ceph_version: 'ceph version 18.0.0',
55 service_instances: [] as any[]
58 hostname: 'gateway-node-2',
60 status: HostStatus.MAINTENANCE,
68 ceph_version: 'ceph version 18.0.0',
73 service_instances: [] as any[]
76 hostname: 'gateway-node-3',
81 ceph_version: 'ceph version 18.0.0',
86 service_instances: [] as any[]
92 BrowserAnimationsModule,
95 HttpClientTestingModule,
102 { provide: AuthStorageService, useValue: fakeAuthStorageService },
104 provide: ActivatedRoute,
107 params: new BehaviorSubject({ group: 'group1' }),
109 params: { group: 'group1' }
113 data: { mode: 'selector' }
121 fixture = TestBed.createComponent(NvmeofGatewayNodeComponent);
122 component = fixture.componentInstance;
123 hostService = TestBed.inject(HostService);
124 orchService = TestBed.inject(OrchestratorService);
125 nvmeofService = TestBed.inject(NvmeofService);
128 it('should create', () => {
129 expect(component).toBeTruthy();
132 it('should initialize columns on component init', () => {
133 component.ngOnInit();
135 expect(component.columns).toBeDefined();
136 expect(component.columns.length).toBeGreaterThan(0);
137 expect(component.columns[0].name).toBe('Hostname');
138 expect(component.columns[0].prop).toBe('hostname');
141 it('should have all required columns defined', () => {
142 component.ngOnInit();
144 const columnProps = component.columns.map((col) => col.prop);
145 expect(columnProps).toContain('hostname');
146 expect(columnProps).toContain('addr');
147 expect(columnProps).toContain('status');
148 expect(columnProps).toContain('labels');
151 it('should initialize with default values', () => {
152 expect(component.hosts).toEqual([]);
153 expect(component.isLoadingHosts).toBe(false);
154 expect(component.count).toBe(0);
155 expect(component.permission).toBeDefined();
158 it('should update selection', () => {
159 const selection = new CdTableSelection();
160 selection.selected = [mockGatewayNodes[0]];
162 component.updateSelection(selection);
164 expect(component.selection).toBe(selection);
165 expect(component.selection.selected.length).toBe(1);
168 it('should get selected hosts', () => {
169 component.selection = new CdTableSelection();
170 component.selection.selected = [mockGatewayNodes[0], mockGatewayNodes[1]];
172 const selectedHosts = component.getSelectedHostnames();
174 expect(selectedHosts.length).toBe(2);
175 expect(selectedHosts[0]).toEqual(mockGatewayNodes[0].hostname);
176 expect(selectedHosts[1]).toEqual(mockGatewayNodes[1].hostname);
179 it('should get selected hostnames', () => {
180 component.selection = new CdTableSelection();
181 component.selection.selected = [mockGatewayNodes[0], mockGatewayNodes[1]];
183 const selectedHostnames = component.getSelectedHostnames();
185 expect(selectedHostnames).toEqual(['gateway-node-1', 'gateway-node-2']);
188 it('should load hosts with orchestrator available and facts feature enabled', fakeAsync(() => {
189 spyOn(nvmeofService, 'getAvailableHosts').and.returnValue(of([mockGatewayNodes[2]]));
190 component.getHosts(new CdTableFetchDataContext(() => undefined));
193 expect(nvmeofService.getAvailableHosts).toHaveBeenCalled();
194 expect(component.hosts.length).toBe(1);
195 expect(component.hosts[0]['hostname']).toBe('gateway-node-3');
198 it('should set count to hosts length', fakeAsync(() => {
199 spyOn(nvmeofService, 'getAvailableHosts').and.returnValue(of(mockGatewayNodes));
200 component.getHosts(new CdTableFetchDataContext(() => undefined));
203 expect(component.count).toBe(component.hosts.length);
206 it('should set count to 0 when no hosts are returned', fakeAsync(() => {
207 spyOn(nvmeofService, 'getAvailableHosts').and.returnValue(of([]));
208 component.getHosts(new CdTableFetchDataContext(() => undefined));
211 expect(component.count).toBe(0);
212 expect(component.hosts.length).toBe(0);
215 it('should handle error when fetching hosts', fakeAsync(() => {
216 spyOn(nvmeofService, 'getAvailableHosts').and.returnValue(throwError(() => new Error('Error')));
217 const context = new CdTableFetchDataContext(() => undefined);
218 spyOn(context, 'error');
220 component.getHosts(context);
223 expect(component.isLoadingHosts).toBe(false);
224 expect(context.error).toHaveBeenCalled();
227 it('should not re-fetch if already loading', fakeAsync(() => {
228 component.isLoadingHosts = true;
229 spyOn(nvmeofService, 'getAvailableHosts');
231 component.getHosts(new CdTableFetchDataContext(() => undefined));
234 expect(nvmeofService.getAvailableHosts).not.toHaveBeenCalled();
237 it('should unsubscribe on component destroy', fakeAsync(() => {
238 spyOn(hostService, 'list').and.returnValue(of([]));
239 spyOn(orchService, 'status').and.returnValue(of({} as any));
240 spyOn(nvmeofService, 'listGatewayGroups').and.returnValue(of([[]]));
241 component.getHosts(new CdTableFetchDataContext(() => undefined));
244 const sub = component['sub'];
245 spyOn(sub, 'unsubscribe');
247 component.ngOnDestroy();
249 expect(sub.unsubscribe).toHaveBeenCalled();
252 it('should handle host list with various label types', fakeAsync(() => {
253 const hostsWithLabels = [
255 ...mockGatewayNodes[0],
256 labels: ['nvmeof', 'gateway', 'high-priority']
259 ...mockGatewayNodes[2],
264 spyOn(hostService, 'list').and.returnValue(of(hostsWithLabels));
265 const mockOrcStatus: any = {
270 spyOn(orchService, 'status').and.returnValue(of(mockOrcStatus));
271 spyOn(nvmeofService, 'listGatewayGroups').and.returnValue(
275 service_id: 'nvmeof.group1',
276 placement: { hosts: ['gateway-node-2'] }
281 spyOn(hostService, 'checkHostsFactsAvailable').and.returnValue(true);
282 component.groupName = 'group1';
283 fixture.detectChanges();
285 component.getHosts(new CdTableFetchDataContext(() => undefined));
288 expect(component.hosts[0]['labels'].length).toBe(3);
289 expect(component.hosts[1]['labels'].length).toBe(0);
292 it('should handle hosts with multiple services', fakeAsync(() => {
293 const hostsWithServices = [
295 ...mockGatewayNodes[0],
297 { type: 'nvmeof-gw', id: 'gateway-1' },
298 { type: 'mon', id: '0' }
303 spyOn(hostService, 'list').and.returnValue(of(hostsWithServices));
304 const mockOrcStatus: any = {
309 spyOn(orchService, 'status').and.returnValue(of(mockOrcStatus));
310 spyOn(nvmeofService, 'listGatewayGroups').and.returnValue(
314 service_id: 'nvmeof.group1',
315 placement: { hosts: ['gateway-node-2'] }
320 spyOn(hostService, 'checkHostsFactsAvailable').and.returnValue(true);
321 component.groupName = 'group1';
322 fixture.detectChanges();
324 component.getHosts(new CdTableFetchDataContext(() => undefined));
327 expect(component.hosts[0]['services'].length).toBe(2);
330 it('should initialize table context on first getHosts call', fakeAsync(() => {
331 spyOn(hostService, 'list').and.returnValue(of(mockGatewayNodes));
332 const mockOrcStatus: any = {
337 spyOn(orchService, 'status').and.returnValue(of(mockOrcStatus));
338 spyOn(nvmeofService, 'listGatewayGroups').and.returnValue(
342 service_id: 'nvmeof.group1',
343 placement: { hosts: ['gateway-node-1', 'gateway-node-2'] }
348 spyOn(hostService, 'checkHostsFactsAvailable').and.returnValue(true);
349 component.groupName = 'group1';
350 fixture.detectChanges();
352 expect((component as any).tableContext).toBeUndefined();
354 component.getHosts(new CdTableFetchDataContext(() => undefined));
357 expect((component as any).tableContext).toBeDefined();
360 it('should reuse table context if already set', fakeAsync(() => {
361 const context = new CdTableFetchDataContext(() => undefined);
362 spyOn(hostService, 'list').and.returnValue(of(mockGatewayNodes));
363 const mockOrcStatus: any = {
368 spyOn(orchService, 'status').and.returnValue(of(mockOrcStatus));
369 spyOn(nvmeofService, 'listGatewayGroups').and.returnValue(of([[]]));
370 spyOn(hostService, 'checkHostsFactsAvailable').and.returnValue(true);
371 fixture.detectChanges();
373 component.getHosts(context);
376 const storedContext = (component as any).tableContext;
377 expect(storedContext).toBe(context);
380 it('should fetch data using fetchHostsAndGroups in details mode', fakeAsync(() => {
381 (component as any).route.snapshot.data = { mode: 'details' };
382 component.ngOnInit();
383 component.groupName = 'group1';
385 spyOn(nvmeofService, 'fetchHostsAndGroups').and.returnValue(
390 service_id: 'nvmeof.group1',
391 spec: { group: 'group1' },
392 placement: { hosts: ['gateway-node-1'] }
396 hosts: mockGatewayNodes
400 fixture.detectChanges();
401 component.getHosts(new CdTableFetchDataContext(() => undefined));
404 expect(nvmeofService.fetchHostsAndGroups).toHaveBeenCalled();
405 expect(component.hosts.length).toBe(1);
406 expect(component.hosts[0].hostname).toBe('gateway-node-1');
409 it('should set selectionType to multiClick in selector mode', () => {
410 (component as any).route.snapshot.data = { mode: 'selector' };
411 component.ngOnInit();
412 expect(component.selectionType).toBe('multiClick');
415 it('should set selectionType to single in details mode', () => {
416 (component as any).route.snapshot.data = { mode: 'details' };
417 component.ngOnInit();
418 expect(component.selectionType).toBe('single');