]>
Commit | Line | Data |
---|---|---|
9f95a23c TL |
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 { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation'; | |
6 | import { BsModalRef } from 'ngx-bootstrap/modal'; | |
7 | import { ToastrModule } from 'ngx-toastr'; | |
8 | import { of } from 'rxjs'; | |
9 | ||
10 | import { | |
11 | configureTestBed, | |
12 | FixtureHelper, | |
13 | FormHelper, | |
f6b5b4d7 TL |
14 | i18nProviders, |
15 | Mocks | |
9f95a23c TL |
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 | ||
9f95a23c TL |
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))); | |
e306af50 | 43 | const keys = component.failureDomainKeys; |
9f95a23c TL |
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, BsModalRef, 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.get(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 | */ | |
f6b5b4d7 | 104 | nodes: Mocks.getCrushMap() |
9f95a23c TL |
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); | |
e306af50 | 117 | expect(component.buckets.length).toBe(5); |
9f95a23c TL |
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.get(TaskWrapperService); | |
210 | spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough(); | |
211 | spyOn(crushRuleService, 'create').and.stub(); | |
212 | }); | |
213 | ||
214 | it('creates a rule with only required fields', () => { | |
f6b5b4d7 | 215 | assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack')); |
9f95a23c TL |
216 | }); |
217 | ||
218 | it('creates a rule with all fields', () => { | |
219 | assert.valuesOnRootChange('ssd-host', 'osd', 'ssd'); | |
f6b5b4d7 | 220 | assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd')); |
9f95a23c TL |
221 | }); |
222 | }); | |
223 | }); |