1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
3 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 import { RouterTestingModule } from '@angular/router/testing';
6 import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
7 import { MockComponent } from 'ng-mocks';
8 import { ToastrModule } from 'ngx-toastr';
9 import { Subject, throwError as observableThrowError } from 'rxjs';
11 import { RbdService } from '~/app/shared/api/rbd.service';
12 import { ComponentsModule } from '~/app/shared/components/components.module';
13 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
14 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
15 import { DataTableModule } from '~/app/shared/datatable/datatable.module';
16 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
17 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18 import { ExecutingTask } from '~/app/shared/models/executing-task';
19 import { Permissions } from '~/app/shared/models/permissions';
20 import { PipesModule } from '~/app/shared/pipes/pipes.module';
21 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
22 import { NotificationService } from '~/app/shared/services/notification.service';
23 import { SummaryService } from '~/app/shared/services/summary.service';
24 import { TaskListService } from '~/app/shared/services/task-list.service';
25 import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
26 import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
27 import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
28 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
29 import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
30 import { RbdSnapshotModel } from './rbd-snapshot.model';
31 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
39 } from 'carbon-components-angular';
40 import { NO_ERRORS_SCHEMA } from '@angular/core';
41 import { CoreModule } from '~/app/core/core.module';
43 describe('RbdSnapshotListComponent', () => {
44 let component: RbdSnapshotListComponent;
45 let fixture: ComponentFixture<RbdSnapshotListComponent>;
46 let summaryService: SummaryService;
47 let modalService: ModalCdsService;
49 const fakeAuthStorageService = {
53 getPermissions: () => {
54 return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
61 RbdSnapshotListComponent,
63 MockComponent(RbdSnapshotFormModalComponent),
67 BrowserAnimationsModule,
70 HttpClientTestingModule,
74 ToastrModule.forRoot(),
80 { provide: AuthStorageService, useValue: fakeAuthStorageService },
86 schemas: [NO_ERRORS_SCHEMA]
88 [CriticalConfirmationModalComponent]
92 fixture = TestBed.createComponent(RbdSnapshotListComponent);
93 component = fixture.componentInstance;
95 // Access the component's native element
96 const element = fixture.nativeElement;
98 // Dynamically create and append the cds-placeholder element
99 const cdsPlaceholder = document.createElement('cds-placeholder');
100 element.appendChild(cdsPlaceholder);
102 // Trigger change detection to update the view
103 fixture.detectChanges();
104 component.ngOnChanges();
105 summaryService = TestBed.inject(SummaryService);
108 it('should create', () => {
109 fixture.detectChanges();
110 expect(component).toBeTruthy();
113 describe('api delete request', () => {
115 let rbdService: RbdService;
116 let notificationService: NotificationService;
117 let authStorageService: AuthStorageService;
120 fixture.detectChanges();
121 const modalService = TestBed.inject(ModalCdsService);
122 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
124 rbdService = new RbdService(null, null);
125 notificationService = new NotificationService(null, null, null);
126 authStorageService = new AuthStorageService();
127 authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
128 component = new RbdSnapshotListComponent(
141 spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
142 spyOn(notificationService, 'notifyTask').and.stub();
143 spyOn(modalService, 'stopLoadingSpinner').and.stub();
146 // @TODO: fix this later. fails with the new cds modal.
147 // disabling this for now.
148 it.skip('should call stopLoadingSpinner if the request fails', fakeAsync(() => {
149 // expect(container.querySelector('cds-placeholder')).not.toBeNull();
150 component.updateSelection(new CdTableSelection([{ name: 'someName' }]));
151 expect(called).toBe(false);
152 component.deleteSnapshotModal();
153 component.modalRef.snapshotForm = { value: { snapName: 'someName' } };
154 component.modalRef.submitAction();
156 spyOn(modalService, 'stopLoadingSpinner').and.callFake(() => {
159 expect(called).toBe(true);
163 describe('handling of executing tasks', () => {
164 let snapshots: RbdSnapshotModel[];
166 const addSnapshot = (name: string) => {
167 const model = new RbdSnapshotModel();
170 snapshots.push(model);
173 const addTask = (task_name: string, snapshot_name: string) => {
174 const task = new ExecutingTask();
175 task.name = task_name;
177 image_spec: 'rbd/foo',
178 snapshot_name: snapshot_name
180 summaryService.addRunningTask(task);
183 const refresh = (data: any) => {
184 summaryService['summaryDataSource'].next(data);
188 fixture.detectChanges();
193 component.snapshots = snapshots;
194 component.poolName = 'rbd';
195 component.rbdName = 'foo';
196 refresh({ executing_tasks: [], finished_tasks: [] });
197 component.ngOnChanges();
198 fixture.detectChanges();
201 it('should gets all snapshots without tasks', () => {
202 expect(component.snapshots.length).toBe(3);
203 expect(component.snapshots.every((image) => !image.cdExecuting)).toBeTruthy();
206 it('should add a new image from a task', () => {
207 addTask('rbd/snap/create', 'd');
208 expect(component.snapshots.length).toBe(4);
209 expectItemTasks(component.snapshots[0], undefined);
210 expectItemTasks(component.snapshots[1], undefined);
211 expectItemTasks(component.snapshots[2], undefined);
212 expectItemTasks(component.snapshots[3], 'Creating');
215 it('should show when an existing image is being modified', () => {
216 addTask('rbd/snap/edit', 'a');
217 addTask('rbd/snap/delete', 'b');
218 addTask('rbd/snap/rollback', 'c');
219 expect(component.snapshots.length).toBe(3);
220 expectItemTasks(component.snapshots[0], 'Updating');
221 expectItemTasks(component.snapshots[1], 'Deleting');
222 expectItemTasks(component.snapshots[2], 'Rolling back');
226 // cds-modal opening fails in the unit tests. since e2e is already there, disabling this.
227 // @TODO: should be fixed later on
228 describe.skip('snapshot modal dialog', () => {
230 component.poolName = 'pool01';
231 component.rbdName = 'image01';
232 spyOn(TestBed.inject(ModalService), 'show').and.callFake(() => {
234 ref.componentInstance = new RbdSnapshotFormModalComponent(
239 TestBed.inject(ActionLabelsI18n),
243 ref.componentInstance.onSubmit = new Subject();
248 it('should display old snapshot name', () => {
249 component.selection.selected = [{ name: 'oldname' }];
250 component.openEditSnapshotModal();
251 expect(component.modalRef.componentInstance.snapName).toBe('oldname');
252 expect(component.modalRef.componentInstance.editing).toBeTruthy();
255 it('should display suggested snapshot name', () => {
256 component.openCreateSnapshotModal();
257 expect(component.modalRef.componentInstance.snapName).toMatch(
258 RegExp(`^${component.rbdName}_[\\d-]+T[\\d.:]+[\\+-][\\d:]+$`)
263 it('should test all TableActions combinations', () => {
264 component.ngOnInit();
265 const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
266 const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
267 component.tableActions
270 expect(tableActions).toEqual({
271 'create,update,delete': {
290 actions: ['Create', 'Rename', 'Protect', 'Unprotect', 'Clone', 'Copy', 'Rollback'],
299 actions: ['Create', 'Clone', 'Copy', 'Delete'],
308 actions: ['Create', 'Clone', 'Copy'],
317 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback', 'Delete'],
326 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback'],
355 describe('clone button disable state', () => {
356 let actions: RbdSnapshotActionsModel;
359 fixture.detectChanges();
360 const rbdService = TestBed.inject(RbdService);
361 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
362 actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
365 it('should be disabled with version 1 and protected false', () => {
366 const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
367 const disableDesc = actions.getCloneDisableDesc(selection);
368 expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
375 ])('should be enabled with version %d and protected %s', (version, is_protected) => {
376 actions.cloneFormatVersion = version;
377 const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
378 const disableDesc = actions.getCloneDisableDesc(selection);
379 expect(disableDesc).toBe(false);
383 describe('protect button disable state', () => {
384 let actions: RbdSnapshotActionsModel;
387 fixture.detectChanges();
388 const rbdService = TestBed.inject(RbdService);
389 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
390 actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
393 it('should be disabled if layering not supported', () => {
394 const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
395 const disableDesc = actions.getProtectDisableDesc(selection, ['deep-flatten', 'fast-diff']);
396 expect(disableDesc).toBe('The layering feature needs to be enabled on parent image');