Skip to content

Commit

Permalink
Merge 18e17f0 into b2f0e9f
Browse files Browse the repository at this point in the history
  • Loading branch information
atarix83 committed Mar 2, 2020
2 parents b2f0e9f + 18e17f0 commit 506c479
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 84 deletions.
1 change: 0 additions & 1 deletion src/app/core/auth/auth-request.service.ts
@@ -1,7 +1,6 @@
import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs';
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import { EPersonDataService } from '../eperson/eperson-data.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RequestService } from '../data/request.service';
import { GLOBAL_CONFIG } from '../../../config';
Expand Down
58 changes: 52 additions & 6 deletions src/app/core/auth/auth.actions.ts
@@ -1,9 +1,7 @@
// import @ngrx
import { Action } from '@ngrx/store';

// import type function
import { type } from '../../shared/ngrx/type';

// import models
import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
Expand Down Expand Up @@ -31,6 +29,9 @@ export const AuthActionTypes = {
REGISTRATION_ERROR: type('dspace/auth/REGISTRATION_ERROR'),
REGISTRATION_SUCCESS: type('dspace/auth/REGISTRATION_SUCCESS'),
SET_REDIRECT_URL: type('dspace/auth/SET_REDIRECT_URL'),
RETRIEVE_AUTHENTICATED_EPERSON: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON'),
RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'),
RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'),
};

/* tslint:disable:max-classes-per-file */
Expand Down Expand Up @@ -76,11 +77,11 @@ export class AuthenticatedSuccessAction implements Action {
payload: {
authenticated: boolean;
authToken: AuthTokenInfo;
user: EPerson
userHref: string
};

constructor(authenticated: boolean, authToken: AuthTokenInfo, user: EPerson) {
this.payload = { authenticated, authToken, user };
constructor(authenticated: boolean, authToken: AuthTokenInfo, userHref: string) {
this.payload = { authenticated, authToken, userHref };
}
}

Expand Down Expand Up @@ -322,6 +323,47 @@ export class SetRedirectUrlAction implements Action {
}
}

/**
* Retrieve the authenticated eperson.
* @class RetrieveAuthenticatedEpersonAction
* @implements {Action}
*/
export class RetrieveAuthenticatedEpersonAction implements Action {
public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON;
payload: string;

constructor(user: string) {
this.payload = user ;
}
}

/**
* Set the authenticated eperson in the state.
* @class RetrieveAuthenticatedEpersonSuccessAction
* @implements {Action}
*/
export class RetrieveAuthenticatedEpersonSuccessAction implements Action {
public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS;
payload: EPerson;

constructor(user: EPerson) {
this.payload = user ;
}
}

/**
* Set the authenticated eperson in the state.
* @class RetrieveAuthenticatedEpersonSuccessAction
* @implements {Action}
*/
export class RetrieveAuthenticatedEpersonErrorAction implements Action {
public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR;
payload: Error;

constructor(payload: Error) {
this.payload = payload ;
}
}
/* tslint:enable:max-classes-per-file */

