Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ACS-8726] Use functional route guards #4096

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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';

Expand All @@ -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
Loading