From 61844a348b2934321567b1457e6d05f318fc8b7e Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 6 Mar 2019 21:29:36 +0800 Subject: Reorganize file structure. --- .../app/user/user-dialog/user-dialog.component.css | 5 + .../user/user-dialog/user-dialog.component.html | 5 + .../user/user-dialog/user-dialog.component.spec.ts | 25 +++++ .../app/user/user-dialog/user-dialog.component.ts | 43 ++++++++ Timeline/ClientApp/src/app/user/user-info.ts | 4 + .../user-login-success.component.css | 7 ++ .../user-login-success.component.html | 5 + .../user-login-success.component.spec.ts | 25 +++++ .../user-login-success.component.ts | 22 ++++ .../app/user/user-login/user-login.component.css | 24 +++++ .../app/user/user-login/user-login.component.html | 18 ++++ .../user/user-login/user-login.component.spec.ts | 25 +++++ .../app/user/user-login/user-login.component.ts | 32 ++++++ .../src/app/user/user-service/user.service.spec.ts | 12 +++ .../src/app/user/user-service/user.service.ts | 116 +++++++++++++++++++++ 15 files changed, 368 insertions(+) create mode 100644 Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css create mode 100644 Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html create mode 100644 Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts create mode 100644 Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts create mode 100644 Timeline/ClientApp/src/app/user/user-info.ts create mode 100644 Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.css create mode 100644 Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html create mode 100644 Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts create mode 100644 Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts create mode 100644 Timeline/ClientApp/src/app/user/user-login/user-login.component.css create mode 100644 Timeline/ClientApp/src/app/user/user-login/user-login.component.html create mode 100644 Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts create mode 100644 Timeline/ClientApp/src/app/user/user-login/user-login.component.ts create mode 100644 Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts create mode 100644 Timeline/ClientApp/src/app/user/user-service/user.service.ts (limited to 'Timeline/ClientApp/src/app/user') diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css new file mode 100644 index 00000000..a443e3c0 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css @@ -0,0 +1,5 @@ +.container { + display: flex; + justify-content: center; + align-content: center; +} diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html new file mode 100644 index 00000000..50d6ba56 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts new file mode 100644 index 00000000..884a3710 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserDialogComponent } from './user-dialog.component'; + +xdescribe('UserDialogComponent', () => { + let component: UserDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UserDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts new file mode 100644 index 00000000..7511de16 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { UserInfo } from '../user-info'; +import { UserService } from '../user-service/user.service'; +import { LoginEvent, LoginMessage } from '../user-login/user-login.component'; + +@Component({ + selector: 'app-user-dialog', + templateUrl: './user-dialog.component.html', + styleUrls: ['./user-dialog.component.css'] +}) +export class UserDialogComponent implements OnInit { + + constructor(private userService: UserService) { } + + state: 'loading' | 'login' | 'success' = 'loading'; + + loginMessage: LoginMessage; + + displayLoginSuccessMessage = false; + userInfo: UserInfo; + + ngOnInit() { + this.userService.validateUserLoginState().subscribe(result => { + if (result.state === 'success') { + this.userInfo = result.userInfo; + this.state = 'success'; + } else { + this.loginMessage = result.state; + this.state = 'login'; + } + }); + } + + login(event: LoginEvent) { + this.userService.tryLogin(event.username, event.password).subscribe(result => { + this.userInfo = result; + this.displayLoginSuccessMessage = true; + this.state = 'success'; + }, (error: Error) => { + this.loginMessage = error.message; + }); + } +} diff --git a/Timeline/ClientApp/src/app/user/user-info.ts b/Timeline/ClientApp/src/app/user/user-info.ts new file mode 100644 index 00000000..490b00ba --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-info.ts @@ -0,0 +1,4 @@ +export interface UserInfo { + username: string; + roles: string[]; +} diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.css b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.css new file mode 100644 index 00000000..6486142b --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.css @@ -0,0 +1,7 @@ +.login-success-message { + color: green; +} + +.username { + color: blue; +} diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html new file mode 100644 index 00000000..943c137f --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html @@ -0,0 +1,5 @@ + +

You have been login as {{ userInfo.username }}.

+

Your roles are {{ userInfo.roles.join(', ') }}.

diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts new file mode 100644 index 00000000..bdcd354b --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserLoginSuccessComponent } from './user-login-success.component'; + +describe('UserLoginSuccessComponent', () => { + let component: UserLoginSuccessComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UserLoginSuccessComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserLoginSuccessComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts new file mode 100644 index 00000000..99de5970 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { UserInfo } from '../user-info'; + +@Component({ + selector: 'app-user-login-success', + templateUrl: './user-login-success.component.html', + styleUrls: ['./user-login-success.component.css'] +}) +export class UserLoginSuccessComponent implements OnInit { + + @Input() + displayLoginSuccessMessage = false; + + @Input() + userInfo: UserInfo; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.css b/Timeline/ClientApp/src/app/user/user-login/user-login.component.css new file mode 100644 index 00000000..8bf6b408 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.css @@ -0,0 +1,24 @@ +form { + display: flex; + flex-wrap: wrap; +} + +div.w-100 { + width: 100%; +} + +.login-button { + margin-left: auto; +} + +.no-login-message { + color: blue; +} + +.invalid-login-message { + color: red; +} + +.error-message { + color: red; +} diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.html b/Timeline/ClientApp/src/app/user/user-login/user-login.component.html new file mode 100644 index 00000000..b1dd289d --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.html @@ -0,0 +1,18 @@ +
+ + + +

{{ message }}

+
+ + Username + + +
+ + Password + + +
+ +
diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts new file mode 100644 index 00000000..b606b7b4 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserLoginComponent } from './user-login.component'; + +describe('UserLoginComponent', () => { + let component: UserLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UserLoginComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts new file mode 100644 index 00000000..da642cb8 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts @@ -0,0 +1,32 @@ +import { Component, Output, OnInit, EventEmitter, Input } from '@angular/core'; +import { FormGroup, FormControl } from '@angular/forms'; + +export type LoginMessage = 'nologin' | 'invalidlogin' | string; + +export class LoginEvent { + username: string; + password: string; +} + +@Component({ + selector: 'app-user-login', + templateUrl: './user-login.component.html', + styleUrls: ['./user-login.component.css'] +}) +export class UserLoginComponent { + + @Input() + message: LoginMessage; + + @Output() + login = new EventEmitter(); + + form = new FormGroup({ + username: new FormControl(''), + password: new FormControl('') + }); + + onLoginButtonClick() { + this.login.emit(this.form.value); + } +} diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts new file mode 100644 index 00000000..b9221b90 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { UserService } from './user.service'; + +xdescribe('UserService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: UserService = TestBed.get(UserService); + expect(service).toBeTruthy(); + }); +}); diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.ts b/Timeline/ClientApp/src/app/user/user-service/user.service.ts new file mode 100644 index 00000000..009e5292 --- /dev/null +++ b/Timeline/ClientApp/src/app/user/user-service/user.service.ts @@ -0,0 +1,116 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Observable, of, throwError } from 'rxjs'; +import { map, catchError, retry } from 'rxjs/operators'; + +import { UserInfo } from '../user-info'; + +export interface UserCredentials { + username: string; + password: string; +} + +export interface CreateTokenResult { + token: string; + userInfo: UserInfo; +} + +export interface TokenValidationRequest { + token: string; +} + +export interface TokenValidationResult { + isValid: boolean; + userInfo?: UserInfo; +} + +export interface UserLoginState { + state: 'nologin' | 'invalidlogin' | 'success'; + userInfo?: UserInfo; +} + +export class BadNetworkException extends Error { + constructor() { + super('Network is bad.'); + } +} + +export class AlreadyLoginException extends Error { + constructor() { + super('There is already a token saved. Please call validateUserLoginState first.'); + } +} + +export class BadCredentialsException extends Error { + constructor() { + super(`Username or password is wrong.`); + } +} + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + + private token: string; + private userInfo: UserInfo; + + constructor(private httpClient: HttpClient) { } + + validateUserLoginState(): Observable { + if (this.token === undefined || this.token === null) { + return of({ state: 'nologin' }); + } + + return this.httpClient.post('/api/User/ValidateToken', { token: this.token }).pipe( + retry(3), + catchError(error => { + console.error('Failed to validate token.'); + return throwError(error); + }), + map(result => { + if (result.isValid) { + this.userInfo = result.userInfo; + return { + state: 'success', + userInfo: result.userInfo + }; + } else { + this.token = null; + this.userInfo = null; + return { + state: 'invalidlogin' + }; + } + }) + ); + } + + tryLogin(username: string, password: string): Observable { + if (this.token) { + return throwError(new AlreadyLoginException()); + } + + return this.httpClient.post('/api/User/CreateToken', { + username, password + }).pipe( + catchError((error: HttpErrorResponse) => { + if (error.error instanceof ErrorEvent) { + console.error('An error occurred when login: ' + error.error.message); + return throwError(new BadNetworkException()); + } else if (error.status === 400) { + console.error('An error occurred when login: wrong credentials.'); + return throwError(new BadCredentialsException()); + } else { + console.error('An unknown error occurred when login: ' + error); + return throwError(error); + } + }), + map(result => { + this.token = result.token; + this.userInfo = result.userInfo; + return result.userInfo; + }) + ); + } +} -- cgit v1.2.3 From 83672dcdced6e0d42fa570d69c91417b3ea88b51 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 6 Mar 2019 23:14:45 +0800 Subject: Write all unit tests. --- .../user/user-dialog/user-dialog.component.spec.ts | 101 +++++++++++++++++-- .../user-login-success.component.html | 2 +- .../user-login-success.component.spec.ts | 18 +++- .../user/user-login/user-login.component.spec.ts | 43 +++++++- .../src/app/user/user-service/user.service.spec.ts | 108 ++++++++++++++++++++- 5 files changed, 256 insertions(+), 16 deletions(-) (limited to 'Timeline/ClientApp/src/app/user') diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts index 884a3710..d24c0cd2 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts @@ -1,25 +1,114 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, Output, EventEmitter } from '@angular/core'; +import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; +import { delay } from 'rxjs/operators'; +import { UserInfo } from '../user-info'; import { UserDialogComponent } from './user-dialog.component'; +import { UserService, UserLoginState } from '../user-service/user.service'; +import { LoginEvent } from '../user-login/user-login.component'; -xdescribe('UserDialogComponent', () => { +@Component({ + /* tslint:disable-next-line:component-selector*/ + selector: 'mat-progress-spinner', + template: '' +}) +class MatProgressSpinnerStubComponent { } + +@Component({ + selector: 'app-user-login', + /* tslint:disable-next-line:use-input-property-decorator*/ + inputs: ['message'], + template: '' +}) +class UserLoginStubComponent { + @Output() + login = new EventEmitter(); +} + +@Component({ + selector: 'app-user-login-success', + /* tslint:disable-next-line:use-input-property-decorator*/ + inputs: ['userInfo', 'displayLoginSuccessMessage'], + template: '' +}) +class UserLoginSuccessStubComponent { } + +describe('UserDialogComponent', () => { let component: UserDialogComponent; let fixture: ComponentFixture; + let mockUserService: jasmine.SpyObj; beforeEach(async(() => { + mockUserService = jasmine.createSpyObj('UserService', ['validateUserLoginState', 'tryLogin']); + TestBed.configureTestingModule({ - declarations: [ UserDialogComponent ] + declarations: [UserDialogComponent, MatProgressSpinnerStubComponent, + UserLoginStubComponent, UserLoginSuccessStubComponent], + providers: [{ provide: UserService, useValue: mockUserService }] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(UserDialogComponent); component = fixture.componentInstance; + }); + + it('progress spinner should work well', fakeAsync(() => { + mockUserService.validateUserLoginState.and.returnValue(of({ state: 'nologin' }).pipe(delay(10))); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('mat-progress-spinner'))).toBeTruthy(); + tick(10); fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('mat-progress-spinner'))).toBeFalsy(); + })); + + it('nologin should work well', () => { + mockUserService.validateUserLoginState.and.returnValue(of({ state: 'nologin' })); + + fixture.detectChanges(); + + expect(mockUserService.validateUserLoginState).toHaveBeenCalled(); + expect(fixture.debugElement.query(By.css('app-user-login'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeFalsy(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('success should work well', () => { + mockUserService.validateUserLoginState.and.returnValue(of({ state: 'success', userInfo: {} })); + + fixture.detectChanges(); + + expect(mockUserService.validateUserLoginState).toHaveBeenCalled(); + expect(fixture.debugElement.query(By.css('app-user-login'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeTruthy(); + }); + + it('login should work well', () => { + mockUserService.validateUserLoginState.and.returnValue(of({ state: 'nologin' })); + + fixture.detectChanges(); + expect(mockUserService.validateUserLoginState).toHaveBeenCalled(); + expect(fixture.debugElement.query(By.css('app-user-login'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeFalsy(); + + mockUserService.tryLogin.withArgs('user', 'user').and.returnValue(of({ + username: 'user', + roles: ['user'] + })); + + (fixture.debugElement.query(By.css('app-user-login')).componentInstance as + UserLoginStubComponent).login.emit({ + username: 'user', + password: 'user' + }); + + fixture.detectChanges(); + + expect(mockUserService.tryLogin).toHaveBeenCalledWith('user', 'user'); + + expect(fixture.debugElement.query(By.css('app-user-login'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeTruthy(); }); }); diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html index 943c137f..e156f0f8 100644 --- a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.html @@ -2,4 +2,4 @@ Login succeeds!

You have been login as {{ userInfo.username }}.

-

Your roles are {{ userInfo.roles.join(', ') }}.

+

Your roles are {{ userInfo.roles.join(', ') }}.

diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts index bdcd354b..ba015ae6 100644 --- a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts @@ -1,25 +1,39 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { UserLoginSuccessComponent } from './user-login-success.component'; +import { By } from '@angular/platform-browser'; describe('UserLoginSuccessComponent', () => { let component: UserLoginSuccessComponent; let fixture: ComponentFixture; + const mockUserInfo = { + username: 'crupest', + roles: ['superman', 'coder'] + }; + beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ UserLoginSuccessComponent ] + declarations: [UserLoginSuccessComponent] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(UserLoginSuccessComponent); component = fixture.componentInstance; + component.userInfo = mockUserInfo; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should work well', () => { + expect((fixture.debugElement.query(By.css('span.username')).nativeElement as HTMLSpanElement).textContent) + .toBe(mockUserInfo.username); + expect((fixture.debugElement.query(By.css('span.roles')).nativeElement as HTMLSpanElement).textContent) + .toBe(mockUserInfo.roles.join(', ')); + }); }); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts index b606b7b4..acd13721 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts @@ -1,6 +1,9 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; -import { UserLoginComponent } from './user-login.component'; +import { UserLoginComponent, LoginEvent } from './user-login.component'; describe('UserLoginComponent', () => { let component: UserLoginComponent; @@ -8,9 +11,11 @@ describe('UserLoginComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ UserLoginComponent ] + declarations: [UserLoginComponent], + imports: [ReactiveFormsModule], + schemas: [NO_ERRORS_SCHEMA] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { @@ -22,4 +27,34 @@ describe('UserLoginComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('reactive form should work well', () => { + const usernameInput = fixture.debugElement.query(By.css('input[type=text]')).nativeElement as HTMLInputElement; + const passwordInput = fixture.debugElement.query(By.css('input[type=password]')).nativeElement as HTMLInputElement; + + usernameInput.value = 'user'; + usernameInput.dispatchEvent(new Event('input')); + passwordInput.value = 'user'; + passwordInput.dispatchEvent(new Event('input')); + + fixture.detectChanges(); + + expect(component.form.value).toEqual({ + username: 'user', + password: 'user' + }); + }); + + it('login event should work well', fakeAsync(() => { + let userCredential: LoginEvent; + component.login.subscribe((e: LoginEvent) => { userCredential = e; }); + fixture.detectChanges(); + const mockValue = { + username: 'user', + password: 'user' + }; + component.form.setValue(mockValue); + component.onLoginButtonClick(); + expect(userCredential).toEqual(mockValue); + })); }); diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts index b9221b90..28cfefd7 100644 --- a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts @@ -1,12 +1,114 @@ import { TestBed } from '@angular/core/testing'; +import { HttpRequest } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { UserService } from './user.service'; +import { UserInfo } from '../user-info'; +import { + UserService, UserCredentials, CreateTokenResult, + UserLoginState, TokenValidationRequest, TokenValidationResult +} from './user.service'; -xdescribe('UserService', () => { - beforeEach(() => TestBed.configureTestingModule({})); +describe('UserService', () => { + const tokenCreateUrl = '/api/User/CreateToken'; + + beforeEach(() => TestBed.configureTestingModule({ + imports: [HttpClientTestingModule] + })); it('should be created', () => { const service: UserService = TestBed.get(UserService); expect(service).toBeTruthy(); }); + + it('should be nologin at first', () => { + const service: UserService = TestBed.get(UserService); + service.validateUserLoginState().subscribe(result => { + expect(result.state).toBe('nologin'); + }); + }); + + it('login should work well', () => { + const service: UserService = TestBed.get(UserService); + + const mockUserInfo: UserInfo = { + username: 'user', + roles: ['user', 'other'] + }; + + service.tryLogin('user', 'user').subscribe(result => { + expect(result).toEqual(mockUserInfo); + }); + + const httpController = TestBed.get(HttpTestingController) as HttpTestingController; + + httpController.expectOne((request: HttpRequest) => + request.url === tokenCreateUrl && + request.body.username === 'user' && + request.body.password === 'user').flush({ + token: 'test-token', + userInfo: mockUserInfo + }); + + httpController.verify(); + }); + + describe('validateUserLoginState', () => { + let service: UserService; + let httpController: HttpTestingController; + + const mockUserInfo: UserInfo = { + username: 'user', + roles: ['user', 'other'] + }; + + const mockToken = 'mock-token'; + + const tokenValidateRequestMatcher = (req: HttpRequest) => { + return req.url === '/api/User/ValidateToken' && req.body.token === mockToken; + } + + beforeEach(() => { + service = TestBed.get(UserService); + httpController = TestBed.get(HttpTestingController); + + service.tryLogin('user', 'user').subscribe(); // subscribe to activate login + + httpController.expectOne(tokenCreateUrl).flush({ + token: mockToken, + userInfo: mockUserInfo + }); + }); + + it('success should work well', () => { + service.validateUserLoginState().subscribe((result: UserLoginState) => { + expect(result).toEqual({ + state: 'success', + userInfo: mockUserInfo + }); + }); + + httpController.expectOne(tokenValidateRequestMatcher).flush({ + isValid: true, + userInfo: mockUserInfo + }); + + httpController.verify(); + }); + + it('invalid should work well', () => { + service.validateUserLoginState().subscribe((result: UserLoginState) => { + expect(result).toEqual({ + state: 'invalidlogin' + }); + }); + + httpController.expectOne(tokenValidateRequestMatcher).flush({ + isValid: false + }); + + httpController.verify(); + }); + }); + + // TODO: test on error situations. }); -- cgit v1.2.3 From 7107d431fe7019ccc20e90e5aecb5feb64fc53b3 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 6 Mar 2019 23:20:49 +0800 Subject: Fix a lint error. --- Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Timeline/ClientApp/src/app/user') diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts index 28cfefd7..0095f031 100644 --- a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts @@ -65,7 +65,7 @@ describe('UserService', () => { const tokenValidateRequestMatcher = (req: HttpRequest) => { return req.url === '/api/User/ValidateToken' && req.body.token === mockToken; - } + }; beforeEach(() => { service = TestBed.get(UserService); -- cgit v1.2.3