/**
Expand All @@ -343,4 +385,8 @@ export type AuthActions
| RegistrationErrorAction
| RegistrationSuccessAction
| AddAuthenticationMessageAction
| ResetAuthenticationMessagesAction;
| ResetAuthenticationMessagesAction
| RetrieveAuthenticatedEpersonAction
| RetrieveAuthenticatedEpersonErrorAction
| RetrieveAuthenticatedEpersonSuccessAction
| SetRedirectUrlAction;
104 changes: 78 additions & 26 deletions src/app/core/auth/auth.effects.spec.ts
Expand Up @@ -18,12 +18,14 @@ import {
LogOutErrorAction,
LogOutSuccessAction,
RefreshTokenErrorAction,
RefreshTokenSuccessAction
RefreshTokenSuccessAction,
RetrieveAuthenticatedEpersonAction,
RetrieveAuthenticatedEpersonErrorAction,
RetrieveAuthenticatedEpersonSuccessAction
} from './auth.actions';
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
import { AuthService } from './auth.service';
import { AuthState } from './auth.reducer';

import { EPersonMock } from '../../shared/testing/eperson-mock';

describe('AuthEffects', () => {
Expand All @@ -42,13 +44,14 @@ describe('AuthEffects', () => {
authServiceStub = new AuthServiceStub();
token = authServiceStub.getToken();
}

beforeEach(() => {
init();
TestBed.configureTestingModule({
providers: [
AuthEffects,
{provide: AuthService, useValue: authServiceStub},
{provide: Store, useValue: store},
{ provide: AuthService, useValue: authServiceStub },
{ provide: Store, useValue: store },
provideMockActions(() => actions),
// other providers
],
Expand All @@ -63,11 +66,11 @@ describe('AuthEffects', () => {
actions = hot('--a-', {
a: {
type: AuthActionTypes.AUTHENTICATE,
payload: {email: 'user', password: 'password'}
payload: { email: 'user', password: 'password' }
}
});

const expected = cold('--b-', {b: new AuthenticationSuccessAction(token)});
const expected = cold('--b-', { b: new AuthenticationSuccessAction(token) });

expect(authEffects.authenticate$).toBeObservable(expected);
});
Expand All @@ -80,11 +83,11 @@ describe('AuthEffects', () => {
actions = hot('--a-', {
a: {
type: AuthActionTypes.AUTHENTICATE,
payload: {email: 'user', password: 'wrongpassword'}
payload: { email: 'user', password: 'wrongpassword' }
}
});

const expected = cold('--b-', {b: new AuthenticationErrorAction(new Error('Message Error test'))});
const expected = cold('--b-', { b: new AuthenticationErrorAction(new Error('Message Error test')) });

expect(authEffects.authenticate$).toBeObservable(expected);
});
Expand All @@ -94,9 +97,9 @@ describe('AuthEffects', () => {
describe('authenticateSuccess$', () => {

it('should return a AUTHENTICATED action in response to a AUTHENTICATE_SUCCESS action', () => {
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATE_SUCCESS, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATE_SUCCESS, payload: token } });

const expected = cold('--b-', {b: new AuthenticatedAction(token)});
const expected = cold('--b-', { b: new AuthenticatedAction(token) });

expect(authEffects.authenticateSuccess$).toBeObservable(expected);
});
Expand All @@ -106,9 +109,9 @@ describe('AuthEffects', () => {

describe('when token is valid', () => {
it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => {
actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } });

const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EPersonMock)});
const expected = cold('--b-', { b: new AuthenticatedSuccessAction(true, token, EPersonMock._links.self.href) });

expect(authEffects.authenticated$).toBeObservable(expected);
});
Expand All @@ -118,23 +121,42 @@ describe('AuthEffects', () => {
it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => {
spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test')));

actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } });

const expected = cold('--b-', {b: new AuthenticatedErrorAction(new Error('Message Error test'))});
const expected = cold('--b-', { b: new AuthenticatedErrorAction(new Error('Message Error test')) });

expect(authEffects.authenticated$).toBeObservable(expected);
});
});
});

describe('authenticatedSuccess$', () => {

it('should return a RETRIEVE_AUTHENTICATED_EPERSON action in response to a AUTHENTICATED_SUCCESS action', () => {
actions = hot('--a-', {
a: {
type: AuthActionTypes.AUTHENTICATED_SUCCESS, payload: {
authenticated: true,
authToken: token,
userHref: EPersonMock._links.self.href
}
}
});

const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonAction(EPersonMock._links.self.href) });

expect(authEffects.authenticatedSuccess$).toBeObservable(expected);
});
});

describe('checkToken$', () => {

describe('when check token succeeded', () => {
it('should return a AUTHENTICATED action in response to a CHECK_AUTHENTICATION_TOKEN action', () => {

actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN}});
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN } });

const expected = cold('--b-', {b: new AuthenticatedAction(token)});
const expected = cold('--b-', { b: new AuthenticatedAction(token) });

expect(authEffects.checkToken$).toBeObservable(expected);
});
Expand All @@ -144,23 +166,53 @@ describe('AuthEffects', () => {
it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => {
spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(observableThrow(''));

actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token } });

const expected = cold('--b-', {b: new CheckAuthenticationTokenErrorAction()});
const expected = cold('--b-', { b: new CheckAuthenticationTokenErrorAction() });

expect(authEffects.checkToken$).toBeObservable(expected);
});
})
});

describe('retrieveAuthenticatedEperson$', () => {

describe('when request is successful', () => {
it('should return a RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS action in response to a RETRIEVE_AUTHENTICATED_EPERSON action', () => {
actions = hot('--a-', {
a: {
type: AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON,
payload: EPersonMock._links.self.href
}
});

const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock) });

expect(authEffects.retrieveAuthenticatedEperson$).toBeObservable(expected);
});
});

describe('when request is not successful', () => {
it('should return a RETRIEVE_AUTHENTICATED_EPERSON_ERROR action in response to a RETRIEVE_AUTHENTICATED_EPERSON action', () => {
spyOn((authEffects as any).authService, 'retrieveAuthenticatedUserByHref').and.returnValue(observableThrow(new Error('Message Error test')));

actions = hot('--a-', { a: { type: AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON, payload: token } });

const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonErrorAction(new Error('Message Error test')) });

expect(authEffects.retrieveAuthenticatedEperson$).toBeObservable(expected);
});
});
});

describe('refreshToken$', () => {

describe('when refresh token succeeded', () => {
it('should return a REFRESH_TOKEN_SUCCESS action in response to a REFRESH_TOKEN action', () => {

actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN}});
actions = hot('--a-', { a: { type: AuthActionTypes.REFRESH_TOKEN } });

const expected = cold('--b-', {b: new RefreshTokenSuccessAction(token)});
const expected = cold('--b-', { b: new RefreshTokenSuccessAction(token) });

expect(authEffects.refreshToken$).toBeObservable(expected);
});
Expand All @@ -170,9 +222,9 @@ describe('AuthEffects', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => {
spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(observableThrow(''));

actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.REFRESH_TOKEN, payload: token } });

const expected = cold('--b-', {b: new RefreshTokenErrorAction()});
const expected = cold('--b-', { b: new RefreshTokenErrorAction() });

expect(authEffects.refreshToken$).toBeObservable(expected);
});
Expand All @@ -184,9 +236,9 @@ describe('AuthEffects', () => {
describe('when refresh token succeeded', () => {
it('should return a LOG_OUT_SUCCESS action in response to a LOG_OUT action', () => {

actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT}});
actions = hot('--a-', { a: { type: AuthActionTypes.LOG_OUT } });

const expected = cold('--b-', {b: new LogOutSuccessAction()});
const expected = cold('--b-', { b: new LogOutSuccessAction() });

expect(authEffects.logOut$).toBeObservable(expected);
});
Expand All @@ -196,9 +248,9 @@ describe('AuthEffects', () => {
it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => {
spyOn((authEffects as any).authService, 'logout').and.returnValue(observableThrow(new Error('Message Error test')));

actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT, payload: token}});
actions = hot('--a-', { a: { type: AuthActionTypes.LOG_OUT, payload: token } });

const expected = cold('--b-', {b: new LogOutErrorAction(new Error('Message Error test'))});
const expected = cold('--b-', { b: new LogOutErrorAction(new Error('Message Error test')) });

expect(authEffects.logOut$).toBeObservable(expected);
});
Expand Down
23 changes: 21 additions & 2 deletions src/app/core/auth/auth.effects.ts
Expand Up @@ -26,7 +26,10 @@ import {
RefreshTokenSuccessAction,
RegistrationAction,
RegistrationErrorAction,
RegistrationSuccessAction
RegistrationSuccessAction,
RetrieveAuthenticatedEpersonAction,
RetrieveAuthenticatedEpersonErrorAction,
RetrieveAuthenticatedEpersonSuccessAction
} from './auth.actions';
import { EPerson } from '../eperson/models/eperson.model';
import { AuthStatus } from './models/auth-status.model';
Expand Down Expand Up @@ -66,18 +69,34 @@ export class AuthEffects {
ofType(AuthActionTypes.AUTHENTICATED),
switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser(action.payload).pipe(
map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)),
map((userHref: string) => new AuthenticatedSuccessAction((userHref !== null), action.payload, userHref)),
catchError((error) => observableOf(new AuthenticatedErrorAction(error))),);
})
);

@Effect()
public authenticatedSuccess$: Observable<Action> = this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATED_SUCCESS),
map((action: AuthenticatedSuccessAction) => new RetrieveAuthenticatedEpersonAction(action.payload.userHref))
);

// It means "reacts to this action but don't send another"
@Effect({ dispatch: false })
public authenticatedError$: Observable<Action> = this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATED_ERROR),
tap((action: LogOutSuccessAction) => this.authService.removeToken())
);

@Effect()
public retrieveAuthenticatedEperson$: Observable<Action> = this.actions$.pipe(
ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON),
switchMap((action: RetrieveAuthenticatedEpersonAction) => {
return this.authService.retrieveAuthenticatedUserByHref(action.payload).pipe(
map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user)),
catchError((error) => observableOf(new RetrieveAuthenticatedEpersonErrorAction(error))));
})
);

@Effect()
public checkToken$: Observable<Action> = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN),
switchMap(() => {
Expand Down

0 comments on commit 506c479

Please sign in to comment.