]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
62bdbebc367ba9e62065b5262b204b49112dd7d8
[ceph-ci.git] /
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { RouterTestingModule } from '@angular/router/testing';
4
5 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
6 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
7 import { ToastrModule } from 'ngx-toastr';
8 import { of } from 'rxjs';
9
10 import {
11   configureTestBed,
12   FixtureHelper,
13   FormHelper,
14   i18nProviders,
15   Mocks
16 } from '../../../../testing/unit-test-helper';
17 import { CrushRuleService } from '../../../shared/api/crush-rule.service';
18 import { CrushNode } from '../../../shared/models/crush-node';
19 import { CrushRuleConfig } from '../../../shared/models/crush-rule';
20 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
21 import { PoolModule } from '../pool.module';
22 import { CrushRuleFormModalComponent } from './crush-rule-form-modal.component';
23
24 describe('CrushRuleFormComponent', () => {
25   let component: CrushRuleFormModalComponent;
26   let crushRuleService: CrushRuleService;
27   let fixture: ComponentFixture<CrushRuleFormModalComponent>;
28   let formHelper: FormHelper;
29   let fixtureHelper: FixtureHelper;
30   let data: { names: string[]; nodes: CrushNode[] };
31
32   // Object contains functions to get something
33   const get = {
34     nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
35     nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
36   };
37
38   // Expects that are used frequently
39   const assert = {
40     failureDomains: (nodes: CrushNode[], types: string[]) => {
41       const expectation = {};
42       types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
43       const keys = component.failureDomainKeys;
44       expect(keys).toEqual(types);
45       keys.forEach((key) => {
46         expect(component.failureDomains[key].length).toBe(expectation[key].length);
47       });
48     },
49     formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
50       expect(component.form.value).toEqual({
51         name: '',
52         root,
53         failure_domain: failureDomain,
54         device_class: device
55       });
56     },
57     valuesOnRootChange: (
58       rootName: string,
59       expectedFailureDomain: string,
60       expectedDevice: string
61     ) => {
62       const node = get.nodeByName(rootName);
63       formHelper.setValue('root', node);
64       assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
65     },
66     creation: (rule: CrushRuleConfig) => {
67       formHelper.setValue('name', rule.name);
68       fixture.detectChanges();
69       component.onSubmit();
70       expect(crushRuleService.create).toHaveBeenCalledWith(rule);
71     }
72   };
73
74   configureTestBed({
75     imports: [
76       HttpClientTestingModule,
77       RouterTestingModule,
78       ToastrModule.forRoot(),
79       PoolModule,
80       NgBootstrapFormValidationModule.forRoot()
81     ],
82     providers: [CrushRuleService, NgbActiveModal, i18nProviders]
83   });
84
85   beforeEach(() => {
86     fixture = TestBed.createComponent(CrushRuleFormModalComponent);
87     fixtureHelper = new FixtureHelper(fixture);
88     component = fixture.componentInstance;
89     formHelper = new FormHelper(component.form);
90     crushRuleService = TestBed.inject(CrushRuleService);
91     data = {
92       names: ['rule1', 'rule2'],
93       /**
94        * Create the following test crush map:
95        * > default
96        * --> ssd-host
97        * ----> 3x osd with ssd
98        * --> mix-host
99        * ----> hdd-rack
100        * ------> 2x osd-rack with hdd
101        * ----> ssd-rack
102        * ------> 2x osd-rack with ssd
103        */
104       nodes: Mocks.getCrushMap()
105     };
106     spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
107     fixture.detectChanges();
108   });
109
110   it('should create', () => {
111     expect(component).toBeTruthy();
112   });
113
114   it('calls listing to get rules on ngInit', () => {
115     expect(crushRuleService.getInfo).toHaveBeenCalled();
116     expect(component.names.length).toBe(2);
117     expect(component.buckets.length).toBe(5);
118   });
119
120   describe('lists', () => {
121     afterEach(() => {
122       // The available buckets should not change
123       expect(component.buckets).toEqual(
124         get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
125       );
126     });
127
128     it('has the following lists after init', () => {
129       assert.failureDomains(data.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
130       expect(component.devices).toEqual(['hdd', 'ssd']);
131     });
132
133     it('has the following lists after selection of ssd-host', () => {
134       formHelper.setValue('root', get.nodeByName('ssd-host'));
135       assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
136       expect(component.devices).toEqual(['ssd']);
137     });
138
139     it('has the following lists after selection of mix-host', () => {
140       formHelper.setValue('root', get.nodeByName('mix-host'));
141       expect(component.devices).toEqual(['hdd', 'ssd']);
142       assert.failureDomains(
143         get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
144         ['osd-rack', 'rack']
145       );
146     });
147   });
148
149   describe('selection', () => {
150     it('selects the first root after init automatically', () => {
151       assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
152     });
153
154     it('should select all values automatically by selecting "ssd-host" as root', () => {
155       assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
156     });
157
158     it('selects automatically the most common failure domain', () => {
159       // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
160       assert.valuesOnRootChange('mix-host', 'osd-rack', '');
161     });
162
163     it('should override automatic selections', () => {
164       assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
165       assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
166       assert.valuesOnRootChange('mix-host', 'osd-rack', '');
167     });
168
169     it('should not override manual selections if possible', () => {
170       formHelper.setValue('failure_domain', 'rack', true);
171       formHelper.setValue('device_class', 'ssd', true);
172       assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
173     });
174
175     it('should preselect device by domain selection', () => {
176       formHelper.setValue('failure_domain', 'osd', true);
177       assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
178     });
179   });
180
181   describe('form validation', () => {
182     it(`isn't valid if name is not set`, () => {
183       expect(component.form.invalid).toBeTruthy();
184       formHelper.setValue('name', 'someProfileName');
185       expect(component.form.valid).toBeTruthy();
186     });
187
188     it('sets name invalid', () => {
189       component.names = ['awesomeProfileName'];
190       formHelper.expectErrorChange('name', 'awesomeProfileName', 'uniqueName');
191       formHelper.expectErrorChange('name', 'some invalid text', 'pattern');
192       formHelper.expectErrorChange('name', null, 'required');
193     });
194
195     it(`should show all default form controls`, () => {
196       // name
197       // root (preselected(first root))
198       // failure_domain (preselected=type that is most common)
199       // device_class (preselected=any if multiple or some type if only one device type)
200       fixtureHelper.expectIdElementsVisible(
201         ['name', 'root', 'failure_domain', 'device_class'],
202         true
203       );
204     });
205   });
206
207   describe('submission', () => {
208     beforeEach(() => {
209       const taskWrapper = TestBed.inject(TaskWrapperService);
210       spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough();
211       spyOn(crushRuleService, 'create').and.stub();
212     });
213
214     it('creates a rule with only required fields', () => {
215       assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack'));
216     });
217
218     it('creates a rule with all fields', () => {
219       assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
220       assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));
221     });
222   });
223 });