Skip to content

Commit

Permalink
[ACS-8726] Use functional route guards (#4096)
Browse files Browse the repository at this point in the history
* [ACS-8726] Use functional route guards

* [ACS-8726] reduce duplication
nikita-web-ua authored Sep 6, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 49cd06d commit 3a2d870
Showing 7 changed files with 216 additions and 245 deletions.
Original file line number Diff line number Diff line change
@@ -22,22 +22,16 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AuthenticationService } from '@alfresco/adf-core';

@Injectable({
providedIn: 'root'
})
export class ViewProfileRuleGuard implements CanActivate {
constructor(private authService: AuthenticationService) {}
export const ViewProfileRuleGuard: CanActivateFn = () => {
const authService = inject(AuthenticationService);

canActivate(): Observable<boolean> | Promise<boolean> | boolean {
return this.isEcmLoggedIn() || this.authService.isOauth();
}
const isEcmLoggedIn = (): boolean => {
return authService.isEcmLoggedIn() || (authService.isECMProvider() && authService.isKerberosEnabled());
};

private isEcmLoggedIn(): boolean {
return this.authService.isEcmLoggedIn() || (this.authService.isECMProvider() && this.authService.isKerberosEnabled());
}
}
return isEcmLoggedIn() || authService.isOauth();
};
Original file line number Diff line number Diff line change
@@ -22,118 +22,121 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { ExtensionsDataLoaderGuard } from './extensions-data-loader.guard';
import { ActivatedRouteSnapshot } from '@angular/router';
import { Subject, throwError } from 'rxjs';
import { EXTENSION_DATA_LOADERS, ExtensionsDataLoaderGuard, resetInvoked } from './extensions-data-loader.guard';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { TestBed } from '@angular/core/testing';
import { Observable, Subject, throwError } from 'rxjs';

