first version of sample.component finished

This commit is contained in:
VLE2FE
2020-06-19 08:43:22 +02:00
parent 60298e53a5
commit 0d77113704
53 changed files with 1336 additions and 275 deletions

View File

@ -0,0 +1,53 @@
// 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<HttpClient>;
// let localStorageServiceSpy: jasmine.SpyObj<LocalStorageService>;
//
// 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<HttpClient>;
// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj<LocalStorageService>;
// });
//
// 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');
// });
// });

View File

@ -0,0 +1,55 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {LocalStorageService} from 'angular-2-local-storage';
import {Observable} from 'rxjs';
import {ErrorComponent} from '../error/error.component';
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
@Injectable({
providedIn: 'root'
})
export class ApiService {
private host = '/api';
constructor(
private http: HttpClient,
private storage: LocalStorageService,
private modalService: ModalService
) { }
get<T>(url, f: (data?: T, err?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.get(this.host + url, this.authOptions()), f);
}
post<T>(url, data = null, f: (data?: T, err?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.post(this.host + url, data, this.authOptions()), f);
}
put<T>(url, data = null, f: (data?: T, err?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.put(this.host + url, data, this.authOptions()), f);
}
delete<T>(url, f: (data?: T, err?) => void = () => {}) {
this.requestErrorHandler<T>(this.http.delete(this.host + url, this.authOptions()), f);
}
private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?) => void) {
observable.subscribe(data => {
f(data, undefined);
}, () => {
const modalRef = this.modalService.openComponent(ErrorComponent);
modalRef.instance.message = 'Network request failed!';
});
}
private authOptions() {
const auth = this.storage.get('basicAuth');
if (auth) {
return {headers: new HttpHeaders({Authorization: 'Basic ' + auth})};
}
else {
return {};
}
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AutocompleteService } from './autocomplete.service';
describe('AutocompleteService', () => {
let service: AutocompleteService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AutocompleteService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import {QuickScore} from 'quick-score';
import {of} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AutocompleteService {
constructor() { }
bind(ref, list) {
return this.search.bind(ref, list);
}
search(arr, str) {
const qs = new QuickScore(arr);
return of(str === '' ? [] : qs.search(str).map(e => e.item));
}
}

View File

@ -0,0 +1,81 @@
// 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<ApiService>;
// let localStorageServiceSpy: jasmine.SpyObj<LocalStorageService>;
//
// 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<ApiService>;
// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj<LocalStorageService>;
// });
//
// 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();
// });
// });
// });

View File

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import {ApiService} from './api.service';
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router';
import {LocalStorageService} from 'angular-2-local-storage';
import {Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoginService implements CanActivate {
private loggedIn;
constructor(
private api: ApiService,
private storage: LocalStorageService
) {
}
login(username = '', password = '') {
return new Promise(resolve => {
if (username !== '') {
this.storage.set('basicAuth', btoa(username + ':' + password));
}
this.api.get('/authorized', (data: any, error) => {
if (!error) {
if (data.status === 'Authorization successful') {
this.loggedIn = true;
resolve(true);
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
} else {
this.loggedIn = false;
this.storage.remove('basicAuth');
resolve(false);
}
});
});
}
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
return new Observable<boolean>(observer => {
if (this.loggedIn === undefined) {
this.login().then(res => {
observer.next(res as any);
observer.complete();
});
}
else {
observer.next(this.loggedIn);
observer.complete();
}
});
}
}

View File

@ -0,0 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { ValidationService } from './validation.service';
let validationService: ValidationService;
describe('ValidationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ValidationService]
});
validationService = TestBed.inject(ValidationService);
});
it('should be created', () => {
expect(validationService).toBeTruthy();
});
it('should return true on a correct username', () => {
expect(validationService.username('abc')).toEqual({ok: true, error: ''});
});
it('should return an error on an incorrect username', () => {
expect(validationService.username('abc#')).toEqual({ok: false, error: 'username must only contain a-z0-9-_.'});
});
it('should return true on a correct password', () => {
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!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'});
});
});

View File

@ -0,0 +1,124 @@
import { Injectable } from '@angular/core';
import Joi from '@hapi/joi';
import {AbstractControl} from '@angular/forms';
@Injectable({
providedIn: 'root'
})
export class ValidationService {
private vUsername = Joi.string()
.lowercase()
.pattern(new RegExp('^[a-z0-9-_.]+$'))
.min(1)
.max(128);
private vPassword = Joi.string()
.pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
.max(128);
constructor() { }
generate(method, args) { // generate a Validator function
return (control: AbstractControl): {[key: string]: any} | null => {
let ok;
let error;
if (args) {
({ok, error} = this[method](control.value, ...args));
}
else {
({ok, error} = this[method](control.value));
}
return ok ? null : { failure: error };
};
}
username(data) {
const {ignore, error} = this.vUsername.validate(data);
if (error) {
return {ok: false, error: 'username must only contain a-z0-9-_.'};
}
return {ok: true, error: ''};
}
password(data) {
const {ignore, error} = this.vPassword.validate(data);
if (error) {
if (Joi.string().min(8).validate(data).error) {
return {ok: false, error: 'password must have at least 8 characters'};
}
else if (Joi.string().pattern(/[a-z]+/).validate(data).error) {
return {ok: false, error: 'password must have at least one lowercase character'};
}
else if (Joi.string().pattern(/[A-Z]+/).validate(data).error) {
return {ok: false, error: 'password must have at least one uppercase character'};
}
else if (Joi.string().pattern(/[0-9]+/).validate(data).error) {
return {ok: false, error: 'password must have at least one number'};
}
else if (Joi.string().pattern(/[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~]+/).validate(data).error) {
return {ok: false, error: 'password must have at least one of the following characters !"#%&\'()*+,-.\\/:;<=>?@[]^_`{|}~'};
}
else {
return {ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'};
}
}
return {ok: true, error: ''};
}
string(data) {
const {ignore, error} = Joi.string().max(128).allow('').validate(data);
if (error) {
return {ok: false, error: 'must contain max 128 characters'};
}
return {ok: true, error: ''};
}
stringOf(data, list) {
const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: 'must be one of ' + list.join(', ')};
}
return {ok: true, error: ''};
}
stringLength(data, length) {
const {ignore, error} = Joi.string().max(length).allow('').validate(data);
if (error) {
return {ok: false, error: 'must contain max ' + length + ' characters'};
}
return {ok: true, error: ''};
}
minMax(data, min, max) {
const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
if (error) {
return {ok: false, error: `must be between ${min} and ${max}`};
}
return {ok: true, error: ''};
}
min(data, min) {
const {ignore, error} = Joi.number().allow('').min(min).validate(data);
if (error) {
return {ok: false, error: `must not be below ${min}`};
}
return {ok: true, error: ''};
}
max(data, max) {
const {ignore, error} = Joi.number().allow('').min(max).validate(data);
if (error) {
return {ok: false, error: `must not be above ${max}`};
}
return {ok: true, error: ''};
}
unique(data, list) {
const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data);
if (error) {
return {ok: false, error: `values must be unique`};
}
return {ok: true, error: ''};
}
}