diff --git a/config-guide.md b/config-guide.md index d10b3fed33..6804d6c589 100644 --- a/config-guide.md +++ b/config-guide.md @@ -126,11 +126,15 @@ In this section you can specify classes for service offerings in the following f "description": { "ru": "class_description_ru", "en": "class_description_en" - } + }, + "serviceOfferings": [ + "so-id1", + "so-id2" + ] } ] -Each classes should have a unique id, name, and description. Name and description should be localized for used languages. +Each classes should have a unique id, name, description and list of service offering ids, which belong to this class. Name and description should be localized for used languages. ### Session Timeout diff --git a/src/app/reducers/account-tags/redux/account-tags.effects.spec.ts b/src/app/reducers/account-tags/redux/account-tags.effects.spec.ts new file mode 100644 index 0000000000..e35eaa554d --- /dev/null +++ b/src/app/reducers/account-tags/redux/account-tags.effects.spec.ts @@ -0,0 +1,227 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Injectable } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { Actions } from '@ngrx/effects'; +import { StoreModule } from '@ngrx/store'; +import { cold, hot } from 'jasmine-marbles'; +import { Observable } from 'rxjs/Observable'; +import { empty } from 'rxjs/observable/empty'; +import { of } from 'rxjs/observable/of'; +import { MockDialogService } from '../../../../testutils/mocks/mock-dialog.service'; +import { DialogService } from '../../../dialog/dialog-service/dialog.service'; +import * as fromAccountTags from './account-tags.reducers'; +import * as accountTagActions from './account-tags.actions'; +import { AccountTagsEffects } from './account-tags.effects'; +import { Tag } from '../../../shared/models/tag.model'; +import { TagService } from '../../../shared/services/tags/tag.service'; +import { AccountTagService } from '../../../shared/services/tags/account-tag.service'; +import { ConfigService } from '../../../shared/services/config.service'; +import { ServiceOffering } from '../../../shared/models/service-offering.model'; +import { StorageTypes } from '../../../shared/models/offering.model'; + +@Injectable() +class MockAsyncJobService { + public completeAllJobs(): void { + } +} + +@Injectable() +class MockTagService { + public getList(): void { + } + public setServiceOfferingParams(): void { + } +} + + +@Injectable() +class MockStorageService { + private storage: any = { + user: { + userid: '1' + } + }; + + public write(key: string, value: string): void { + this.storage[key] = value; + } + + public read(key: string): string { + return this.storage[key] || null; + } + + public remove(key: string): void { + delete this.storage[key]; + } + + public resetInMemoryStorage(): void { + this.storage = {}; + } +} + +class MockMatDialog { + public open(): void { + } + public closeAll(): void { + } +} + +export class TestActions extends Actions { + constructor() { + super(empty()); + } + + public set stream(source: Observable) { + this.source = source; + } +} + +export function getActions() { + return new TestActions(); +} + + +describe('Account tags Effects', () => { + let actions$: TestActions; + let service: TagService; + let accountService: AccountTagService; + let configService: ConfigService; + let dialogService: DialogService; + + let effects: AccountTagsEffects; + + const list: Array = []; + + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + StoreModule.forRoot({ ...fromAccountTags.accountTagsReducers}), + ], + providers: [ + AccountTagsEffects, + ConfigService, + { provide: Actions, useFactory: getActions }, + { provide: TagService, useClass: MockTagService }, + { provide: AccountTagService, useClass: MockTagService }, + { provide: DialogService, useClass: MockDialogService }, + ] + }); + actions$ = TestBed.get(Actions); + service = TestBed.get(TagService); + accountService = TestBed.get(AccountTagService); + configService = TestBed.get(ConfigService); + dialogService = TestBed.get(DialogService); + effects = TestBed.get(AccountTagsEffects); + }); + + it('should return a collection from LoadAccountTagsResponse', () => { + const spyGetList = spyOn(service, 'getList').and.returnValue(of(list)); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(true); + + const action = new accountTagActions.LoadAccountTagsRequest(); + const completion = new accountTagActions.LoadAccountTagsResponse(list); + + actions$.stream = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + + expect(effects.loadAccountTags$).toBeObservable(expected); + expect(spyGetList).toHaveBeenCalled(); + }); + + it('should not return a collection from LoadAccountTagsResponse', () => { + const spyGetList = spyOn(service, 'getList'); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(false); + + const action = new accountTagActions.LoadAccountTagsRequest(); + + actions$.stream = hot('-a', { a: action }); + const expected = cold('', []); + + expect(effects.loadAccountTags$).toBeObservable(expected); + expect(spyGetList).not.toHaveBeenCalled(); + }); + + it('should return an empty collection from LoadAccountTagsResponse', () => { + const spyGetList = spyOn(service, 'getList').and + .returnValue(Observable.throw(new Error('Error occurred!'))); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(true); + + const action = new accountTagActions.LoadAccountTagsRequest(); + const completion = new accountTagActions.LoadAccountTagsResponse([]); + + actions$.stream = hot('a', { a: action }); + const expected = cold('b', { b: completion }); + + expect(effects.loadAccountTags$).toBeObservable(expected); + }); + + it('should update custom SO params', () => { + const offering = { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: true + }; + const spySetParam = spyOn(accountService, 'setServiceOfferingParams').and.returnValue(of(offering)); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(true); + + const action = new accountTagActions.UpdateCustomServiceOfferingParams(offering); + const completion = new accountTagActions.UpdateCustomServiceOfferingParamsSuccess(offering); + + actions$.stream = hot('-a', { a: action }); + const expected = cold('-b', { b: completion }); + + expect(effects.updateCustomServiceOfferingParams$).toBeObservable(expected); + expect(spySetParam).toHaveBeenCalled(); + }); + + it('should not update custom SO params', () => { + const offering = { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: true + }; + const spySetParam = spyOn(accountService, 'setServiceOfferingParams'); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(false); + + const action = new accountTagActions.UpdateCustomServiceOfferingParams(offering); + + actions$.stream = hot('-a', { a: action }); + const expected = cold('', []); + + expect(effects.updateCustomServiceOfferingParams$).toBeObservable(expected); + expect(spySetParam).not.toHaveBeenCalled(); + }); + + it('should return an error during updating custom SO params', () => { + const offering = { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: true + }; + const spySetParam = spyOn(accountService, 'setServiceOfferingParams').and. + returnValue(Observable.throw(new Error('Error occurred!'))); + const spyAccountTag = spyOn(configService, 'get').and.returnValue(true); + + const action = new accountTagActions.UpdateCustomServiceOfferingParams(offering); + const completion = new accountTagActions.UpdateCustomServiceOfferingParamsError(new Error('Error occurred!')); + + actions$.stream = hot('a', { a: action }); + const expected = cold('a', { a: completion }); + + expect(effects.updateCustomServiceOfferingParams$).toBeObservable(expected); + }); + + it('should show alert after updating error', () => { + const spyAlert = spyOn(dialogService, 'alert'); + const action = new accountTagActions.UpdateCustomServiceOfferingParamsError(new Error('Error occurred!')); + + actions$.stream = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.updateCustomServiceOfferingParamsError$).toBeObservable(expected); + expect(spyAlert).toHaveBeenCalled(); + }); + +}); diff --git a/src/app/reducers/account-tags/redux/account-tags.reducers.ts b/src/app/reducers/account-tags/redux/account-tags.reducers.ts index 7f78456257..bfd762212b 100644 --- a/src/app/reducers/account-tags/redux/account-tags.reducers.ts +++ b/src/app/reducers/account-tags/redux/account-tags.reducers.ts @@ -134,13 +134,6 @@ export const isLoading = createSelector( state => state.loading ); -export const selectServiceOfferingClassTags = createSelector( - selectAll, - (tags) => { - return tags.filter(tag => tag.key.includes(AccountTagKeys.serviceOfferingClass)); - } -); - export const selectServiceOfferingParamTags = createSelector( selectAll, (tags) => { diff --git a/src/app/reducers/service-offerings/redux/service-offering-class.effects.ts b/src/app/reducers/service-offerings/redux/service-offering-class.effects.ts index 0818425403..194c282b5c 100644 --- a/src/app/reducers/service-offerings/redux/service-offering-class.effects.ts +++ b/src/app/reducers/service-offerings/redux/service-offering-class.effects.ts @@ -11,7 +11,6 @@ export class ServiceOfferingClassEffects { @Effect() loadServiceOfferingClasses$: Observable = this.actions$ .ofType(actions.LOAD_SERVICE_OFFERING_CLASS_REQUEST) - .filter(() => this.isAccountTagEnabled()) .switchMap((action: actions.LoadServiceOfferingClassRequest) => { return Observable.of(this.configService.get('serviceOfferingClasses')) .map(classList => new actions.LoadServiceOfferingClassResponse(classList)) @@ -22,8 +21,4 @@ export class ServiceOfferingClassEffects { private actions$: Actions, private configService: ConfigService ) { } - - public isAccountTagEnabled(): boolean { - return this.configService.get('accountTagsEnabled'); - } } diff --git a/src/app/reducers/service-offerings/redux/service-offerings.reducers.spec.ts b/src/app/reducers/service-offerings/redux/service-offerings.reducers.spec.ts index 1e776417a6..dd3c7a839b 100644 --- a/src/app/reducers/service-offerings/redux/service-offerings.reducers.spec.ts +++ b/src/app/reducers/service-offerings/redux/service-offerings.reducers.spec.ts @@ -527,5 +527,210 @@ describe('Test service offering reducer', () => { .toEqual([list[1]]); }); + it('should filter offerings by classes', () => { + const list = [ + { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2 + }, + { + id: '2', name: 'off2', hosttags: 't1', + storagetype: StorageTypes.shared, + cpunumber: 2, memory: 2 + } + ]; + const filtered = [{ + id: '2', name: 'off2', hosttags: 't1', + storagetype: StorageTypes.shared, + cpunumber: 2, memory: 2 + }]; + + const soClasses = [ + { + "id": "testClass1", + "name": { + "ru": "Тест1", + "en": "Test1" + }, + "description": { + "ru": "тестовое описание 1", + "en": "test description 1" + }, + "serviceOfferings": [ + "1" + ] + }, + { + "id": "testClass2", + "name": { + "ru": "Тест2", + "en": "Test2" + }, + "description": { + "ru": "тестовое описание 2", + "en": "test description 2" + }, + "serviceOfferings": [ + "2" + ] + } + ]; + + const classesMap = { "testClass2": "testClass2" }; + const res = list.filter(item => fromSOs.classesFilter(item, soClasses, classesMap)); + + expect(res).toEqual(filtered); + }); + + it('should get filtered offerings', () => { + const list = [ + { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: false + }, + { + id: '2', name: 'off2', hosttags: 't1', + storagetype: StorageTypes.shared, + cpunumber: 2, memory: 2, iscustomized: true + } + ]; + const filtered = [{ + id: '2', name: 'off2', hosttags: 't1', + storagetype: StorageTypes.shared, + cpunumber: 2, memory: 2, iscustomized: true + }]; + + const soClasses = [ + { + "id": "testClass1", + "name": { + "ru": "Тест1", + "en": "Test1" + }, + "description": { + "ru": "тестовое описание 1", + "en": "test description 1" + }, + "serviceOfferings": [ + "1" + ] + }, + { + "id": "testClass2", + "name": { + "ru": "Тест2", + "en": "Test2" + }, + "description": { + "ru": "тестовое описание 2", + "en": "test description 2" + }, + "serviceOfferings": [ + "2" + ] + } + ]; + + const selectedClasses = ["testClass2"]; + const query = 'off2'; + const viewMode = ServiceOfferingType.custom; + + const res = fromSOs.selectFilteredOfferings.projector(list, viewMode, selectedClasses, query, soClasses); + + expect(res).toEqual(filtered); + }); + + it('should get filtered offerings (else branch)', () => { + const list = [ + { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: false + }, + { + id: '2', name: 'off2', hosttags: 't1', + storagetype: StorageTypes.shared, + cpunumber: 2, memory: 2, iscustomized: true + } + ]; + const filtered = [{ + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, + cpunumber: 2, memory: 2, iscustomized: false + }]; + + const soClasses = [ + { + "id": "testClass1", + "name": { + "ru": "Тест1", + "en": "Test1" + }, + "description": { + "ru": "тестовое описание 1", + "en": "test description 1" + }, + "serviceOfferings": [ + "1" + ] + }, + { + "id": "testClass2", + "name": { + "ru": "Тест2", + "en": "Test2" + }, + "description": { + "ru": "тестовое описание 2", + "en": "test description 2" + }, + "serviceOfferings": [ + "2" + ] + } + ]; + + const selectedClasses = []; + const query = ''; + const viewMode = ServiceOfferingType.fixed; + + const res = fromSOs.selectFilteredOfferings.projector(list, viewMode, selectedClasses, query, soClasses); + + expect(res).toEqual(filtered); + }); + + it('should get default params for offering', () => { + const defaults = { + cpunumber: 1, + cpuspeed: 1024, + memory: 512 + }; + const customRestrictions = { cpunumber: { min: 1 }, memory: { min: 1 } }; + const resourceUsage = { + available: new ResourcesData(), + consumed: new ResourcesData(), + max: new ResourcesData() + }; + resourceUsage.available.cpus = 2; + resourceUsage.available.memory = 2; + spyOn(ResourceStats, 'fromAccount').and.returnValue(resourceUsage); + const params1 = { + cpunumber: 1, + cpuspeed: 1024, + memory: 2 + }; + const params2 = { + cpunumber: 1, + cpuspeed: 1000, + memory: 1 + }; + + expect(fromSOs.getDefaultParams.projector(defaults, customRestrictions, null, null)).toEqual(params1); + expect(fromSOs.getDefaultParams.projector(null, customRestrictions, null, null)).toEqual(params2); + expect(fromSOs.getDefaultParams.projector(null, null, customRestrictions, null)).toEqual(params2); + }); + }); diff --git a/src/app/reducers/service-offerings/redux/service-offerings.reducers.ts b/src/app/reducers/service-offerings/redux/service-offerings.reducers.ts index 107b5ea588..49979acf7b 100644 --- a/src/app/reducers/service-offerings/redux/service-offerings.reducers.ts +++ b/src/app/reducers/service-offerings/redux/service-offerings.reducers.ts @@ -16,9 +16,9 @@ import { isOfferingLocal } from '../../../shared/models/offering.model'; import { DefaultServiceOfferingClassId, ServiceOffering, - ServiceOfferingClassKey, ServiceOfferingParamKey, - ServiceOfferingType + ServiceOfferingType, + ServiceOfferingClass } from '../../../shared/models/service-offering.model'; import { Tag } from '../../../shared/models/tag.model'; import { Zone } from '../../../shared/models/zone.model'; @@ -268,15 +268,7 @@ export const getAvailableOfferings = createSelector( customRestrictions, ResourceStats.fromAccount([user]), zone - ).sort((a: ServiceOffering, b: ServiceOffering) => { - if (!a.iscustomized && b.iscustomized) { - return -1; - } - if (a.iscustomized && !b.iscustomized) { - return 1; - } - return 0; - }); + ); const filterByCompatibilityPolicy = (offering: ServiceOffering) => { if (compatibilityPolicy) { @@ -303,12 +295,12 @@ export const getAvailableOfferings = createSelector( } ); -export const classesFilter = (offering: ServiceOffering, tags: Tag[], classesMap) => { - const tag = offering && tags.find(tag => tag.key === ServiceOfferingClassKey + '.' + offering.id); - const classes = tag && tag.value.split(','); +export const classesFilter = (offering: ServiceOffering, soClasses: ServiceOfferingClass[], classesMap: any) => { + const classes = soClasses.filter(soClass => + soClass.serviceOfferings && soClass.serviceOfferings.indexOf(offering.id) > -1); const showGeneral = !!classesMap[DefaultServiceOfferingClassId]; - return classes && classes.find(soClass => classesMap[soClass]) - || (showGeneral && !classes); + return classes.length && classes.find(soClass => classesMap[soClass.id]) + || (showGeneral && !classes.length); }; export const selectFilteredOfferings = createSelector( @@ -316,9 +308,8 @@ export const selectFilteredOfferings = createSelector( filterSelectedViewMode, filterSelectedClasses, filterQuery, - fromSOClass.selectEntities, - fromAccountTags.selectAll, - (offerings, viewMode, selectedClasses, query, classes, tags) => { + fromSOClass.selectAll, + (offerings, viewMode, selectedClasses, query, classes ) => { const classesMap = selectedClasses.reduce((m, i) => ({ ...m, [i]: i }), {}); const queryLower = query && query.toLowerCase(); @@ -328,7 +319,7 @@ export const selectFilteredOfferings = createSelector( const selectedClassesFilter = (offering: ServiceOffering) => { if (selectedClasses.length) { - return classesFilter(offering, tags, classesMap); + return classesFilter(offering, classes, classesMap); } return true; }; @@ -381,9 +372,8 @@ export const selectFilteredOfferingsForVmCreation = createSelector( filterSelectedViewMode, filterSelectedClasses, filterQuery, - fromSOClass.selectEntities, - fromAccountTags.selectServiceOfferingClassTags, - (offerings, viewMode, selectedClasses, query, classes, tags) => { + fromSOClass.selectAll, + (offerings, viewMode, selectedClasses, query, classes) => { const classesMap = selectedClasses.reduce((m, i) => ({ ...m, [i]: i }), {}); const queryLower = query && query.toLowerCase(); @@ -393,7 +383,7 @@ export const selectFilteredOfferingsForVmCreation = createSelector( const selectedClassesFilter = (offering: ServiceOffering) => { if (selectedClasses.length) { - return classesFilter(offering, tags, classesMap); + return classesFilter(offering, classes, classesMap); } return true; }; @@ -471,15 +461,7 @@ export const getAvailableByResourcesSync = ( } return enoughCpus && enoughMemory; - }) - .sort((a: ServiceOffering, b: ServiceOffering) => { - if (!a.iscustomized && b.iscustomized) { - return -1; - } - if (a.iscustomized && !b.iscustomized) { - return 1; - } - return 0; + }); }; diff --git a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html index d3b907458d..69f975edf3 100644 --- a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html +++ b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.html @@ -14,7 +14,6 @@

