]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
72a37309c4eb7684dd97c2ef959c2742a8ac5c22
[ceph-ci.git] /
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
3 import { ReactiveFormsModule } from '@angular/forms';
4 import { Router } from '@angular/router';
5 import { RouterTestingModule } from '@angular/router/testing';
6
7 import _ from 'lodash';
8 import { ToastrModule } from 'ngx-toastr';
9 import { of as observableOf, throwError } from 'rxjs';
10
11 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
12 import { RgwSiteService } from '~/app/shared/api/rgw-site.service';
13 import { RgwUserService } from '~/app/shared/api/rgw-user.service';
14 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
15 import { NotificationService } from '~/app/shared/services/notification.service';
16 import { SharedModule } from '~/app/shared/shared.module';
17 import { configureTestBed, FormHelper } from '~/testing/unit-test-helper';
18 import { RgwBucketMfaDelete } from '../models/rgw-bucket-mfa-delete';
19 import { RgwBucketVersioning } from '../models/rgw-bucket-versioning';
20 import { RgwBucketFormComponent } from './rgw-bucket-form.component';
21
22 describe('RgwBucketFormComponent', () => {
23   let component: RgwBucketFormComponent;
24   let fixture: ComponentFixture<RgwBucketFormComponent>;
25   let rgwBucketService: RgwBucketService;
26   let getPlacementTargetsSpy: jasmine.Spy;
27   let rgwBucketServiceGetSpy: jasmine.Spy;
28   let enumerateSpy: jasmine.Spy;
29   let formHelper: FormHelper;
30
31   configureTestBed({
32     declarations: [RgwBucketFormComponent],
33     imports: [
34       HttpClientTestingModule,
35       ReactiveFormsModule,
36       RouterTestingModule,
37       SharedModule,
38       ToastrModule.forRoot()
39     ]
40   });
41
42   beforeEach(() => {
43     fixture = TestBed.createComponent(RgwBucketFormComponent);
44     component = fixture.componentInstance;
45     rgwBucketService = TestBed.inject(RgwBucketService);
46     rgwBucketServiceGetSpy = spyOn(rgwBucketService, 'get');
47     getPlacementTargetsSpy = spyOn(TestBed.inject(RgwSiteService), 'get');
48     enumerateSpy = spyOn(TestBed.inject(RgwUserService), 'enumerate');
49     formHelper = new FormHelper(component.bucketForm);
50   });
51
52   it('should create', () => {
53     expect(component).toBeTruthy();
54   });
55
56   describe('bucketNameValidator', () => {
57     const testValidator = (name: string, valid: boolean, expectedError?: string) => {
58       rgwBucketServiceGetSpy.and.returnValue(throwError('foo'));
59       formHelper.setValue('bid', name, true);
60       tick();
61       if (valid) {
62         formHelper.expectValid('bid');
63       } else {
64         formHelper.expectError('bid', expectedError);
65       }
66     };
67
68     it('should validate empty name', fakeAsync(() => {
69       formHelper.expectErrorChange('bid', '', 'required', true);
70     }));
71
72     it('bucket names cannot be formatted as IP address', fakeAsync(() => {
73       const testIPs = ['1.1.1.01', '001.1.1.01', '127.0.0.1'];
74       for (const ip of testIPs) {
75         testValidator(ip, false, 'ipAddress');
76       }
77     }));
78
79     it('bucket name must be >= 3 characters long (1/2)', fakeAsync(() => {
80       testValidator('ab', false, 'shouldBeInRange');
81     }));
82
83     it('bucket name must be >= 3 characters long (2/2)', fakeAsync(() => {
84       testValidator('abc', true);
85     }));
86
87     it('bucket name must be <= than 63 characters long (1/2)', fakeAsync(() => {
88       testValidator(_.repeat('a', 64), false, 'shouldBeInRange');
89     }));
90
91     it('bucket name must be <= than 63 characters long (2/2)', fakeAsync(() => {
92       testValidator(_.repeat('a', 63), true);
93     }));
94
95     it('bucket names must not contain uppercase characters or underscores (1/2)', fakeAsync(() => {
96       testValidator('iAmInvalid', false, 'containsUpperCase');
97     }));
98
99     it('bucket names can only contain lowercase letters, numbers, and hyphens', fakeAsync(() => {
100       testValidator('$$$', false, 'onlyLowerCaseAndNumbers');
101     }));
102
103     it('bucket names must not contain uppercase characters or underscores (2/2)', fakeAsync(() => {
104       testValidator('i_am_invalid', false, 'containsUpperCase');
105     }));
106
107     it('bucket names must start and end with letters or numbers', fakeAsync(() => {
108       testValidator('abcd-', false, 'lowerCaseOrNumber');
109     }));
110
111     it('bucket names with invalid labels (1/3)', fakeAsync(() => {
112       testValidator('abc.1def.Ghi2', false, 'containsUpperCase');
113     }));
114
115     it('bucket names with invalid labels (2/3)', fakeAsync(() => {
116       testValidator('abc.1_xy', false, 'containsUpperCase');
117     }));
118
119     it('bucket names with invalid labels (3/3)', fakeAsync(() => {
120       testValidator('abc.*def', false, 'lowerCaseOrNumber');
121     }));
122
123     it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (1/3)', fakeAsync(() => {
124       testValidator('xyz.abc', true);
125     }));
126
127     it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (2/3)', fakeAsync(() => {
128       testValidator('abc.1-def', true);
129     }));
130
131     it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (3/3)', fakeAsync(() => {
132       testValidator('abc.ghi2', true);
133     }));
134
135     it('bucket names must be unique', fakeAsync(() => {
136       testValidator('bucket-name-is-unique', true);
137     }));
138
139     it('should get zonegroup and placement targets', () => {
140       const payload: Record<string, any> = {
141         zonegroup: 'default',
142         placement_targets: [
143           {
144             name: 'default-placement',
145             data_pool: 'default.rgw.buckets.data'
146           },
147           {
148             name: 'placement-target2',
149             data_pool: 'placement-target2.rgw.buckets.data'
150           }
151         ]
152       };
153       getPlacementTargetsSpy.and.returnValue(observableOf(payload));
154       enumerateSpy.and.returnValue(observableOf([]));
155       fixture.detectChanges();
156
157       expect(component.zonegroup).toBe(payload.zonegroup);
158       const placementTargets = [];
159       for (const placementTarget of payload['placement_targets']) {
160         placementTarget[
161           'description'
162         ] = `${placementTarget['name']} (pool: ${placementTarget['data_pool']})`;
163         placementTargets.push(placementTarget);
164       }
165       expect(component.placementTargets).toEqual(placementTargets);
166     });
167   });
168
169   describe('submit form', () => {
170     let notificationService: NotificationService;
171
172     beforeEach(() => {
173       spyOn(TestBed.inject(Router), 'navigate').and.stub();
174       notificationService = TestBed.inject(NotificationService);
175       spyOn(notificationService, 'show');
176     });
177
178     it('should validate name', () => {
179       component.editing = false;
180       component.createForm();
181       const control = component.bucketForm.get('bid');
182       expect(_.isFunction(control.asyncValidator)).toBeTruthy();
183     });
184
185     it('should not validate name', () => {
186       component.editing = true;
187       component.createForm();
188       const control = component.bucketForm.get('bid');
189       expect(control.asyncValidator).toBeNull();
190     });
191
192     it('tests create success notification', () => {
193       spyOn(rgwBucketService, 'create').and.returnValue(observableOf([]));
194       component.editing = false;
195       component.bucketForm.markAsDirty();
196       component.submit();
197       expect(notificationService.show).toHaveBeenCalledWith(
198         NotificationType.success,
199         `Created Object Gateway bucket 'null'`
200       );
201     });
202
203     it('tests update success notification', () => {
204       spyOn(rgwBucketService, 'update').and.returnValue(observableOf([]));
205       component.editing = true;
206       component.bucketForm.markAsDirty();
207       component.submit();
208       expect(notificationService.show).toHaveBeenCalledWith(
209         NotificationType.success,
210         `Updated Object Gateway bucket 'null'.`
211       );
212     });
213   });
214
215   describe('mfa credentials', () => {
216     const checkMfaCredentialsVisibility = (
217       fakeResponse: object,
218       versioningChecked: boolean,
219       mfaDeleteChecked: boolean,
220       expectedVisibility: boolean
221     ) => {
222       component['route'].params = observableOf({ bid: 'bid' });
223       component.editing = true;
224       rgwBucketServiceGetSpy.and.returnValue(observableOf(fakeResponse));
225       enumerateSpy.and.returnValue(observableOf([]));
226       component.ngOnInit();
227       component.bucketForm.patchValue({
228         versioning: versioningChecked,
229         'mfa-delete': mfaDeleteChecked
230       });
231       fixture.detectChanges();
232
233       const mfaTokenSerial = fixture.debugElement.nativeElement.querySelector('#mfa-token-serial');
234       const mfaTokenPin = fixture.debugElement.nativeElement.querySelector('#mfa-token-pin');
235       if (expectedVisibility) {
236         expect(mfaTokenSerial).toBeTruthy();
237         expect(mfaTokenPin).toBeTruthy();
238       } else {
239         expect(mfaTokenSerial).toBeFalsy();
240         expect(mfaTokenPin).toBeFalsy();
241       }
242     };
243
244     it('inputs should be visible when required', () => {
245       checkMfaCredentialsVisibility(
246         {
247           versioning: RgwBucketVersioning.SUSPENDED,
248           mfa_delete: RgwBucketMfaDelete.DISABLED
249         },
250         false,
251         false,
252         false
253       );
254       checkMfaCredentialsVisibility(
255         {
256           versioning: RgwBucketVersioning.SUSPENDED,
257           mfa_delete: RgwBucketMfaDelete.DISABLED
258         },
259         true,
260         false,
261         false
262       );
263       checkMfaCredentialsVisibility(
264         {
265           versioning: RgwBucketVersioning.ENABLED,
266           mfa_delete: RgwBucketMfaDelete.DISABLED
267         },
268         false,
269         false,
270         false
271       );
272       checkMfaCredentialsVisibility(
273         {
274           versioning: RgwBucketVersioning.ENABLED,
275           mfa_delete: RgwBucketMfaDelete.ENABLED
276         },
277         true,
278         true,
279         false
280       );
281       checkMfaCredentialsVisibility(
282         {
283           versioning: RgwBucketVersioning.SUSPENDED,
284           mfa_delete: RgwBucketMfaDelete.DISABLED
285         },
286         false,
287         true,
288         true
289       );
290       checkMfaCredentialsVisibility(
291         {
292           versioning: RgwBucketVersioning.SUSPENDED,
293           mfa_delete: RgwBucketMfaDelete.ENABLED
294         },
295         false,
296         false,
297         true
298       );
299       checkMfaCredentialsVisibility(
300         {
301           versioning: RgwBucketVersioning.SUSPENDED,
302           mfa_delete: RgwBucketMfaDelete.ENABLED
303         },
304         true,
305         true,
306         true
307       );
308       checkMfaCredentialsVisibility(
309         {
310           versioning: RgwBucketVersioning.ENABLED,
311           mfa_delete: RgwBucketMfaDelete.ENABLED
312         },
313         false,
314         true,
315         true
316       );
317     });
318   });
319
320   describe('object locking', () => {
321     const expectPatternLockError = (value: string) => {
322       formHelper.setValue('lock_enabled', true, true);
323       formHelper.setValue('lock_retention_period_days', value);
324       formHelper.expectError('lock_retention_period_days', 'pattern');
325     };
326
327     const expectValidLockInputs = (enabled: boolean, mode: string, days: string) => {
328       formHelper.setValue('lock_enabled', enabled);
329       formHelper.setValue('lock_mode', mode);
330       formHelper.setValue('lock_retention_period_days', days);
331       ['lock_enabled', 'lock_mode', 'lock_retention_period_days'].forEach((name) => {
332         const control = component.bucketForm.get(name);
333         expect(control.valid).toBeTruthy();
334         expect(control.errors).toBeNull();
335       });
336     };
337
338     it('should check lock enabled checkbox [mode=create]', () => {
339       component.createForm();
340       const control = component.bucketForm.get('lock_enabled');
341       expect(control.disabled).toBeFalsy();
342     });
343
344     it('should check lock enabled checkbox [mode=edit]', () => {
345       component.editing = true;
346       component.createForm();
347       const control = component.bucketForm.get('lock_enabled');
348       expect(control.disabled).toBeTruthy();
349     });
350
351     it('should have the "lockDays" error', () => {
352       formHelper.setValue('lock_enabled', true);
353       const control = component.bucketForm.get('lock_retention_period_days');
354       control.updateValueAndValidity();
355       expect(control.value).toBe(0);
356       expect(control.invalid).toBeTruthy();
357       formHelper.expectError(control, 'lockDays');
358     });
359
360     it('should have the "pattern" error [1]', () => {
361       expectPatternLockError('-1');
362     });
363
364     it('should have the "pattern" error [2]', () => {
365       expectPatternLockError('1.2');
366     });
367
368     it('should have valid values [1]', () => {
369       expectValidLockInputs(true, 'Governance', '1');
370     });
371
372     it('should have valid values [2]', () => {
373       expectValidLockInputs(false, 'Compliance', '2');
374     });
375   });
376 });