finished tests

This commit is contained in:
VLE2FE 2020-05-22 09:36:50 +02:00
parent 3048899247
commit fd7cf7b77a
20 changed files with 3431 additions and 1542 deletions

View File

@ -22,7 +22,7 @@
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets",
@ -46,7 +46,6 @@
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
@ -90,7 +89,6 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"codeCoverage": true,
"assets": [
"src/favicon.ico",
"src/assets"

4622
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,31 +12,31 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.14",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/core": "~8.2.14",
"@angular/forms": "~8.2.14",
"@angular/platform-browser": "~8.2.14",
"@angular/platform-browser-dynamic": "~8.2.14",
"@angular/router": "~8.2.14",
"@angular/animations": "~9.1.7",
"@angular/common": "~9.1.7",
"@angular/compiler": "~9.1.7",
"@angular/core": "~9.1.7",
"@angular/forms": "~9.1.7",
"@angular/platform-browser": "~9.1.7",
"@angular/platform-browser-dynamic": "~9.1.7",
"@angular/router": "~9.1.7",
"@hapi/joi": "^17.1.1",
"@inst-iot/bosch-angular-ui-components": "^0.5.30",
"angular-2-local-storage": "^3.0.2",
"flatpickr": "^4.6.3",
"rxjs": "~6.4.0",
"rxjs": "~6.5.5",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.803.26",
"@angular/cli": "~8.3.26",
"@angular/compiler-cli": "~8.2.14",
"@angular/language-service": "~8.2.14",
"@angular-devkit/build-angular": "~0.901.6",
"@angular/cli": "~9.1.6",
"@angular/compiler-cli": "~9.1.7",
"@angular/language-service": "~9.1.7",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
@ -47,6 +47,6 @@
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
"typescript": "~3.8.3"
}
}

View File

@ -1,14 +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(() => TestBed.configureTestingModule({
providers: [ApiService, HttpClient]
}));
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', () => {
const service: ApiService = TestBed.inject(ApiService);
expect(service).toBeTruthy();
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

@ -2,10 +2,12 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from './home/home.component';
import {LoginService} from './login.service';
import {SamplesComponent} from './samples/samples.component';
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'samples', component: SamplesComponent},
{path: 'replace-me', component: HomeComponent, canActivate: [LoginService]},
// if not authenticated

View File

@ -1,6 +1,7 @@
<rb-full-header>
<nav *rbMainNavItems>
<a routerLink="/" routerLinkActive="active" rbLoadingLink>Home</a>
<a routerLink="/samples" routerLinkActive="active" rbLoadingLink>Samples</a>
</nav>
<div *rbSubBrandHeader>Digital Fingerprint of Plastics</div>
</rb-full-header>

View File