describe('ExtensionsDataLoaderGuard', () => {
let route: ActivatedRouteSnapshot;
let emittedSpy;
let completedSpy;
let erroredSpy;

describe('canActivate', () => {
beforeEach(() => {
route = {} as ActivatedRouteSnapshot;
emittedSpy = jasmine.createSpy('emitted');
completedSpy = jasmine.createSpy('completed');
erroredSpy = jasmine.createSpy('errored');
});

it('should emit true and complete if no callback are present', () => {
const guard = new ExtensionsDataLoaderGuard([]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete in case of only one callback is present, completed', () => {
const subject = new Subject<true>();
const guard = new ExtensionsDataLoaderGuard([() => subject.asObservable()]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);

subject.next(true);
expect(emittedSpy).not.toHaveBeenCalled();
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).not.toHaveBeenCalled();

subject.complete();
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete in case of only one callback is present, errored', () => {
const guard = new ExtensionsDataLoaderGuard([() => throwError(new Error())]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should NOT complete in case of multiple callbacks are present and not all of them have been completed', () => {
const subject1 = new Subject<true>();
const subject2 = new Subject<true>();
const guard = new ExtensionsDataLoaderGuard([() => subject1.asObservable(), () => subject2.asObservable()]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);

subject1.next();
subject2.next();
subject1.complete();
expect(emittedSpy).not.toHaveBeenCalled();
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).not.toHaveBeenCalled();
});

it('should emit true and complete in case of multiple callbacks are present and all of them have been completed', () => {
const subject1 = new Subject<true>();
const subject2 = new Subject<true>();
const guard = new ExtensionsDataLoaderGuard([() => subject1.asObservable(), () => subject2.asObservable()]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);

subject1.next();
subject2.next();
subject1.complete();
subject2.complete();
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete even if one of the observables are errored, to not block the application loading', () => {
const subject1 = new Subject<true>();
const guard = new ExtensionsDataLoaderGuard([() => subject1.asObservable(), () => throwError(new Error())]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);

subject1.next();
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should call canActivate only once', () => {
const subject1 = new Subject<true>();
const extensionLoaders = {
fct1: () => subject1.asObservable()
};
const extensionLoaderSpy = spyOn(extensionLoaders, 'fct1').and.callThrough();
const guard = new ExtensionsDataLoaderGuard([extensionLoaders.fct1]);

guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);
expect(extensionLoaderSpy).toHaveBeenCalled();

extensionLoaderSpy.calls.reset();
subject1.next(true);
subject1.complete();
guard.canActivate(route).subscribe(emittedSpy, erroredSpy, completedSpy);
expect(extensionLoaderSpy).not.toHaveBeenCalled();
});
const setupTest = (extensionDataLoaders: any[]) => {
TestBed.overrideProvider(EXTENSION_DATA_LOADERS, { useValue: extensionDataLoaders });
return TestBed.runInInjectionContext(() => ExtensionsDataLoaderGuard(route, {} as RouterStateSnapshot)) as Observable<boolean>;
};

beforeEach(() => {
route = {} as ActivatedRouteSnapshot;
emittedSpy = jasmine.createSpy('emitted');
completedSpy = jasmine.createSpy('completed');
erroredSpy = jasmine.createSpy('errored');
resetInvoked();
});

it('should emit true and complete if no callback are present', () => {
const guard = setupTest([]);
guard.subscribe(emittedSpy, erroredSpy, completedSpy);
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete in case of only one callback is present, completed', () => {
const subject = new Subject<true>();
const guard = setupTest([() => subject.asObservable()]);
guard.subscribe(emittedSpy, erroredSpy, completedSpy);
subject.next(true);
expect(emittedSpy).not.toHaveBeenCalled();
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).not.toHaveBeenCalled();

subject.complete();
expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete in case of only one callback is present, errored', () => {
const guard = setupTest([() => throwError(new Error())]);
guard.subscribe(emittedSpy, erroredSpy, completedSpy);

expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should NOT complete in case of multiple callbacks are present and not all of them have been completed', () => {
const subject1 = new Subject<true>();
const subject2 = new Subject<true>();
const guard = setupTest([() => subject1.asObservable(), () => subject2.asObservable()]);

guard.subscribe(emittedSpy, erroredSpy, completedSpy);
subject1.next();
subject2.next();
subject1.complete();

expect(emittedSpy).not.toHaveBeenCalled();
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).not.toHaveBeenCalled();
});

it('should emit true and complete in case of multiple callbacks are present and all of them have been completed', () => {
const subject1 = new Subject<true>();
const subject2 = new Subject<true>();
const guard = setupTest([() => subject1.asObservable(), () => subject2.asObservable()]);

guard.subscribe(emittedSpy, erroredSpy, completedSpy);
subject1.next();
subject2.next();
subject1.complete();
subject2.complete();

expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should emit true and complete even if one of the observables are errored, to not block the application loading', () => {
const subject1 = new Subject<true>();
const guard = setupTest([() => subject1.asObservable(), () => throwError(new Error())]);

guard.subscribe(emittedSpy, erroredSpy, completedSpy);
subject1.next();

expect(emittedSpy).toHaveBeenCalledWith(true);
expect(erroredSpy).not.toHaveBeenCalled();
expect(completedSpy).toHaveBeenCalled();
});

it('should call canActivate only once', () => {
const subject1 = new Subject<true>();
const extensionLoaders = {
fct1: () => subject1.asObservable()
};
const extensionLoaderSpy = spyOn(extensionLoaders, 'fct1').and.callThrough();
const guard = setupTest([extensionLoaders.fct1]);

guard.subscribe(emittedSpy, erroredSpy, completedSpy);
expect(extensionLoaderSpy).toHaveBeenCalled();

extensionLoaderSpy.calls.reset();
subject1.next(true);
subject1.complete();

guard.subscribe(emittedSpy, erroredSpy, completedSpy);
expect(extensionLoaderSpy).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -22,8 +22,8 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/

import { Injectable, InjectionToken, Inject } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { InjectionToken, inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router';
import { Observable, forkJoin, of } from 'rxjs';
import { tap, map, catchError } from 'rxjs/operators';

@@ -36,44 +36,37 @@ export const EXTENSION_DATA_LOADERS = new InjectionToken<ExtensionLoaderCallback
factory: DefaultExtensionLoaderFactory
});

@Injectable({ providedIn: 'root' })
export class ExtensionsDataLoaderGuard implements CanActivate {
private invoked = false;
let invoked = false;

constructor(
@Inject(EXTENSION_DATA_LOADERS)
private extensionDataLoaders: ExtensionLoaderCallback[]
) {}

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
if (!this.invoked) {
if (!this.extensionDataLoaders.length) {
this.invoked = true;
return of(true);
}

const dataLoaderCallbacks = this.extensionDataLoaders.map((callback) => callback(route));

// Undocumented forkJoin behaviour/bug:
// https://github.com/ReactiveX/rxjs/issues/3246
// So all callbacks need to emit before completion, otherwise forkJoin will short circuit
return forkJoin(...dataLoaderCallbacks).pipe(
map(() => true),
tap(() => (this.invoked = true)),
catchError((e) => {
// eslint-disable-next-line no-console
console.error('Some of the extension data loader guards has been errored.');
// eslint-disable-next-line no-console
console.error(e);
return of(true);
})
);
} else {
export const ExtensionsDataLoaderGuard: CanActivateFn = (route: ActivatedRouteSnapshot): Observable<boolean> => {
const extensionDataLoaders = inject(EXTENSION_DATA_LOADERS);
if (!invoked) {
if (!extensionDataLoaders.length) {
invoked = true;
return of(true);
}
}

canActivateChild(): Observable<boolean> {
const dataLoaderCallbacks = extensionDataLoaders.map((callback) => callback(route));

// Undocumented forkJoin behaviour/bug:
// https://github.com/ReactiveX/rxjs/issues/3246
// So all callbacks need to emit before completion, otherwise forkJoin will short circuit
return forkJoin(...dataLoaderCallbacks).pipe(
map(() => true),
tap(() => (invoked = true)),
catchError((e) => {
// eslint-disable-next-line no-console
console.error('Some of the extension data loader guards has been errored.');
// eslint-disable-next-line no-console
console.error(e);
return of(true);
})
);
} else {
return of(true);
}
}
};

export const resetInvoked = () => {
invoked = false;
};
Loading

0 comments on commit 3a2d870

Please sign in to comment.