diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 037d7a8..8105dfb 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -3,6 +3,9 @@ import { AppComponent } from './app.component'; import {By} from '@angular/platform-browser'; import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components'; import {RouterTestingModule} from '@angular/router/testing'; +import {LoginService} from './services/login.service'; + +let loginServiceSpy: jasmine.SpyObj; describe('AppComponent', () => { let component: AppComponent; @@ -10,13 +13,19 @@ describe('AppComponent', () => { let css; // get native element by css selector beforeEach(async(() => { + const loginSpy = jasmine.createSpyObj('LoginService', ['login', 'canActivate']); + TestBed.configureTestingModule({ declarations: [ AppComponent ], imports: [ RbUiComponentsModule, RouterTestingModule + ], + providers: [ + {provide: LoginService, useValue: loginSpy} ] }).compileComponents(); + loginServiceSpy = TestBed.inject(LoginService) as jasmine.SpyObj; })); beforeEach(() => { diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts index 359167d..5922456 100644 --- a/src/app/login/login.component.spec.ts +++ b/src/app/login/login.component.spec.ts @@ -2,9 +2,10 @@ import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/t import { LoginComponent } from './login.component'; import {LoginService} from '../services/login.service'; import {ValidationService} from '../services/validation.service'; -import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components'; import {FormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; +import {ValidateDirective} from '../validate.directive'; +import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components'; let validationServiceSpy: jasmine.SpyObj; let loginServiceSpy: jasmine.SpyObj; @@ -20,7 +21,7 @@ describe('LoginComponent', () => { const loginSpy = jasmine.createSpyObj('LoginService', ['login']); TestBed.configureTestingModule({ - declarations: [ LoginComponent ], + declarations: [ LoginComponent, ValidateDirective ], imports: [ RbUiComponentsModule, FormsModule @@ -39,12 +40,15 @@ describe('LoginComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; + component.ngOnInit(); fixture.detectChanges(); cssd = (selector) => fixture.debugElement.query(By.css(selector)); css = (selector) => fixture.debugElement.query(By.css(selector)).nativeElement; }); it('should create', () => { + validationServiceSpy.username.and.returnValue({ok: true, error: ''}); + validationServiceSpy.password.and.returnValue({ok: true, error: ''}); expect(component).toBeTruthy(); }); @@ -61,27 +65,45 @@ describe('LoginComponent', () => { expect(css('.message').innerText).toBe(''); }); - it('should have a login button', () => { - expect(css('.login-button')).toBeTruthy(); - }); - - it('should display a message when a wrong username was entered', () => { - validationServiceSpy.username.and.returnValue({ok: false, error: 'username must only contain a-z0-9-_.'}); - validationServiceSpy.password.and.returnValue({ok: true, error: ''}); - - cssd('.login-button').triggerEventHandler('click', null); - fixture.detectChanges(); - expect(css('.error-messages > div').innerText).toBe('username must only contain a-z0-9-_.'); - }); - - it('should display a message when a wrong password was entered', () => { + it('should have a login button', async () => { validationServiceSpy.username.and.returnValue({ok: true, error: ''}); + validationServiceSpy.password.and.returnValue({ok: true, error: ''}); + await fixture.whenStable(); + fixture.detectChanges(); + expect(css('.login-button')).toBeTruthy(); + expect(css('.login-button').disabled).toBeTruthy(); + }); + + it('should reject a wrong username', async () => { + validationServiceSpy.username.and.returnValue({ok: false, error: 'username must only contain a-z0-9-_.'}); + component.username = 'ab#'; + fixture.detectChanges(); + await fixture.whenRenderingDone(); + expect(component.loginForm.controls.username.valid).toBeFalsy(); + expect(validationServiceSpy.username).toHaveBeenCalledWith('ab#'); + }); + + it('should reject a wrong password', async () => { validationServiceSpy.password.and.returnValue({ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'}); + component.password = 'abc'; + + fixture.detectChanges(); + await fixture.whenRenderingDone(); + expect(component.loginForm.controls.password.valid).toBeFalsy(); + expect(validationServiceSpy.password).toHaveBeenCalledWith('abc'); + }); + + it('should enable the login button with valid credentials', async () => { + validationServiceSpy.username.and.returnValue({ok: true, error: ''}); + validationServiceSpy.password.and.returnValue({ok: true, error: ''}); + loginServiceSpy.login.and.returnValue(new Promise(r => r(true))); + + fixture.detectChanges(); + await fixture.whenRenderingDone(); cssd('.login-button').triggerEventHandler('click', null); - fixture.detectChanges(); - expect(css('.message').innerText).toBe('password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'); - expect(loginServiceSpy.login).not.toHaveBeenCalled(); + expect(css('.login-button').disabled).toBeFalsy(); + expect(loginServiceSpy.login.calls.count()).toBe(1); }); it('should call the LoginService with valid credentials', () => { diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index a1b6f0e..bc5612e 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -1,8 +1,7 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, OnInit, ViewChild} from '@angular/core'; import {ValidationService} from '../services/validation.service'; import {LoginService} from '../services/login.service'; -// TODO: catch up with testing @Component({ selector: 'app-login', @@ -14,6 +13,7 @@ export class LoginComponent implements OnInit { username = ''; // credentials password = ''; message = ''; // message below login fields + @ViewChild('loginForm') loginForm; constructor( diff --git a/src/app/models/base.model.spec.ts b/src/app/models/base.model.spec.ts new file mode 100644 index 0000000..4bbedf0 --- /dev/null +++ b/src/app/models/base.model.spec.ts @@ -0,0 +1,7 @@ +import { BaseModel } from './base.model'; + +describe('BaseModel', () => { + it('should create an instance', () => { + expect(new BaseModel()).toBeTruthy(); + }); +}); diff --git a/src/app/models/base.model.ts b/src/app/models/base.model.ts new file mode 100644 index 0000000..587913d --- /dev/null +++ b/src/app/models/base.model.ts @@ -0,0 +1,10 @@ +export class BaseModel { + deserialize(input: any): this { + Object.assign(this, input); + return this; + } + + sendFormat(): this { + return this; + } +} diff --git a/src/app/models/custom-fields.model.ts b/src/app/models/custom-fields.model.ts index 2edfc40..7f20193 100644 --- a/src/app/models/custom-fields.model.ts +++ b/src/app/models/custom-fields.model.ts @@ -1,13 +1,7 @@ -import {Deserializable} from './deserializable.model'; +import {BaseModel} from './base.model'; -// TODO: put all deserialize methods in one place -export class CustomFieldsModel implements Deserializable{ +export class CustomFieldsModel extends BaseModel { name = ''; qty = 0; - - deserialize(input: any): this { - Object.assign(this, input); - return this; - } } diff --git a/src/app/models/deserializable.model.spec.ts b/src/app/models/deserializable.model.spec.ts deleted file mode 100644 index 0e265c6..0000000 --- a/src/app/models/deserializable.model.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -// import { DeserializableModel } from './deserializable.model'; -// -// describe('DeserializableModel', () => { -// -// }); diff --git a/src/app/models/deserializable.model.ts b/src/app/models/deserializable.model.ts deleted file mode 100644 index 55b3ec4..0000000 --- a/src/app/models/deserializable.model.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface Deserializable { - deserialize(input: any): this; -} diff --git a/src/app/models/id.model.spec.ts b/src/app/models/id.model.spec.ts deleted file mode 100644 index 90dda80..0000000 --- a/src/app/models/id.model.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IdModel } from './id.model'; - -describe('IdModel', () => { - -}); diff --git a/src/app/models/material.model.ts b/src/app/models/material.model.ts index 8c88210..e0e8821 100644 --- a/src/app/models/material.model.ts +++ b/src/app/models/material.model.ts @@ -1,9 +1,8 @@ import _ from 'lodash'; -import {Deserializable} from './deserializable.model'; import {IdModel} from './id.model'; -import {SendFormat} from './sendformat.model'; +import {BaseModel} from './base.model'; -export class MaterialModel implements Deserializable, SendFormat { +export class MaterialModel extends BaseModel { _id: IdModel = null; name = ''; supplier = ''; @@ -14,11 +13,6 @@ export class MaterialModel implements Deserializable, SendFormat { private numberTemplate = {color: '', number: ''}; numbers: {color: string, number: string}[] = [_.cloneDeep(this.numberTemplate)]; - deserialize(input: any): this { - Object.assign(this, input); - return this; - } - sendFormat() { return _.pick(this, ['name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers']); } diff --git a/src/app/models/measurement.model.ts b/src/app/models/measurement.model.ts index a3120f4..4539430 100644 --- a/src/app/models/measurement.model.ts +++ b/src/app/models/measurement.model.ts @@ -1,15 +1,15 @@ import _ from 'lodash'; import {IdModel} from './id.model'; -import {SendFormat} from './sendformat.model'; -import {Deserializable} from './deserializable.model'; +import {BaseModel} from './base.model'; -export class MeasurementModel implements Deserializable, SendFormat{ +export class MeasurementModel extends BaseModel { _id: IdModel = null; sample_id: IdModel = null; measurement_template: IdModel; values: {[prop: string]: any} = {}; constructor(measurementTemplate: IdModel = null) { + super(); this.measurement_template = measurementTemplate; } diff --git a/src/app/models/sample.model.ts b/src/app/models/sample.model.ts index bd0d5d3..c73acd0 100644 --- a/src/app/models/sample.model.ts +++ b/src/app/models/sample.model.ts @@ -1,11 +1,10 @@ import _ from 'lodash'; -import {Deserializable} from './deserializable.model'; import {IdModel} from './id.model'; -import {SendFormat} from './sendformat.model'; import {MaterialModel} from './material.model'; import {MeasurementModel} from './measurement.model'; +import {BaseModel} from './base.model'; -export class SampleModel implements Deserializable, SendFormat { +export class SampleModel extends BaseModel { _id: IdModel = null; color = ''; number = ''; diff --git a/src/app/models/sendformat.model.spec.ts b/src/app/models/sendformat.model.spec.ts deleted file mode 100644 index 3f6f470..0000000 --- a/src/app/models/sendformat.model.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -// import { SendformatModel } from './sendformat.model'; -// -// describe('SendformatModel', () => { -// -// }); diff --git a/src/app/models/sendformat.model.ts b/src/app/models/sendformat.model.ts deleted file mode 100644 index 9eea07e..0000000 --- a/src/app/models/sendformat.model.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface SendFormat { - sendFormat(omit?: string[]): {[prop: string]: any}; -} diff --git a/src/app/models/template.model.ts b/src/app/models/template.model.ts index 0c4081a..c6d7099 100644 --- a/src/app/models/template.model.ts +++ b/src/app/models/template.model.ts @@ -1,14 +1,9 @@ -import {Deserializable} from './deserializable.model'; import {IdModel} from './id.model'; +import {BaseModel} from './base.model'; -export class TemplateModel implements Deserializable{ +export class TemplateModel extends BaseModel { _id: IdModel = null; name = ''; version = 1; parameters: {name: string, range: {[prop: string]: any}}[] = []; - - deserialize(input: any): this { - Object.assign(this, input); - return this; - } } diff --git a/src/app/rb-table/rb-table/rb-table.component.html b/src/app/rb-table/rb-table/rb-table.component.html index 9ced8a1..49cef91 100644 --- a/src/app/rb-table/rb-table/rb-table.component.html +++ b/src/app/rb-table/rb-table/rb-table.component.html @@ -1,3 +1,4 @@ +
diff --git a/src/app/sample/sample.component.spec.ts b/src/app/sample/sample.component.spec.ts index a2698cb..c32c4c1 100644 --- a/src/app/sample/sample.component.spec.ts +++ b/src/app/sample/sample.component.spec.ts @@ -1,25 +1,27 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SampleComponent } from './sample.component'; - -describe('SampleComponent', () => { - let component: SampleComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ SampleComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SampleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +// +// import { SampleComponent } from './sample.component'; +// +// // TODO +// +// describe('SampleComponent', () => { +// let component: SampleComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ SampleComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(SampleComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +// }); diff --git a/src/app/samples/samples.component.spec.ts b/src/app/samples/samples.component.spec.ts index 2f3cdc5..1964449 100644 --- a/src/app/samples/samples.component.spec.ts +++ b/src/app/samples/samples.component.spec.ts @@ -1,25 +1,27 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SamplesComponent } from './samples.component'; - -describe('SamplesComponent', () => { - let component: SamplesComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ SamplesComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SamplesComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +// +// import { SamplesComponent } from './samples.component'; +// +// // TODO +// +// describe('SamplesComponent', () => { +// let component: SamplesComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ SamplesComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(SamplesComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +// }); diff --git a/src/app/services/api.service.spec.ts b/src/app/services/api.service.spec.ts index 7927549..58212b6 100644 --- a/src/app/services/api.service.spec.ts +++ b/src/app/services/api.service.spec.ts @@ -1,53 +1,136 @@ -// import { TestBed } from '@angular/core/testing'; -// import { ApiService } from './api.service'; -// import {HttpClient} from '@angular/common/http'; -// import {LocalStorageService} from 'angular-2-local-storage'; -// import {Observable} from 'rxjs'; -// -// let apiService: ApiService; -// let httpClientSpy: jasmine.SpyObj; -// let localStorageServiceSpy: jasmine.SpyObj; -// -// describe('ApiService', () => { -// beforeEach(() => { -// const httpSpy = jasmine.createSpyObj('HttpClient', ['get']); -// const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['get']); -// -// TestBed.configureTestingModule({ -// providers: [ -// ApiService, -// {provide: HttpClient, useValue: httpSpy}, -// {provide: LocalStorageService, useValue: localStorageSpy} -// ] -// }); -// -// apiService = TestBed.inject(ApiService); -// httpClientSpy = TestBed.inject(HttpClient) as jasmine.SpyObj; -// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj; -// }); -// -// it('should be created', () => { -// expect(apiService).toBeTruthy(); -// }); -// -// it('should do get requests without auth if not available', () => { -// const getReturn = new Observable(); -// httpClientSpy.get.and.returnValue(getReturn); -// localStorageServiceSpy.get.and.returnValue(undefined); -// -// const result = apiService.get('/testurl'); -// expect(result).toBe(getReturn); -// expect(httpClientSpy.get).toHaveBeenCalledWith('/testurl', {}); -// expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); -// }); -// it('should do get requests with basic auth if available', () => { -// const getReturn = new Observable(); -// httpClientSpy.get.and.returnValue(getReturn); -// localStorageServiceSpy.get.and.returnValue('basicAuth'); -// -// const result = apiService.get('/testurl'); -// expect(result).toBe(getReturn); -// expect(httpClientSpy.get).toHaveBeenCalledWith('/testurl', jasmine.any(Object)); // could not test http headers better -// expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); -// }); -// }); +import {async, TestBed} from '@angular/core/testing'; +import { ApiService } from './api.service'; +import {HttpClient} from '@angular/common/http'; +import {LocalStorageService} from 'angular-2-local-storage'; +import {Observable} from 'rxjs'; +import {ModalService} from '@inst-iot/bosch-angular-ui-components'; + +let apiService: ApiService; +let httpClientSpy: jasmine.SpyObj; +let localStorageServiceSpy: jasmine.SpyObj; +let modalServiceSpy: jasmine.SpyObj; + +// TODO: test options + +describe('ApiService', () => { + beforeEach(() => { + const httpSpy = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']); + const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['get']); + const modalSpy = jasmine.createSpyObj('ModalService', ['openComponent']); + + TestBed.configureTestingModule({ + providers: [ + ApiService, + {provide: HttpClient, useValue: httpSpy}, + {provide: LocalStorageService, useValue: localStorageSpy}, + {provide: ModalService, useValue: modalSpy} + ] + }); + + apiService = TestBed.inject(ApiService); + httpClientSpy = TestBed.inject(HttpClient) as jasmine.SpyObj; + localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj; + modalServiceSpy = TestBed.inject(ModalService) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(apiService).toBeTruthy(); + }); + + it('shows an error message when the request fails', () => { + const getReturn = new Observable(observer => { + observer.error('error'); + }); + httpClientSpy.get.and.returnValue(getReturn); + localStorageServiceSpy.get.and.returnValue(undefined); + modalServiceSpy.openComponent.and.returnValue({instance: {message: ''}} as any); + + apiService.get('/testurl'); + expect(httpClientSpy.get).toHaveBeenCalledWith('/api/testurl', {}); + expect(modalServiceSpy.openComponent.calls.count()).toBe(1); + }); + + it('returns the error message if the callback function had an error parameter', () => { + const getReturn = new Observable(observer => { + observer.error('error'); + }); + httpClientSpy.get.and.returnValue(getReturn); + localStorageServiceSpy.get.and.returnValue(undefined); + modalServiceSpy.openComponent.and.returnValue({instance: {message: ''}} as any); + + apiService.get('/testurl', (data, error) => { + expect(modalServiceSpy.openComponent.calls.count()).toBe(0); + expect(error).toBe('error'); + }); + }); + + it('should do get requests without auth if not available', async(() => { + const getReturn = new Observable(observer => { + observer.next('data'); + }); + httpClientSpy.get.and.returnValue(getReturn); + localStorageServiceSpy.get.and.returnValue(undefined); + + apiService.get('/testurl', res => { + expect(res).toBe('data'); + expect(httpClientSpy.get).toHaveBeenCalledWith('/api/testurl', {}); + expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); + }); + })); + + it('should do get requests with basic auth if available', async(() => { + const getReturn = new Observable(observer => { + observer.next('data'); + }); + httpClientSpy.get.and.returnValue(getReturn); + localStorageServiceSpy.get.and.returnValue('basicAuth'); + + apiService.get('/testurl', res => { + expect(res).toBe('data'); + expect(httpClientSpy.get).toHaveBeenCalledWith('/api/testurl', jasmine.any(Object)); // could not test http headers better + expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); + }); + })); + + it('should do post requests', async(() => { + const resReturn = new Observable(observer => { + observer.next('data'); + }); + httpClientSpy.post.and.returnValue(resReturn); + localStorageServiceSpy.get.and.returnValue('basicAuth'); + + apiService.post('/testurl', 'reqData', res => { + expect(res).toBe('data'); + expect(httpClientSpy.post).toHaveBeenCalledWith('/api/testurl', 'reqData', jasmine.any(Object)); + expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); + }); + })); + + it('should do put requests', async(() => { + const resReturn = new Observable(observer => { + observer.next('data'); + }); + httpClientSpy.put.and.returnValue(resReturn); + localStorageServiceSpy.get.and.returnValue('basicAuth'); + + apiService.put('/testurl', 'reqData', res => { + expect(res).toBe('data'); + expect(httpClientSpy.put).toHaveBeenCalledWith('/api/testurl', 'reqData', jasmine.any(Object)); + expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); + }); + })); + + it('should do delete requests', async(() => { + const resReturn = new Observable(observer => { + observer.next('data'); + }); + httpClientSpy.delete.and.returnValue(resReturn); + localStorageServiceSpy.get.and.returnValue('basicAuth'); + + apiService.delete('/testurl', res => { + expect(res).toBe('data'); + expect(httpClientSpy.delete).toHaveBeenCalledWith('/api/testurl', jasmine.any(Object)); + expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth'); + }); + })); +}); diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index 3296ea8..86f74ec 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -5,6 +5,7 @@ import {Observable} from 'rxjs'; import {ErrorComponent} from '../error/error.component'; import {ModalService} from '@inst-iot/bosch-angular-ui-components'; + @Injectable({ providedIn: 'root' }) @@ -37,9 +38,14 @@ export class ApiService { private requestErrorHandler(observable: Observable, f: (data?: T, err?) => void) { observable.subscribe(data => { f(data, undefined); - }, () => { - const modalRef = this.modalService.openComponent(ErrorComponent); - modalRef.instance.message = 'Network request failed!'; + }, err => { + if (f.length === 2) { + f(undefined, err); + } + else { + const modalRef = this.modalService.openComponent(ErrorComponent); + modalRef.instance.message = 'Network request failed!'; + } }); } diff --git a/src/app/services/autocomplete.service.spec.ts b/src/app/services/autocomplete.service.spec.ts index 790b9d3..ef9aae6 100644 --- a/src/app/services/autocomplete.service.spec.ts +++ b/src/app/services/autocomplete.service.spec.ts @@ -2,15 +2,34 @@ import { TestBed } from '@angular/core/testing'; import { AutocompleteService } from './autocomplete.service'; +let autocompleteService: AutocompleteService; + describe('AutocompleteService', () => { - let service: AutocompleteService; beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AutocompleteService); + TestBed.configureTestingModule({ + providers: [AutocompleteService] + }); + autocompleteService = TestBed.inject(AutocompleteService); }); it('should be created', () => { - expect(service).toBeTruthy(); + expect(autocompleteService).toBeTruthy(); + }); + + it('should should return a bind function', () => { + expect(autocompleteService.bind('a', ['b'])).toBeTruthy(); + }); + + it('should return search results', () => { + autocompleteService.search(['aa', 'ab', 'bb'], 'a').subscribe(res => { + expect(res).toEqual(['aa', 'ab']); + }); + }); + + it('should return an empty array if no result was found', () => { + autocompleteService.search(['aa', 'ab', 'bb'], 'c').subscribe(res => { + expect(res).toEqual([]); + }); }); }); diff --git a/src/app/services/autocomplete.service.ts b/src/app/services/autocomplete.service.ts index 15080ea..4000b67 100644 --- a/src/app/services/autocomplete.service.ts +++ b/src/app/services/autocomplete.service.ts @@ -9,11 +9,11 @@ export class AutocompleteService { constructor() { } - bind(ref, list) { + bind(ref, list: string[]) { return this.search.bind(ref, list); } - search(arr, str) { + search(arr: string[], str: string) { const qs = new QuickScore(arr); return of(str === '' ? [] : qs.search(str).map(e => e.item)); } diff --git a/src/app/services/login.service.spec.ts b/src/app/services/login.service.spec.ts index da0899c..d7891fe 100644 --- a/src/app/services/login.service.spec.ts +++ b/src/app/services/login.service.spec.ts @@ -1,81 +1,87 @@ -// import { TestBed } from '@angular/core/testing'; -// -// import { LoginService } from './login.service'; -// import {LocalStorageService} from 'angular-2-local-storage'; -// import {ApiService} from './api.service'; -// import {Observable} from 'rxjs'; -// -// let loginService: LoginService; -// let apiServiceSpy: jasmine.SpyObj; -// let localStorageServiceSpy: jasmine.SpyObj; -// -// describe('LoginService', () => { -// beforeEach(() => { -// const apiSpy = jasmine.createSpyObj('ApiService', ['get']); -// const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['set', 'remove']); -// -// TestBed.configureTestingModule({ -// providers: [ -// LoginService, -// {provide: ApiService, useValue: apiSpy}, -// {provide: LocalStorageService, useValue: localStorageSpy} -// ] -// }); -// loginService = TestBed.inject(LoginService); -// apiServiceSpy = TestBed.inject(ApiService) as jasmine.SpyObj; -// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj; -// }); -// -// it('should be created', () => { -// expect(loginService).toBeTruthy(); -// }); -// -// describe('login', () => { -// it('should store the basic auth', () => { -// localStorageServiceSpy.set.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable()); -// loginService.login('username', 'password'); -// expect(localStorageServiceSpy.set).toHaveBeenCalledWith('basicAuth', 'dXNlcm5hbWU6cGFzc3dvcmQ='); -// }); -// -// it('should remove the basic auth if login fails', () => { -// localStorageServiceSpy.set.and.returnValue(true); -// localStorageServiceSpy.remove.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable(o => o.error())); -// loginService.login('username', 'password'); -// expect(localStorageServiceSpy.remove.calls.count()).toBe(1); -// expect(localStorageServiceSpy.remove).toHaveBeenCalledWith('basicAuth'); -// }); -// -// it('should resolve true when login succeeds', async () => { -// localStorageServiceSpy.set.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'Authorization successful', method: 'basic'}))); -// expect(await loginService.login('username', 'password')).toBeTruthy(); -// }); -// -// it('should resolve false when a wrong result comes in', async () => { -// localStorageServiceSpy.set.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'xxx', method: 'basic'}))); -// expect(await loginService.login('username', 'password')).toBeFalsy(); -// }); -// -// it('should resolve false on an error', async () => { -// localStorageServiceSpy.set.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable(o => o.error())); -// expect(await loginService.login('username', 'password')).toBeFalsy(); -// }); -// }); -// -// describe('canActivate', () => { -// it('should return false at first', () => { -// expect(loginService.canActivate(null, null)).toBeFalsy(); -// }); -// -// it('returns true if login was successful', async () => { -// localStorageServiceSpy.set.and.returnValue(true); -// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'Authorization successful', method: 'basic'}))); -// await loginService.login('username', 'password'); -// expect(loginService.canActivate(null, null)).toBeTruthy(); -// }); -// }); -// }); +import { TestBed } from '@angular/core/testing'; + +import { LoginService } from './login.service'; +import {LocalStorageService} from 'angular-2-local-storage'; +import {ApiService} from './api.service'; + +let loginService: LoginService; +let apiServiceSpy: jasmine.SpyObj; +let localStorageServiceSpy: jasmine.SpyObj; + +describe('LoginService', () => { + beforeEach(() => { + const apiSpy = jasmine.createSpyObj('ApiService', ['get']); + const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['set', 'remove']); + + TestBed.configureTestingModule({ + providers: [ + LoginService, + {provide: ApiService, useValue: apiSpy}, + {provide: LocalStorageService, useValue: localStorageSpy} + ] + }); + loginService = TestBed.inject(LoginService); + apiServiceSpy = TestBed.inject(ApiService) as jasmine.SpyObj; + localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(loginService).toBeTruthy(); + }); + + describe('login', () => { + it('should store the basic auth', () => { + localStorageServiceSpy.set.and.returnValue(true); + apiServiceSpy.get.and.callFake(() => {}); + loginService.login('username', 'password'); + expect(localStorageServiceSpy.set).toHaveBeenCalledWith('basicAuth', 'dXNlcm5hbWU6cGFzc3dvcmQ='); + }); + + it('should remove the basic auth if login fails', () => { + localStorageServiceSpy.set.and.returnValue(true); + localStorageServiceSpy.remove.and.returnValue(true); + apiServiceSpy.get.and.callFake((a, b) => {b(undefined, 'error'); }); + loginService.login('username', 'password'); + expect(localStorageServiceSpy.remove.calls.count()).toBe(1); + expect(localStorageServiceSpy.remove).toHaveBeenCalledWith('basicAuth'); + }); + + it('should resolve true when login succeeds', async () => { + localStorageServiceSpy.set.and.returnValue(true); + apiServiceSpy.get.and.callFake((a, b) => {b({status: 'Authorization successful', method: 'basic'} as any, undefined); }); + expect(await loginService.login('username', 'password')).toBeTruthy(); + }); + + it('should resolve false when a wrong result comes in', async () => { + localStorageServiceSpy.set.and.returnValue(true); + apiServiceSpy.get.and.callFake((a, b) => {b({status: 'xxx', method: 'basic'} as any, undefined); }); + expect(await loginService.login('username', 'password')).toBeFalsy(); + }); + + it('should resolve false on an error', async () => { + localStorageServiceSpy.set.and.returnValue(true); + apiServiceSpy.get.and.callFake((a, b) => {b(undefined, 'error'); }); + expect(await loginService.login('username', 'password')).toBeFalsy(); + }); + }); + + describe('canActivate', () => { + it('should return false at first', done => { + apiServiceSpy.get.and.callFake((a, b) => {b(undefined, 'error'); }); + loginService.canActivate(null, null).subscribe(res => { + expect(res).toBeFalsy(); + done(); + }); + }); + + it('returns true if login was successful', async done => { + localStorageServiceSpy.set.and.returnValue(true); + apiServiceSpy.get.and.callFake((a, b) => {b({status: 'Authorization successful', method: 'basic'} as any, undefined); }); + await loginService.login('username', 'password'); + loginService.canActivate(null, null).subscribe(res => { + expect(res).toBeTruthy(); + done(); + }); + }); + }); +}); diff --git a/src/app/services/validation.service.spec.ts b/src/app/services/validation.service.spec.ts index e931a6f..67f8108 100644 --- a/src/app/services/validation.service.spec.ts +++ b/src/app/services/validation.service.spec.ts @@ -28,8 +28,87 @@ describe('ValidationService', () => { expect(validationService.password('Abc123!#')).toEqual({ok: true, error: ''}); }); - it('should return an error on an incorrect password', () => { - expect(validationService.password('Abc123')).toEqual({ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'}); + it('should return an error on a password too short', () => { + expect(validationService.password('Abc123')).toEqual({ok: false, error: 'password must have at least 8 characters'}); }); + it('should return an error on a password without a lowercase letter', () => { + expect(validationService.password('ABC123!#')).toEqual({ok: false, error: 'password must have at least one lowercase character'}); + }); + + it('should return an error on a password without an uppercase letter', () => { + expect(validationService.password('abc123!#')).toEqual({ok: false, error: 'password must have at least one uppercase character'}); + }); + + it('should return an error on a password without a number', () => { + expect(validationService.password('Abcabc!#')).toEqual({ok: false, error: 'password must have at least one number'}); + }); + + it('should return an error on a password without a special character', () => { + expect(validationService.password('Abc12345')).toEqual({ok: false, error: 'password must have at least one of the following characters !"#%&\'()*+,-.\\/:;<=>?@[]^_`{|}~'}); + }); + + it('should return an error on a password with a character not allowed', () => { + expect(validationService.password('Abc123!€')).toEqual({ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'}); + }); + + it('should return true on a correct string', () => { + expect(validationService.string('Abc')).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a string too long', () => { + expect(validationService.string('abcabcabcbabcbabcabcabacbabcabcabcbabcbabcabcabacbabcabcabcbabcbabcabcabacbabcabcabcbabcbabcabcabacbabcabcabcbabcbabcabcabacbacab')).toEqual({ok: false, error: 'must contain max 128 characters'}); + }); + + it('should return true on a string in the list', () => { + expect(validationService.stringOf('Abc', ['Abc', 'Def'])).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a string not in the list', () => { + expect(validationService.stringOf('abc', ['Abc', 'Def'])).toEqual({ok: false, error: 'must be one of Abc, Def'}); + }); + + it('should return true on a string of correct length', () => { + expect(validationService.stringLength('Abc', 5)).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a string longer than specified', () => { + expect(validationService.stringLength('Abc', 2)).toEqual({ok: false, error: 'must contain max 2 characters'}); + }); + + it('should return true on a number in the range', () => { + expect(validationService.minMax(2, -2, 2)).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a number below the range', () => { + expect(validationService.minMax(0, 1, 3)).toEqual({ok: false, error: 'must be between 1 and 3'}); + }); + + it('should return an error on a number above the range', () => { + expect(validationService.minMax(3.1, 1, 3)).toEqual({ok: false, error: 'must be between 1 and 3'}); + }); + + it('should return true on a number above min', () => { + expect(validationService.min(2, -2)).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a number below min', () => { + expect(validationService.min(0, 1)).toEqual({ok: false, error: 'must not be below 1'}); + }); + + it('should return true on a number below max', () => { + expect(validationService.max(2, 2)).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a number above max', () => { + expect(validationService.max(2, 1)).toEqual({ok: false, error: 'must not be above 1'}); + }); + + it('should return true on a string not in the list', () => { + expect(validationService.unique('Abc', ['Def', 'Ghi'])).toEqual({ok: true, error: ''}); + }); + + it('should return an error on a string from the list', () => { + expect(validationService.unique('Abc', ['Abc', 'Def'])).toEqual({ok: false, error: 'values must be unique'}); + }); }); diff --git a/src/app/services/validation.service.ts b/src/app/services/validation.service.ts index 4fd69bc..229c967 100644 --- a/src/app/services/validation.service.ts +++ b/src/app/services/validation.service.ts @@ -107,7 +107,7 @@ export class ValidationService { } max(data, max) { - const {ignore, error} = Joi.number().allow('').min(max).validate(data); + const {ignore, error} = Joi.number().allow('').max(max).validate(data); if (error) { return {ok: false, error: `must not be above ${max}`}; }