]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
bump version to 19.2.0-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-snapshot-list / rbd-snapshot-list.component.spec.ts
CommitLineData
11fdf7f2
TL
1import { HttpClientTestingModule } from '@angular/common/http/testing';
2import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
e306af50 3import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
11fdf7f2
TL
4import { RouterTestingModule } from '@angular/router/testing';
5
f67539c2
TL
6import { NgbModalModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
7import { MockComponent } from 'ng-mocks';
494da23a 8import { ToastrModule } from 'ngx-toastr';
11fdf7f2
TL
9import { Subject, throwError as observableThrowError } from 'rxjs';
10
f67539c2
TL
11import { RbdService } from '~/app/shared/api/rbd.service';
12import { ComponentsModule } from '~/app/shared/components/components.module';
13import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
14import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
15import { DataTableModule } from '~/app/shared/datatable/datatable.module';
16import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
17import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18import { ExecutingTask } from '~/app/shared/models/executing-task';
19import { Permissions } from '~/app/shared/models/permissions';
20import { PipesModule } from '~/app/shared/pipes/pipes.module';
21import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
22import { ModalService } from '~/app/shared/services/modal.service';
23import { NotificationService } from '~/app/shared/services/notification.service';
24import { SummaryService } from '~/app/shared/services/summary.service';
25import { TaskListService } from '~/app/shared/services/task-list.service';
26import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
9f95a23c
TL
27import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
28import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
f67539c2 29import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
11fdf7f2
TL
30import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
31import { RbdSnapshotModel } from './rbd-snapshot.model';
32
33describe('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
f67539c2
TL
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 );
11fdf7f2
TL
72
73 beforeEach(() => {
74 fixture = TestBed.createComponent(RbdSnapshotListComponent);
75 component = fixture.componentInstance;
9f95a23c 76 component.ngOnChanges();
f67539c2 77 summaryService = TestBed.inject(SummaryService);
11fdf7f2
TL
78 });
79
80 it('should create', () => {
81 fixture.detectChanges();
82 expect(component).toBeTruthy();
83 });
84
85 describe('api delete request', () => {
9f95a23c 86 let called: boolean;
11fdf7f2
TL
87 let rbdService: RbdService;
88 let notificationService: NotificationService;
89 let authStorageService: AuthStorageService;
90
91 beforeEach(() => {
92 fixture.detectChanges();
f67539c2
TL
93 const modalService = TestBed.inject(ModalService);
94 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
11fdf7f2
TL
95 called = false;
96 rbdService = new RbdService(null, null);
97 notificationService = new NotificationService(null, null, null);
98 authStorageService = new AuthStorageService();
adb31ebb 99 authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
11fdf7f2
TL
100 component = new RbdSnapshotListComponent(
101 authStorageService,
f67539c2 102 modalService,
11fdf7f2
TL
103 null,
104 null,
105 rbdService,
106 null,
107 notificationService,
108 null,
109 null,
f67539c2
TL
110 actionLabelsI18n,
111 null
11fdf7f2
TL
112 );
113 spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
114 spyOn(notificationService, 'notifyTask').and.stub();
11fdf7f2
TL
115 });
116
801d1391 117 it('should call stopLoadingSpinner if the request fails', fakeAsync(() => {
f67539c2 118 component.updateSelection(new CdTableSelection([{ name: 'someName' }]));
11fdf7f2 119 expect(called).toBe(false);
f67539c2
TL
120 component.deleteSnapshotModal();
121 spyOn(component.modalRef.componentInstance, 'stopLoadingSpinner').and.callFake(() => {
122 called = true;
123 });
124 component.modalRef.componentInstance.submitAction();
11fdf7f2
TL
125 tick(500);
126 expect(called).toBe(true);
127 }));
128 });
129
130 describe('handling of executing tasks', () => {
131 let snapshots: RbdSnapshotModel[];
132
9f95a23c 133 const addSnapshot = (name: string) => {
11fdf7f2
TL
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 = {
9f95a23c 144 image_spec: 'rbd/foo',
11fdf7f2
TL
145 snapshot_name: snapshot_name
146 };
147 summaryService.addRunningTask(task);
148 };
149
9f95a23c 150 const refresh = (data: any) => {
11fdf7f2
TL
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);
eafe8130
TL
176 expectItemTasks(component.snapshots[0], undefined);
177 expectItemTasks(component.snapshots[1], undefined);
178 expectItemTasks(component.snapshots[2], undefined);
179 expectItemTasks(component.snapshots[3], 'Creating');
11fdf7f2
TL
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);
eafe8130
TL
187 expectItemTasks(component.snapshots[0], 'Updating');
188 expectItemTasks(component.snapshots[1], 'Deleting');
189 expectItemTasks(component.snapshots[2], 'Rolling back');
11fdf7f2
TL
190 });
191 });
192
193 describe('snapshot modal dialog', () => {
194 beforeEach(() => {
195 component.poolName = 'pool01';
196 component.rbdName = 'image01';
f67539c2
TL
197 spyOn(TestBed.inject(ModalService), 'show').and.callFake(() => {
198 const ref: any = {};
199 ref.componentInstance = new RbdSnapshotFormModalComponent(
92f5a8d4
TL
200 null,
201 null,
202 null,
203 null,
1e59de90
TL
204 TestBed.inject(ActionLabelsI18n),
205 null
92f5a8d4 206 );
f67539c2 207 ref.componentInstance.onSubmit = new Subject();
11fdf7f2
TL
208 return ref;
209 });
210 });
211
212 it('should display old snapshot name', () => {
213 component.selection.selected = [{ name: 'oldname' }];
11fdf7f2 214 component.openEditSnapshotModal();
f67539c2
TL
215 expect(component.modalRef.componentInstance.snapName).toBe('oldname');
216 expect(component.modalRef.componentInstance.editing).toBeTruthy();
11fdf7f2
TL
217 });
218
219 it('should display suggested snapshot name', () => {
220 component.openCreateSnapshotModal();
f67539c2 221 expect(component.modalRef.componentInstance.snapName).toMatch(
9f95a23c 222 RegExp(`^${component.rbdName}_[\\d-]+T[\\d.:]+[\\+-][\\d:]+$`)
11fdf7f2
TL
223 );
224 });
225 });
226
9f95a23c 227 it('should test all TableActions combinations', () => {
f67539c2 228 component.ngOnInit();
9f95a23c
TL
229 const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
230 const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
231 component.tableActions
232 );
233
234 expect(tableActions).toEqual({
235 'create,update,delete': {
236 actions: [
237 'Create',
238 'Rename',
239 'Protect',
240 'Unprotect',
241 'Clone',
242 'Copy',
243 'Rollback',
244 'Delete'
245 ],
246 primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
247 },
248 'create,update': {
249 actions: ['Create', 'Rename', 'Protect', 'Unprotect', 'Clone', 'Copy', 'Rollback'],
250 primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
251 },
252 'create,delete': {
253 actions: ['Create', 'Clone', 'Copy', 'Delete'],
254 primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
255 },
256 create: {
257 actions: ['Create', 'Clone', 'Copy'],
258 primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
259 },
260 'update,delete': {
261 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback', 'Delete'],
262 primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
263 },
264 update: {
265 actions: ['Rename', 'Protect', 'Unprotect', 'Rollback'],
266 primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
267 },
268 delete: {
269 actions: ['Delete'],
270 primary: { multiple: 'Delete', executing: 'Delete', single: 'Delete', no: 'Delete' }
271 },
272 'no-permissions': {
273 actions: [],
274 primary: { multiple: '', executing: '', single: '', no: '' }
275 }
11fdf7f2
TL
276 });
277 });
f67539c2
TL
278
279 describe('clone button disable state', () => {
280 let actions: RbdSnapshotActionsModel;
281
282 beforeEach(() => {
283 fixture.detectChanges();
284 const rbdService = TestBed.inject(RbdService);
285 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
286 actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
287 });
288
289 it('should be disabled with version 1 and protected false', () => {
290 const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
aee94f69 291 const disableDesc = actions.getCloneDisableDesc(selection);
f67539c2
TL
292 expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
293 });
294
295 it.each([
296 [1, true],
297 [2, true],
298 [2, false]
299 ])('should be enabled with version %d and protected %s', (version, is_protected) => {
300 actions.cloneFormatVersion = version;
301 const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
aee94f69 302 const disableDesc = actions.getCloneDisableDesc(selection);
f67539c2
TL
303 expect(disableDesc).toBe(false);
304 });
305 });
aee94f69
TL
306
307 describe('protect button disable state', () => {
308 let actions: RbdSnapshotActionsModel;
309
310 beforeEach(() => {
311 fixture.detectChanges();
312 const rbdService = TestBed.inject(RbdService);
313 const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
314 actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
315 });
316
317 it('should be disabled if layering not supported', () => {
318 const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
319 const disableDesc = actions.getProtectDisableDesc(selection, ['deep-flatten', 'fast-diff']);
320 expect(disableDesc).toBe('The layering feature needs to be enabled on parent image');
321 });
322 });
11fdf7f2 323});