diff options
author | crupest <crupest@outlook.com> | 2019-04-13 13:03:18 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-04-13 13:03:18 +0800 |
commit | 72890735ced2edc8ccecfed811393e951de5c091 (patch) | |
tree | 39cf181a18a3dc443dbab5669a04d0a23cdefd00 /Timeline/ClientApp/src/app/user/internal-user-service | |
parent | 19cae15eba2bcede41b818e1b8ab7fd5ac92eb05 (diff) | |
download | timeline-72890735ced2edc8ccecfed811393e951de5c091.tar.gz timeline-72890735ced2edc8ccecfed811393e951de5c091.tar.bz2 timeline-72890735ced2edc8ccecfed811393e951de5c091.zip |
Init separate.
Diffstat (limited to 'Timeline/ClientApp/src/app/user/internal-user-service')
5 files changed, 0 insertions, 333 deletions
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts deleted file mode 100644 index 3358a9d9..00000000 --- a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -export class BadNetworkError extends Error { - constructor() { - super('Network is bad.'); - } -} - -export class AlreadyLoginError extends Error { - constructor() { - super('Internal logical error. There is already a token saved. Please call validateUserLoginState first.'); - } -} - -export class BadCredentialsError extends Error { - constructor() { - super('Username or password is wrong.'); - } -} - -export class UnknownError extends Error { - constructor(public internalError?: any) { - super('Sorry, unknown error occured!'); - } -} - -export class ServerInternalError extends Error { - constructor(message?: string) { - super('Wrong server response. ' + message); - } -} diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts deleted file mode 100644 index f52233c9..00000000 --- a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UserCredentials, UserInfo } from '../entities'; - -export const createTokenUrl = '/api/User/CreateToken'; -export const validateTokenUrl = '/api/User/ValidateToken'; - -export type CreateTokenRequest = UserCredentials; - -export interface CreateTokenResponse { - success: boolean; - token?: string; - userInfo?: UserInfo; -} - -export interface ValidateTokenRequest { - token: string; -} - -export interface ValidateTokenResponse { - isValid: boolean; - userInfo?: UserInfo; -} diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts deleted file mode 100644 index f4a85262..00000000 --- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { InternalUserService } from './internal-user.service'; - -export function createMockInternalUserService(): jasmine.SpyObj<InternalUserService> { - return jasmine.createSpyObj('InternalUserService', ['userRouteNavigate', 'refreshAndGetUserState', 'tryLogin']); -} diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts deleted file mode 100644 index 15755382..00000000 --- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { HttpRequest } from '@angular/common/http'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { Router } from '@angular/router'; -import { MatSnackBar } from '@angular/material'; - -import { Mock } from 'src/app/test-utilities/mock'; -import { createMockStorage } from 'src/app/test-utilities/storage.mock'; -import { WINDOW } from '../window-inject-token'; - -import { UserInfo, UserCredentials } from '../entities'; -import { - createTokenUrl, validateTokenUrl, CreateTokenRequest, - CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse -} from './http-entities'; -import { InternalUserService, SnackBarTextKey, snackBarText, TOKEN_STORAGE_KEY } from './internal-user.service'; -import { repeat } from 'src/app/utilities/language-untilities'; - - -describe('InternalUserService', () => { - let mockLocalStorage: Mock<Storage>; - let mockSnackBar: jasmine.SpyObj<MatSnackBar>; - - beforeEach(() => { - mockLocalStorage = createMockStorage(); - mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['open']); - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: WINDOW, useValue: { localStorage: mockLocalStorage } }, - { provide: Router, useValue: null }, - { provide: MatSnackBar, useValue: mockSnackBar } - ] - }); - }); - - it('should be created', () => { - const service: InternalUserService = TestBed.get(InternalUserService); - expect(service).toBeTruthy(); - }); - - const mockUserInfo: UserInfo = { - username: 'user', - roles: ['user', 'other'] - }; - - const mockToken = 'mock-token'; - - describe('validate token', () => { - const validateTokenRequestMatcher = (req: HttpRequest<ValidateTokenRequest>): boolean => - req.url === validateTokenUrl && req.body !== null && req.body.token === mockToken; - - function createTest( - expectSnackBarTextKey: SnackBarTextKey, - setStorageToken: boolean, - setHttpController?: (controller: HttpTestingController) => void - ): () => void { - return fakeAsync(() => { - if (setStorageToken) { - mockLocalStorage.setItem(TOKEN_STORAGE_KEY, mockToken); - } - TestBed.get(InternalUserService); - const controller = TestBed.get(HttpTestingController) as HttpTestingController; - if (setHttpController) { - setHttpController(controller); - } - controller.verify(); - tick(); - expect(mockSnackBar.open).toHaveBeenCalledWith(snackBarText[expectSnackBarTextKey], jasmine.anything(), jasmine.anything()); - }); - } - - it('no login should work well', createTest('noLogin', false)); - it('already login should work well', createTest('alreadyLogin', true, - controller => controller.expectOne(validateTokenRequestMatcher).flush( - <ValidateTokenResponse>{ isValid: true, userInfo: mockUserInfo }))); - it('invalid login should work well', createTest('invalidLogin', true, - controller => controller.expectOne(validateTokenRequestMatcher).flush(<ValidateTokenResponse>{ isValid: false }))); - it('check fail should work well', createTest('checkFail', true, - controller => repeat(4, () => { - controller.expectOne(validateTokenRequestMatcher).error(new ErrorEvent('Network error', { message: 'simulated network error' })); - }))); - }); - - describe('login should work well', () => { - const mockUserCredentials: UserCredentials = { - username: 'user', - password: 'user' - }; - - function createTest(rememberMe: boolean) { - return () => { - const service: InternalUserService = TestBed.get(InternalUserService); - - service.tryLogin({ ...mockUserCredentials, rememberMe: rememberMe }).subscribe(result => { - expect(result).toEqual(mockUserInfo); - }); - - const httpController = TestBed.get(HttpTestingController) as HttpTestingController; - - httpController.expectOne((request: HttpRequest<CreateTokenRequest>) => - request.url === createTokenUrl && request.body !== null && - request.body.username === mockUserCredentials.username && - request.body.password === mockUserCredentials.password).flush(<CreateTokenResponse>{ - success: true, - token: mockToken, - userInfo: mockUserInfo - }); - - expect(service.currentUserInfo).toEqual(mockUserInfo); - - httpController.verify(); - - expect(mockLocalStorage.getItem(TOKEN_STORAGE_KEY)).toBe(rememberMe ? mockToken : null); - }; - } - - it('remember me should work well', createTest(true)); - it('not remember me should work well', createTest(false)); - }); - - // TODO: test on error situations. -}); diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts deleted file mode 100644 index 66eafde9..00000000 --- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Injectable, Inject } from '@angular/core'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { Router } from '@angular/router'; - -import { Observable, throwError, BehaviorSubject, of } from 'rxjs'; -import { map, catchError, retry, switchMap, tap, filter } from 'rxjs/operators'; - -import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError, ServerInternalError } from './errors'; -import { - createTokenUrl, validateTokenUrl, CreateTokenRequest, - CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse -} from './http-entities'; -import { UserCredentials, UserInfo } from '../entities'; -import { MatSnackBar } from '@angular/material'; -import { WINDOW } from '../window-inject-token'; - -export const snackBarText = { - checkFail: 'Failed to check last login', - noLogin: 'No login before!', - alreadyLogin: 'You have login already!', - invalidLogin: 'Last login is no longer invalid!', - ok: 'ok' -}; - -export type SnackBarTextKey = Exclude<keyof typeof snackBarText, 'ok'>; - -export const TOKEN_STORAGE_KEY = 'token'; - -export interface LoginInfo extends UserCredentials { - rememberMe: boolean; -} - -/** - * This service is only used internal in user module. - */ -@Injectable({ - providedIn: 'root' -}) -export class InternalUserService { - - private token: string | null = null; - private userInfoSubject = new BehaviorSubject<UserInfo | null | undefined>(undefined); - - readonly userInfo$: Observable<UserInfo | null> = - <Observable<UserInfo | null>>this.userInfoSubject.pipe(filter(value => value !== undefined)); - - get currentUserInfo(): UserInfo | null | undefined { - return this.userInfoSubject.value; - } - - private openSnackBar(snackBar: MatSnackBar, textKey: SnackBarTextKey) { - setTimeout(() => snackBar.open(snackBarText[textKey], snackBarText.ok, { duration: 2000 }), 0); - } - - constructor(@Inject(WINDOW) private window: Window, private httpClient: HttpClient, private router: Router, snackBar: MatSnackBar) { - const savedToken = this.window.localStorage.getItem(TOKEN_STORAGE_KEY); - if (savedToken === null) { - this.openSnackBar(snackBar, 'noLogin'); - this.userInfoSubject.next(null); - } else { - this.validateToken(savedToken).subscribe(result => { - if (result === null) { - this.window.localStorage.removeItem(TOKEN_STORAGE_KEY); - this.openSnackBar(snackBar, 'invalidLogin'); - this.userInfoSubject.next(null); - } else { - this.token = savedToken; - this.userInfoSubject.next(result); - this.openSnackBar(snackBar, 'alreadyLogin'); - } - }, _ => { - this.openSnackBar(snackBar, 'checkFail'); - this.userInfoSubject.next(null); - }); - } - } - - private validateToken(token: string): Observable<UserInfo | null> { - return this.httpClient.post<ValidateTokenResponse>(validateTokenUrl, <ValidateTokenRequest>{ token: token }).pipe( - retry(3), - switchMap(result => { - if (result.isValid) { - const { userInfo } = result; - if (userInfo) { - return of(userInfo); - } else { - return throwError(new ServerInternalError('IsValid is true but UserInfo is null.')); - } - } else { - return of(null); - } - }), - tap({ - error: error => { - console.error('Failed to validate token.'); - console.error(error); - } - }), - ); - } - - userRouteNavigate(commands: any[] | null) { - this.router.navigate([{ - outlets: { - user: commands - } - }]); - } - - tryLogin(info: LoginInfo): Observable<UserInfo> { - if (this.token) { - return throwError(new AlreadyLoginError()); - } - - return this.httpClient.post<CreateTokenResponse>(createTokenUrl, <CreateTokenRequest>info).pipe( - catchError((error: HttpErrorResponse) => { - if (error.error instanceof ErrorEvent) { - console.error('An error occurred when login: ' + error.error.message); - return throwError(new BadNetworkError()); - } else { - console.error('An unknown error occurred when login: ' + error); - return throwError(new UnknownError(error)); - } - }), - switchMap(result => { - if (result.success) { - if (result.token && result.userInfo) { - this.token = result.token; - if (info.rememberMe) { - this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token); - } - this.userInfoSubject.next(result.userInfo); - return of(result.userInfo); - } else { - console.error('An error occurred when login: server return wrong data.'); - return throwError(new ServerInternalError('Token or userInfo is null.')); - } - } else { - console.error('An error occurred when login: wrong credentials.'); - return throwError(new BadCredentialsError()); - } - }) - ); - } - - logout() { - if (this.currentUserInfo === null) { - throw new Error('No login now. You can\'t logout.'); - } - - this.window.localStorage.removeItem(TOKEN_STORAGE_KEY); - this.token = null; - this.userInfoSubject.next(null); - } -} |