[classes]="classes" [selectedClasses]="selectedClasses" [query]="query" - [classTags]="classTags" [offeringList]="serviceOfferings" [customOfferingRestrictions]="restrictions" [defaultParams]="defaultParams" diff --git a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.ts b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.ts index d214604ff8..ed72b09566 100644 --- a/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.ts +++ b/src/app/service-offering/service-offering-dialog/service-offering-dialog.component.ts @@ -12,7 +12,6 @@ import { ServiceOfferingClass, ServiceOfferingType } from '../../shared/models/service-offering.model'; -import { Tag } from '../../shared/models/tag.model'; import { ICustomOfferingRestrictions } from '../custom-service-offering/custom-offering-restrictions'; import { ICustomServiceOffering } from '../custom-service-offering/custom-service-offering'; @@ -31,7 +30,6 @@ export class ServiceOfferingDialogComponent implements OnInit, OnChanges { @Input() public serviceOfferings: Array; @Input() public classes: Array; @Input() public selectedClasses: Array; - @Input() public classTags: Array; @Input() public serviceOfferingId: string; @Input() public viewMode: string; @Input() public restrictions: ICustomOfferingRestrictions; diff --git a/src/app/service-offering/service-offering-filter/service-offering-filter.component.html b/src/app/service-offering/service-offering-filter/service-offering-filter.component.html index 976e66e7b1..c8dc9f9a57 100644 --- a/src/app/service-offering/service-offering-filter/service-offering-filter.component.html +++ b/src/app/service-offering/service-offering-filter/service-offering-filter.component.html @@ -14,7 +14,7 @@ - + ; diff --git a/src/app/service-offering/service-offering-list/service-offering-list.component.ts b/src/app/service-offering/service-offering-list/service-offering-list.component.ts index 8809fa5344..b299bd84b4 100644 --- a/src/app/service-offering/service-offering-list/service-offering-list.component.ts +++ b/src/app/service-offering/service-offering-list/service-offering-list.component.ts @@ -7,7 +7,6 @@ import { ServiceOffering, ServiceOfferingClass } from '../../shared/models/service-offering.model'; -import { Tag } from '../../shared/models/tag.model'; import { Language } from '../../shared/services/language.service'; import { ICustomOfferingRestrictions } from '../custom-service-offering/custom-offering-restrictions'; import { @@ -25,7 +24,6 @@ export class ServiceOfferingListComponent implements OnChanges { @Input() public offeringList: Array; @Input() public classes: Array; @Input() public selectedClasses: Array; - @Input() public classTags: Array; @Input() public query: string; @Input() public customOfferingRestrictions: ICustomOfferingRestrictions; @Input() public defaultParams: ICustomServiceOffering; @@ -107,6 +105,6 @@ export class ServiceOfferingListComponent implements OnChanges { public filterOfferings(list: ServiceOffering[], soClass: ServiceOfferingClass) { const classesMap = [ soClass ].reduce((m, i) => ({ ...m, [i.id]: i }), {}); - return list.filter(offering => classesFilter(offering, this.classTags, classesMap)); + return list.filter(offering => classesFilter(offering, this.classes, classesMap)); } } diff --git a/src/app/shared/models/service-offering.model.ts b/src/app/shared/models/service-offering.model.ts index 07404efeb8..7044a56180 100644 --- a/src/app/shared/models/service-offering.model.ts +++ b/src/app/shared/models/service-offering.model.ts @@ -24,6 +24,7 @@ export class ServiceOfferingClass { public id: string; public name?: object; public description?: object; + public serviceOfferings?: string[]; constructor(id: string) { this.id = id; @@ -35,6 +36,5 @@ export const ServiceOfferingType = { custom: 'Custom' }; -export const ServiceOfferingClassKey = AccountTagKeys.serviceOfferingClass; export const ServiceOfferingParamKey = AccountTagKeys.serviceOfferingParam; export const DefaultServiceOfferingClassId = 'common'; diff --git a/src/app/shared/services/tags/account-tag-keys.ts b/src/app/shared/services/tags/account-tag-keys.ts index ad64bd026f..cf0b0158e7 100644 --- a/src/app/shared/services/tags/account-tag-keys.ts +++ b/src/app/shared/services/tags/account-tag-keys.ts @@ -1,5 +1,4 @@ export const AccountTagKeys = { sshDescription: 'csui.account.ssh-description', - serviceOfferingClass: 'csui.service-offering.class', serviceOfferingParam: 'csui.service-offering.param', }; diff --git a/src/app/shared/services/tags/account-tag.service.spec.ts b/src/app/shared/services/tags/account-tag.service.spec.ts new file mode 100644 index 0000000000..cc2483bccb --- /dev/null +++ b/src/app/shared/services/tags/account-tag.service.spec.ts @@ -0,0 +1,127 @@ +import { + HttpClientTestingModule, + HttpTestingController +} from '@angular/common/http/testing'; +import { TestBed, } from '@angular/core/testing'; +import { of } from 'rxjs/observable/of'; +import { AccountTagService } from './account-tag.service'; +import { Injectable } from '@angular/core'; +import { AccountService } from '../account.service'; +import { User } from '../../models/user.model'; +import { AuthService } from '../auth.service'; +import { TagService } from './tag.service'; +import { Tag } from '../../models/tag.model'; +import { SSHKeyPair } from '../../models/ssh-keypair.model'; +import { AccountResourceType } from '../../models/account.model'; +import { StorageTypes } from '../../models/offering.model'; +import { ServiceOffering } from '../../models/service-offering.model'; + +@Injectable() +class MockService { + public getAccount(params: {}) { + return { + switchMap: (f) => { + return f({ account: 'Account', domainid: 'D1'}); + } + }; + } +} + +@Injectable() +export class MockAuthService { + public get user() { + return { + account: 'Account', + domainid: 'D1' + }; + } +} + +@Injectable() +class MockTagService { + public getTag(): void { + } + public getValueFromTag(): void { + } + public update(): void { + } +} + +describe('Account tag service', () => { + let mockBackend: HttpTestingController; + let jobQueries = 0; + let accountTagService: AccountTagService; + let accountService: AccountService; + let tagService: TagService; + + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + AccountTagService, + { provide: AccountService, useClass: MockService }, + { provide: AuthService, useClass: MockAuthService }, + { provide: TagService, useClass: MockTagService } + ], + imports: [ + HttpClientTestingModule + ] + }); + + accountTagService = TestBed.get(AccountTagService); + accountService = TestBed.get(AccountService); + tagService = TestBed.get(TagService); + }); + + it('should return user', () => { + expect(accountTagService.user).toEqual({ account: 'Account', domainid: 'D1' }); + }); + + it('should return ssh-key description', () => { + spyOn(tagService, 'getTag').and.returnValue(of({ key: 'ssh-key-description', value: 'desc' })); + spyOn(tagService, 'getValueFromTag').and.returnValue('desc'); + accountTagService.getSshKeyDescription({}).subscribe(res => + expect(res).toEqual('desc')); + }); + + it('should not return ssh-key description', () => { + spyOn(tagService, 'getTag').and.returnValue(of(null)); + spyOn(tagService, 'getValueFromTag').and.returnValue('desc'); + accountTagService.getSshKeyDescription({}).subscribe(res => + expect(res).not.toBeDefined()); + }); + + it('should set ssh-key description', () => { + spyOn(accountTagService, 'writeTag').and.returnValue(of(true)); + const key = {fingerprint: '123'}; + + accountTagService.setSshKeyDescription(key, 'desc').subscribe(res => + expect(res).toEqual('desc')); + }); + + it('should write tag', () => { + const spyUpdate = spyOn(tagService, 'update').and.returnValue(of(true)); + const key = {fingerprint: '123'}; + + accountTagService.writeTag('key', 'value').subscribe(res => + expect(res).toBeTruthy()); + expect(spyUpdate).toHaveBeenCalledWith( + { account: 'Account', domainid: 'D1'}, AccountResourceType, 'key', 'value'); + }); + + it('should set service offering params', () => { + const spyWrite = spyOn(accountTagService, 'writeTag').and.returnValue(of(true)); + const offering = { + id: '1', name: 'off1', hosttags: 't1,t2', + storagetype: StorageTypes.local, cpuspeed: 1, + cpunumber: 2, memory: 2, iscustomized: true + }; + + accountTagService.setServiceOfferingParams(offering).subscribe(res => + expect(res).toEqual(offering)); + expect(spyWrite).toHaveBeenCalledWith('csui.service-offering.param.1.cpuNumber', '2'); + expect(spyWrite).toHaveBeenCalledWith('csui.service-offering.param.1.cpuSpeed', '1'); + expect(spyWrite).toHaveBeenCalledWith('csui.service-offering.param.1.memory', '2'); + }); + +}); diff --git a/src/app/shared/services/tags/account-tag.service.ts b/src/app/shared/services/tags/account-tag.service.ts index d76b12e4ab..baab842427 100644 --- a/src/app/shared/services/tags/account-tag.service.ts +++ b/src/app/shared/services/tags/account-tag.service.ts @@ -47,7 +47,7 @@ export class AccountTagService implements EntityTagService { return `${this.keys.sshDescription}.${sshKey.fingerprint}`; } - public writeTag(key: string, value: string): Observable { + public writeTag(key: string, value: string): Observable { return this.accountService.getAccount({name: this.user.account, domainid: this.user.domainid}) .switchMap(account => { return this.tagService.update( diff --git a/src/app/vm/container/service-offering-dialog.container.ts b/src/app/vm/container/service-offering-dialog.container.ts index 25ae09a257..e820276b12 100644 --- a/src/app/vm/container/service-offering-dialog.container.ts +++ b/src/app/vm/container/service-offering-dialog.container.ts @@ -9,7 +9,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { Store } from '@ngrx/store'; import { DialogService } from '../../dialog/dialog-service/dialog.service'; import * as accountTagsActions from '../../reducers/account-tags/redux/account-tags.actions'; -import * as fromAccountTags from '../../reducers/account-tags/redux/account-tags.reducers'; import { State } from '../../reducers/index'; import * as soGroupActions from '../../reducers/service-offerings/redux/service-offering-class.actions'; import * as fromSOClasses from '../../reducers/service-offerings/redux/service-offering-class.reducers'; @@ -29,7 +28,6 @@ import { VirtualMachine, VmState } from '../shared/vm.model'; [serviceOfferings]="offerings$ | async" [classes]="classes$ | async" [selectedClasses]="selectedClasses$ | async" - [classTags]="classTags$ | async" [viewMode]="viewMode$ | async" [query]="query$ | async" [isVmRunning]="isVmRunning()" @@ -51,7 +49,6 @@ export class ServiceOfferingDialogContainerComponent implements OnInit, AfterVie readonly defaultParams$ = this.store.select(fromServiceOfferings.getDefaultParams); readonly classes$ = this.store.select(fromSOClasses.selectAll); readonly selectedClasses$ = this.store.select(fromServiceOfferings.filterSelectedClasses); - readonly classTags$ = this.store.select(fromAccountTags.selectServiceOfferingClassTags); readonly viewMode$ = this.store.select(fromServiceOfferings.filterSelectedViewMode); public virtualMachine: VirtualMachine; diff --git a/src/app/vm/vm-creation/service-offering/vm-creation-service-offering.container.ts b/src/app/vm/vm-creation/service-offering/vm-creation-service-offering.container.ts index c9741b2075..c92e9d9aea 100644 --- a/src/app/vm/vm-creation/service-offering/vm-creation-service-offering.container.ts +++ b/src/app/vm/vm-creation/service-offering/vm-creation-service-offering.container.ts @@ -7,7 +7,6 @@ import { } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { Store } from '@ngrx/store'; -import * as fromAccountTags from '../../../reducers/account-tags/redux/account-tags.reducers'; import { State } from '../../../reducers/index'; import * as fromSOClasses from '../../../reducers/service-offerings/redux/service-offering-class.reducers'; @@ -28,7 +27,6 @@ import { ServiceOffering } from '../../../shared/models/service-offering.model'; [serviceOfferingId]="serviceOfferingId" [classes]="classes$ | async" [selectedClasses]="selectedClasses$ | async" - [classTags]="classTags$ | async" [viewMode]="viewMode$ | async" [query]="query$ | async" [restrictions]="customOfferingRestrictions" @@ -47,7 +45,6 @@ export class VmCreationServiceOfferingContainerComponent implements OnInit, Afte readonly classes$ = this.store.select(fromSOClasses.selectAll); readonly query$ = this.store.select(fromServiceOfferings.filterQuery); readonly selectedClasses$ = this.store.select(fromServiceOfferings.filterSelectedClasses); - readonly classTags$ = this.store.select(fromAccountTags.selectServiceOfferingClassTags); readonly viewMode$ = this.store.select(fromServiceOfferings.filterSelectedViewMode); public formMode = ServiceOfferingFromMode.SELECT;