@ -1,30 +1,45 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import {TestBed, async, ComponentFixture} from '@angular/core/testing';
import { AppComponent } from './app.component';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {By} from '@angular/platform-browser';
import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components';
import {RouterTestingModule} from '@angular/router/testing';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let css; // get native element by css selector
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent ],
imports: [
RbUiComponentsModule,
RouterTestingModule
],
declarations: [
AppComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
]
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
css = (selector) => fixture.debugElement.query(By.css(selector)).nativeElement;
});
it(`should have the correct title'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('UI');
it('should create the app', () => {
expect(component).toBeTruthy();
});
it('should have the header', () => {
expect(css('rb-full-header')).toBeTruthy();
});
it('should have the correct app title', () => {
expect(css('rb-full-header div.sub-brand-content > div').innerText).toBe('Digital Fingerprint of Plastics');
});
it('should have the page container', () => {
expect(css('.container')).toBeTruthy();
});
});

View File

@ -6,5 +6,4 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'UI';
}

View File

@ -9,12 +9,14 @@ import { HomeComponent } from './home/home.component';
import {FormsModule} from '@angular/forms';
import {LocalStorageModule} from 'angular-2-local-storage';
import {HttpClientModule} from '@angular/common/http';
import { SamplesComponent } from './samples/samples.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
HomeComponent
HomeComponent,
SamplesComponent
],
imports: [
LocalStorageModule.forRoot({

View File

@ -1,6 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import {Component} from '@angular/core';
import {By} from '@angular/platform-browser';
@Component({selector: 'app-login', template: ''})
class LoginStubComponent {}
describe('HomeComponent', () => {
let component: HomeComponent;
@ -8,7 +12,10 @@ describe('HomeComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
declarations: [
HomeComponent,
LoginStubComponent
]
})
.compileComponents();
}));
@ -22,4 +29,8 @@ describe('HomeComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should load the login component', () => {
expect(fixture.debugElement.query(By.css('app-login'))).toBeTruthy();
});
});

View File

@ -1,12 +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(() => TestBed.configureTestingModule({}));
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', () => {
const service: LoginService = TestBed.inject(LoginService);
expect(service).toBeTruthy();
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

@ -1,25 +1,107 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import { LoginComponent } from './login.component';
import {LoginService} from '../login.service';
import {ValidationService} from '../validation.service';
import {RbUiComponentsModule} from '@inst-iot/bosch-angular-ui-components';
import {FormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
let validationServiceSpy: jasmine.SpyObj<ValidationService>;
let loginServiceSpy: jasmine.SpyObj<LoginService>;
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let css; // get native element by css selector
let cssd; // get debug element by css selector
beforeEach(async(() => {
const validationSpy = jasmine.createSpyObj('ValidationService', ['username', 'password']);
const loginSpy = jasmine.createSpyObj('LoginService', ['login']);
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
declarations: [ LoginComponent ],
imports: [
RbUiComponentsModule,
FormsModule
],
providers: [
{provide: ValidationService, useValue: validationSpy},
{provide: LoginService, useValue: loginSpy}
]
})
.compileComponents();
validationServiceSpy = TestBed.inject(ValidationService) as jasmine.SpyObj<ValidationService>;
loginServiceSpy = TestBed.inject(LoginService) as jasmine.SpyObj<LoginService>;
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
cssd = (selector) => fixture.debugElement.query(By.css(selector));
css = (selector) => fixture.debugElement.query(By.css(selector)).nativeElement;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have the `Please log in` heading', () => {
expect(css('h2').innerText).toBe('Please log in');
});
it('should have empty credential inputs', () => {
expect(css('rb-form-input[label=username] input').value).toBe('');
expect(css('rb-form-input[label=password] input').value).toBe('');
});
it('should have an empty message at the beginning', () => {
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('.message').innerText).toBe('username must only contain a-z0-9-_.');
});
it('should display a message when a wrong password was entered', () => {
validationServiceSpy.username.and.returnValue({ok: true, error: ''});
validationServiceSpy.password.and.returnValue({ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'});
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();
});
it('should call the LoginService with valid credentials', () => {
validationServiceSpy.username.and.returnValue({ok: true, error: ''});
validationServiceSpy.password.and.returnValue({ok: true, error: ''});
loginServiceSpy.login.and.returnValue(new Promise(r => r(true)));
cssd('.login-button').triggerEventHandler('click', null);
expect(loginServiceSpy.login.calls.count()).toBe(1);
});
it('should display an error if the LoginService could not authenticate', fakeAsync(() => {
validationServiceSpy.username.and.returnValue({ok: true, error: ''});
validationServiceSpy.password.and.returnValue({ok: true, error: ''});
loginServiceSpy.login.and.returnValue(new Promise(r => r(false)));
cssd('.login-button').triggerEventHandler('click', null);
expect(loginServiceSpy.login.calls.count()).toBe(1);
tick();
fixture.detectChanges();
expect(css('.message').innerText).toBe('Wrong credentials! Try again.');
}));
});

View File

@ -25,12 +25,11 @@ export class LoginComponent implements OnInit {
login() {
const {ok: userOk, error: userError} = this.validate.username(this.username);
const {ok: passwordOk, error: passwordError} = this.validate.password(this.password);
this.message = userError + (userError + passwordError === '' ? '' : '\n') + passwordError; // display errors
console.log(this.message);
this.message = userError + (userError !== '' && passwordError !== '' ? '\n' : '') + passwordError; // display errors
if (userOk && passwordOk) {
this.loginService.login(this.username, this.password).then(ok => {
if (ok) {
this.message = 'Login successful'; // TODO: think about following action, write tests!
this.message = 'Login successful'; // TODO: think about following action
}
else {
this.message = 'Wrong credentials! Try again.';

View File

@ -0,0 +1 @@
<p>samples works!</p>

View File

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SamplesComponent } from './samples.component';
describe('SamplesComponent', () => {
let component: SamplesComponent;
let fixture: ComponentFixture<SamplesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SamplesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SamplesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-samples',
templateUrl: './samples.component.html',
styleUrls: ['./samples.component.scss']
})
export class SamplesComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,12 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { ValidationService } from './validation.service';
let validationService: ValidationService;
describe('ValidationService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ValidationService]
});
validationService = TestBed.inject(ValidationService);
});
it('should be created', () => {
const service: ValidationService = TestBed.inject(ValidationService);
expect(service).toBeTruthy();
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

@ -1,5 +1,5 @@
{
"/": {
"/api": {
"target": "http://localhost:3000",
"secure": false
}

View File

@ -9,7 +9,7 @@
"src/polyfills.ts"
],
"include": [
"src/**/*.ts"
"src/**/*.d.ts"
],
"exclude": [
"src/test.ts",