]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
ca72007ee81e5b973d5b6dd852318b488d895dc0
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-snapshot-list / rbd-snapshot-list.component.spec.ts
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';
5
6 import { NgbModalModule, 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';
10
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 { ModalService } from '~/app/shared/services/modal.service';
23 import { NotificationService } from '~/app/shared/services/notification.service';
24 import { SummaryService } from '~/app/shared/services/summary.service';
25 import { TaskListService } from '~/app/shared/services/task-list.service';
26 import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
27 import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
28 import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
29 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
30 import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
31 import { RbdSnapshotModel } from './rbd-snapshot.model';
32
33 describe('RbdSnapshotListComponent', () => {
34 let component: RbdSnapshotListComponent;
35 let fixture: ComponentFixture<RbdSnapshotListComponent>;
36 let summaryService: SummaryService;
37
38 const fakeAuthStorageService = {
39 isLoggedIn: () => {
40 return true;
41 },
42 getPermissions: () => {
43 return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
44 }
45 };
46
47 configureTestBed(
48 {
49 declarations: [
50 RbdSnapshotListComponent,
51 RbdTabsComponent,
52 MockComponent(RbdSnapshotFormModalComponent)
53 ],
54 imports: [
55 BrowserAnimationsModule,
56 ComponentsModule,
57 DataTableModule,
58 HttpClientTestingModule,
59 PipesModule,
60 RouterTestingModule,
61 NgbNavModule,
62 ToastrModule.forRoot(),
63 NgbModalModule
64 ],
65 providers: [
66 { provide: AuthStorageService, useValue: fakeAuthStorageService },
67 TaskListService
68 ]
69 },
70 [CriticalConfirmationModalComponent]
71 );
72
73 beforeEach(() => {
74 fixture = TestBed.createComponent(RbdSnapshotListComponent);
75 component = fixture.componentInstance;
76 component.ngOnChanges();
77 summaryService = TestBed.inject(SummaryService);
78 });
79
80 it('should create', () => {
81 fixture.detectChanges();
82 expect(component).toBeTruthy();
83 });
84
85 describe('api delete request', () => {
86 let called: boolean;
87 let rbdService: RbdService;
88 let notificationService: NotificationService;
89 let authStorageService: AuthStorageService;
90
91 beforeEach(() => {
92 fixture.detectChanges();
93 const modalService = TestBed.inject(ModalService);
94 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
95 called = false;
96 rbdService = new RbdService(null, null);
97 notificationService = new NotificationService(null, null, null);
98 authStorageService = new AuthStorageService();
99 authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
100 component = new RbdSnapshotListComponent(
101 authStorageService,
102 modalService,
103 null,
104 null,
105 rbdService,
106 null,
107 notificationService,
108 null,
109 null,
110 actionLabelsI18n,
111 null
112 );
113 spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
114 spyOn(notificationService, 'notifyTask').and.stub();
115 });
116
117 it('should call stopLoadingSpinner if the request fails', fakeAsync(() => {
118 component.updateSelection(new CdTableSelection([{ name: 'someName' }]));
119 expect(called).toBe(false);
120 component.deleteSnapshotModal();
121 spyOn(component.modalRef.componentInstance, 'stopLoadingSpinner').and.callFake(() => {
122 called = true;
123 });
124 component.modalRef.componentInstance.submitAction();
125 tick(500);
126 expect(called).toBe(true);
127 }));
128 });
129
130 describe('handling of executing tasks', () => {
131 let snapshots: RbdSnapshotModel[];
132
133 const addSnapshot = (name: string) => {
134 const model = new RbdSnapshotModel();
135 model.id = 1;
136 model.name = name;
137 snapshots.push(model);
138 };
139
140 const addTask = (task_name: string, snapshot_name: string) => {
141 const task = new ExecutingTask();
142 task.name = task_name;
143 task.metadata = {
144 image_spec: 'rbd/foo',
145 snapshot_name: snapshot_name
146 };
147 summaryService.addRunningTask(task);
148 };
149
150 const refresh = (data: any) => {
151 summaryService['summaryDataSource'].next(data);
152 };
153
154 beforeEach(() => {
155 fixture.detectChanges();
156 snapshots = [];
157 addSnapshot('a');
158 addSnapshot('b');
159 addSnapshot('c');
160 component.snapshots = snapshots;
161 component.poolName = 'rbd';
162 component.rbdName = 'foo';
163 refresh({ executing_tasks: [], finished_tasks: [] });
164 component.ngOnChanges();
165 fixture.detectChanges();
166 });
167
168 it('should gets all snapshots without tasks', () => {
169 expect(component.snapshots.length).toBe(3);
170 expect(component.snapshots.every((image) => !image.cdExecuting)).toBeTruthy();
171 });
172
173 it('should add a new image from a task', () => {
174 addTask('rbd/snap/create', 'd');
175 expect(component.snapshots.length).toBe(4);
176 expectItemTasks(component.snapshots[0], undefined);
177 expectItemTasks(component.snapshots[1], undefined);
178 expectItemTasks(component.snapshots[2], undefined);
179 expectItemTasks(component.snapshots[3], 'Creating');
180 });
181
182 it('should show when an existing image is being modified', () => {
183 addTask('rbd/snap/edit', 'a');
184 addTask('rbd/snap/delete', 'b');
185 addTask('rbd/snap/rollback', 'c');
186 expect(component.snapshots.length).toBe(3);
187 expectItemTasks(component.snapshots[0], 'Updating');
188 expectItemTasks(component.snapshots[1], 'Deleting');
189 expectItemTasks(component.snapshots[2], 'Rolling back');
190 });
191 });
192
193 describe('snapshot modal dialog', () => {
194 beforeEach(() => {
195 component.poolName = 'pool01';
196 component.rbdName = 'image01';
197 spyOn(TestBed.inject(ModalService), 'show').and.callFake(() => {
198 const ref: any = {};
199 ref.componentInstance = new RbdSnapshotFormModalComponent(
200 null,
201 null,
202 null,
203 null,
204 TestBed.inject(ActionLabelsI18n)
205 );
206 ref.componentInstance.onSubmit = new Subject();
207 return ref;
208 });
209 });
210
211 it('should display old snapshot name', () => {
212 component.selection.selected = [{ name: 'oldname' }];
213 component.openEditSnapshotModal();
214 expect(component.modalRef.componentInstance.snapName).toBe('oldname');
215 expect(component.modalRef.componentInstance.editing).toBeTruthy();
216 });
217
218 it('should display suggested snapshot name', () => {
219 component.openCreateSnapshotModal();
220 expect(component.modalRef.componentInstance.snapName).toMatch(
221 RegExp(`^${component.rbdName}_[\\d-]+T[\\d.:]+[\\+-][\\d:]+$`)
222 );
223 });
224 });
225
226 it('should test all TableActions combinations', () => {
227 component.ngOnInit();
228 const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
229 const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
230 component.tableActions
231 );
232
233 expect(tableActions).toEqual({
234 'create,update,delete': {
235 actions: [
236 'Create',
237 'Rename',
238 'Protect',
239 'Unprotect',
240 'Clone',
241 'Copy',
242 'Rollback',
243 'Delete'
244 ],
245 primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
246 },
247 'create,update': {
248 actions: ['Create', 'Rename', 'Protect', 'Unprotect', 'Clone', 'Copy', 'Rollback'],
249 primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
250 },
251 'create,delete': {
252 actions: ['Create', 'Clone', 'Copy', 'Delete'],
253 primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
254 },
255 create: {
256 actions: ['Create', 'Clone', 'Copy'],
257 primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
258 },
259 'update,delete': {
260 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback', 'Delete'],
261 primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
262 },
263 update: {
264 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback'],
265 primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
266 },
267 delete: {
268 actions: ['Delete'],
269 primary: { multiple: 'Delete', executing: 'Delete', single: 'Delete', no: 'Delete' }
270 },
271 'no-permissions': {
272 actions: [],
273 primary: { multiple: '', executing: '', single: '', no: '' }
274 }
275 });
276 });
277
278 describe('clone button disable state', () => {
279 let actions: RbdSnapshotActionsModel;
280
281 beforeEach(() => {
282 fixture.detectChanges();
283 const rbdService = TestBed.inject(RbdService);
284 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
285 actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
286 });
287
288 it('should be disabled with version 1 and protected false', () => {
289 const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
290 const disableDesc = actions.getCloneDisableDesc(selection, ['layering']);
291 expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
292 });
293
294 it.each([
295 [1, true],
296 [2, true],
297 [2, false]
298 ])('should be enabled with version %d and protected %s', (version, is_protected) => {
299 actions.cloneFormatVersion = version;
300 const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
301 const disableDesc = actions.getCloneDisableDesc(selection, ['layering']);
302 expect(disableDesc).toBe(false);
303 });
304 });
305 });