diff --git a/src/app/auth/auth.action.spec.ts b/src/app/auth/auth.action.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..76209f4e5a15a19b69c1510cad6e9c322b49894d --- /dev/null +++ b/src/app/auth/auth.action.spec.ts @@ -0,0 +1,37 @@ +import * as authActions from './auth.action'; +import { UserProfile } from './user-profile.model'; + +describe('[Auth] Action', () => { + it('should create LoginAction', () => { + const action = new authActions.LoginAction(); + expect(action.type).toEqual(authActions.LOGIN); + }); + + it('should create LogoutAction', () => { + const action = new authActions.LogoutAction(); + expect(action.type).toEqual(authActions.LOGOUT); + }); + + it('should create AuthSuccessAction', () => { + const action = new authActions.AuthSuccessAction(); + expect(action.type).toEqual(authActions.AUTH_SUCCESS); + }); + + it('should create LoadUserProfileSuccessAction', () => { + const profile: UserProfile = { id: 'id', username: 'toto' }; + const action = new authActions.LoadUserProfileSuccessAction(profile); + expect(action.type).toEqual(authActions.LOAD_USER_PROFILE_SUCCESS); + expect(action.payload).toEqual(profile); + }); + + it('should create LoadUserRolesSuccessAction', () => { + const action = new authActions.LoadUserRolesSuccessAction(['toto']); + expect(action.type).toEqual(authActions.LOAD_USER_ROLES_SUCCESS); + expect(action.payload).toEqual(['toto']); + }); + + it('should create OpenEditProfileAction', () => { + const action = new authActions.OpenEditProfileAction(); + expect(action.type).toEqual(authActions.OPEN_EDIT_PROFILE); + }); +}); diff --git a/src/app/auth/auth.action.ts b/src/app/auth/auth.action.ts index 481b97a2ffcf383dbddec3ac8f6b70c8d762629f..51a8df8bdfac6027bb012d8ed13bbf91970795b8 100644 --- a/src/app/auth/auth.action.ts +++ b/src/app/auth/auth.action.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Action } from '@ngrx/store'; import { UserProfile } from './user-profile.model'; @@ -7,38 +16,68 @@ export const LOGOUT = '[Auth] Logout'; export const AUTH_SUCCESS = '[Auth] Auth Success'; export const LOAD_USER_PROFILE_SUCCESS = '[Auth] Load User Profile Success'; export const LOAD_USER_ROLES_SUCCESS = '[Auth] Load User Roles Success'; -export const OPEN_EDIT_PROFILE = '[Auth] Edit Profile'; +export const OPEN_EDIT_PROFILE = '[Auth] Open Edit Profile'; +/** + * @class + * @classdesc LoginAction action. + * @readonly + */ export class LoginAction implements Action { readonly type = LOGIN; constructor(public payload: {} = null) { } } +/** + * @class + * @classdesc LogoutAction action. + * @readonly + */ export class LogoutAction implements Action { readonly type = LOGOUT; constructor(public payload: {} = null) { } } +/** + * @class + * @classdesc AuthSuccessAction action. + * @readonly + */ export class AuthSuccessAction implements Action { readonly type = AUTH_SUCCESS; constructor(public payload: {} = null) { } } +/** + * @class + * @classdesc LoadUserProfileSuccessAction action. + * @readonly + */ export class LoadUserProfileSuccessAction implements Action { readonly type = LOAD_USER_PROFILE_SUCCESS; constructor(public payload: UserProfile) { } } +/** + * @class + * @classdesc LoadUserRolesSuccessAction action. + * @readonly + */ export class LoadUserRolesSuccessAction implements Action { readonly type = LOAD_USER_ROLES_SUCCESS; constructor(public payload: string[]) { } } +/** + * @class + * @classdesc OpenEditProfileAction action. + * @readonly + */ export class OpenEditProfileAction implements Action { readonly type = OPEN_EDIT_PROFILE; diff --git a/src/app/auth/auth.effects.ts b/src/app/auth/auth.effects.ts index 7f492302140914a38199cc0cb3a47375e3560357..b309dbc1e2b1bb213c11377ba1c9f5cbecb44f73 100644 --- a/src/app/auth/auth.effects.ts +++ b/src/app/auth/auth.effects.ts @@ -1,32 +1,55 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { Injectable } from '@angular/core'; -import { from } from 'rxjs'; -import { switchMap, tap } from 'rxjs/operators'; import { Effect, Actions, ofType } from '@ngrx/effects'; +import { from } from 'rxjs'; +import { switchMap, tap } from 'rxjs/operators'; import { KeycloakService } from 'keycloak-angular'; import * as authActions from './auth.action'; import { environment } from '../../environments/environment'; @Injectable() +/** + * @class + * @classdesc Authentication effects. + */ + export class AuthEffects { constructor( private actions$: Actions, private keycloak: KeycloakService ) { } + /** + * Executes log in. + */ @Effect({ dispatch: false }) loginAction$ = this.actions$.pipe( ofType(authActions.LOGIN), tap(_ => this.keycloak.login()) ); + /** + * Executes log out. + */ @Effect({ dispatch: false }) logoutAction$ = this.actions$.pipe( ofType(authActions.LOGOUT), tap(_ => this.keycloak.logout()) ); + /** + * Saves user profile and gets user roles. + */ @Effect() authSuccessAction$ = this.actions$.pipe( ofType(authActions.AUTH_SUCCESS), @@ -40,6 +63,9 @@ export class AuthEffects { ) ); + /** + * Opens edit profile page. + */ @Effect({ dispatch: false }) OpenEditProfileAction$ = this.actions$.pipe( ofType(authActions.OPEN_EDIT_PROFILE), diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index 8d7d58afc16229fe4ca6add1af004262bf99dd60..4f69a3e398dc94c1a8b46c54c3f4146caa123676 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -1,12 +1,21 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { NgModule } from '@angular/core'; -import { KeycloakAngularModule } from 'keycloak-angular'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; +import { KeycloakAngularModule } from 'keycloak-angular'; -import { initializeKeycloakAnis } from './init.keycloak'; import { reducer } from './auth.reducer'; import { AuthEffects } from './auth.effects'; +import { initializeKeycloakAnis } from './init.keycloak'; @NgModule({ imports: [ @@ -18,4 +27,8 @@ import { AuthEffects } from './auth.effects'; initializeKeycloakAnis ] }) +/** + * @class + * @classdesc Authentication module. + */ export class AuthModule { } diff --git a/src/app/auth/auth.reducer.spec.ts b/src/app/auth/auth.reducer.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e04ed67dee4b06ff414149506e609b190e0b643f --- /dev/null +++ b/src/app/auth/auth.reducer.spec.ts @@ -0,0 +1,69 @@ +import * as fromAuth from './auth.reducer'; +import * as authActions from './auth.action'; +import { UserProfile } from './user-profile.model'; + +describe('[Auth] Reducer', () => { + it('should return init state', () => { + const { initialState } = fromAuth; + const action = {} as authActions.Actions; + const state = fromAuth.reducer(undefined, action); + + expect(state).toBe(initialState); + }); + + it('should set isAuthenticated to true', () => { + const { initialState } = fromAuth; + const action = new authActions.AuthSuccessAction(); + const state = fromAuth.reducer(initialState, action); + + expect(state.isAuthenticated).toBeTruthy(); + expect(state.userProfile).toBeNull(); + expect(state.userRoles.length).toEqual(0); + expect(state).not.toEqual(initialState); + }); + + it('should set userProfile', () => { + const profile: UserProfile = { id: 'id', username: 'toto' }; + const { initialState } = fromAuth; + const action = new authActions.LoadUserProfileSuccessAction(profile); + const state = fromAuth.reducer(initialState, action); + + expect(state.isAuthenticated).toBeFalsy(); + expect(state.userProfile).toEqual(profile); + expect(state.userRoles.length).toEqual(0); + expect(state).not.toEqual(initialState); + }); + + it('should set userRoles', () => { + const { initialState } = fromAuth; + const action = new authActions.LoadUserRolesSuccessAction(['toto']); + const state = fromAuth.reducer(initialState, action); + + expect(state.isAuthenticated).toBeFalsy(); + expect(state.userProfile).toBeNull(); + expect(state.userRoles.length).toEqual(1); + expect(state.userRoles[0]).toEqual('toto'); + expect(state).not.toEqual(initialState); + }); + + it('should get isAuthenticated', () => { + const action = {} as authActions.Actions; + const state = fromAuth.reducer(undefined, action); + + expect(fromAuth.isAuthenticated(state)).toBeFalsy(); + }); + + it('should get userProfile', () => { + const action = {} as authActions.Actions; + const state = fromAuth.reducer(undefined, action); + + expect(fromAuth.getUserProfile(state)).toBeNull(); + }); + + it('should get userRoles', () => { + const action = {} as authActions.Actions; + const state = fromAuth.reducer(undefined, action); + + expect(fromAuth.getUserRoles(state).length).toEqual(0); + }); +}); diff --git a/src/app/auth/auth.reducer.ts b/src/app/auth/auth.reducer.ts index 576ca0100cf210e755ca825a5195a957a58617c1..2ecad6844a5e9659d225fe22b2cadf63eb387cdb 100644 --- a/src/app/auth/auth.reducer.ts +++ b/src/app/auth/auth.reducer.ts @@ -1,7 +1,20 @@ -import * as actions from './auth.action'; +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import * as actions from './auth.action'; import { UserProfile } from './user-profile.model'; +/** + * Interface for authentication state. + * + * @interface State + */ export interface State { isAuthenticated: boolean; userProfile: UserProfile; @@ -14,6 +27,14 @@ export const initialState: State = { userRoles: [] }; +/** + * Reduces state. + * + * @param {State} state - The state. + * @param {actions} action - The action. + * + * @return State + */ export function reducer(state: State = initialState, action: actions.Actions): State { switch (action.type) { case actions.AUTH_SUCCESS: @@ -23,19 +44,15 @@ export function reducer(state: State = initialState, action: actions.Actions): S }; case actions.LOAD_USER_PROFILE_SUCCESS: - const userProfile = action.payload; - return { ...state, - userProfile + userProfile: action.payload }; case actions.LOAD_USER_ROLES_SUCCESS: - const userRoles = action.payload; - return { ...state, - userRoles + userRoles: action.payload }; default: diff --git a/src/app/auth/auth.selector.spec.ts b/src/app/auth/auth.selector.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c07e09681e3f4f580890d17530c6076b0d610803 --- /dev/null +++ b/src/app/auth/auth.selector.spec.ts @@ -0,0 +1,19 @@ +import * as authSelector from './auth.selector'; +import * as fromAuth from './auth.reducer'; + +describe('[Auth] Selector', () => { + it('should get isAuthenticated', () => { + const state = { auth: { ...fromAuth.initialState }}; + expect(authSelector.isAuthenticated(state)).toBeFalsy(); + }); + + it('should get userProfile', () => { + const state = { auth: { ...fromAuth.initialState }}; + expect(authSelector.getUserProfile(state)).toBeNull(); + }); + + it('should get userRoles', () => { + const state = { auth: { ...fromAuth.initialState }}; + expect(authSelector.getUserRoles(state).length).toEqual(0); + }); +}); diff --git a/src/app/auth/auth.selector.ts b/src/app/auth/auth.selector.ts index a58dfd67eb2ee9295cbcd18d4df0aa76ac438733..db6ccddec34494926d889d520eecaa85a3918e54 100644 --- a/src/app/auth/auth.selector.ts +++ b/src/app/auth/auth.selector.ts @@ -1,3 +1,12 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as auth from './auth.reducer'; diff --git a/src/app/auth/init.keycloak.ts b/src/app/auth/init.keycloak.ts index 3b9e98db17f3c6381b1aeabece40054d1196fe5f..9b589434cd55f511bd6702a6bb265d436752210a 100644 --- a/src/app/auth/init.keycloak.ts +++ b/src/app/auth/init.keycloak.ts @@ -1,14 +1,32 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import { APP_INITIALIZER } from '@angular/core'; -import { from } from 'rxjs'; -import { KeycloakService, KeycloakEventType } from 'keycloak-angular'; import { Store } from '@ngrx/store'; +import { from } from 'rxjs'; +import { KeycloakService, KeycloakEventType } from 'keycloak-angular'; import * as keycloakActions from './auth.action'; import * as fromKeycloak from './auth.reducer'; import { environment } from '../../environments/environment'; -function initializeKeycloak(keycloak: KeycloakService, store: Store<{ keycloak: fromKeycloak.State }>) { +/** + * Inits Keycloak. + * + * @param {KeycloakService} keycloak - The Keycloak service. + * @param {Store<{ keycloak: fromKeycloak.State }>} store - The Keycloak store. + * + * @return any + */ + +function initializeKeycloak(keycloak: KeycloakService, store: Store<{ keycloak: fromKeycloak.State }>): any { return async () => { from(keycloak.keycloakEvents$).subscribe(event => { if (event.type === KeycloakEventType.OnAuthSuccess) { @@ -36,5 +54,5 @@ export const initializeKeycloakAnis = { provide: APP_INITIALIZER, useFactory: initializeKeycloak, multi: true, - deps: [ KeycloakService, Store ], + deps: [ KeycloakService, Store ] }; diff --git a/src/app/auth/user-profile.model.ts b/src/app/auth/user-profile.model.ts index c2addf7e0613e83e9015c843bef7ad7131352113..1848db380bfe6638251dfe12b6ad090c682e8550 100644 --- a/src/app/auth/user-profile.model.ts +++ b/src/app/auth/user-profile.model.ts @@ -1,3 +1,18 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +/** + * Interface for user profile. + * + * @interface UserProfile + */ export interface UserProfile { id?: string; username?: string;