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