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