diff --git a/projects/aca-content/src/lib/components/view-profile/view-profile.guard.ts b/projects/aca-content/src/lib/components/view-profile/view-profile.guard.ts index 2fd170aa59..eebda82213 100644 --- a/projects/aca-content/src/lib/components/view-profile/view-profile.guard.ts +++ b/projects/aca-content/src/lib/components/view-profile/view-profile.guard.ts @@ -22,22 +22,16 @@ * from Hyland Software. If not, see . */ -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 | Promise | 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(); +}; diff --git a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.spec.ts b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.spec.ts index ea49e4ddfb..4cbbd5288c 100644 --- a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.spec.ts +++ b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.spec.ts @@ -22,9 +22,10 @@ * from Hyland Software. If not, see . */ -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; @@ -32,108 +33,110 @@ describe('ExtensionsDataLoaderGuard', () => { 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(); - 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(); - const subject2 = new Subject(); - 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(); - const subject2 = new Subject(); - 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(); - 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(); - 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; + }; + + 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(); + 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(); + const subject2 = new Subject(); + 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(); + const subject2 = new Subject(); + 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(); + 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(); + 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(); }); }); diff --git a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts index f79352486b..d9a5aa0e9c 100644 --- a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts +++ b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts @@ -22,8 +22,8 @@ * from Hyland Software. If not, see . */ -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 { - 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 => { + const extensionDataLoaders = inject(EXTENSION_DATA_LOADERS); + if (!invoked) { + if (!extensionDataLoaders.length) { + invoked = true; return of(true); } - } - canActivateChild(): Observable { + 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; +}; diff --git a/projects/aca-shared/src/lib/routing/plugin-enabled.guard.spec.ts b/projects/aca-shared/src/lib/routing/plugin-enabled.guard.spec.ts index 011477925e..819cc2ffed 100644 --- a/projects/aca-shared/src/lib/routing/plugin-enabled.guard.spec.ts +++ b/projects/aca-shared/src/lib/routing/plugin-enabled.guard.spec.ts @@ -25,11 +25,10 @@ import { AppConfigService } from '@alfresco/adf-core'; import { TestBed } from '@angular/core/testing'; import { PluginEnabledGuard } from './plugin-enabled.guard'; -import { ActivatedRouteSnapshot, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('PluginEnabledGuard', () => { - let service: PluginEnabledGuard; let getSpy: jasmine.Spy<(key: string, defaultValue?: boolean) => boolean>; let route: ActivatedRouteSnapshot; @@ -37,7 +36,6 @@ describe('PluginEnabledGuard', () => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule] }); - service = TestBed.inject(PluginEnabledGuard); getSpy = spyOn(TestBed.inject(AppConfigService), 'get'); route = new ActivatedRouteSnapshot(); route.data = { @@ -45,41 +43,31 @@ describe('PluginEnabledGuard', () => { }; }); - describe('canActivate', () => { - it('should call appConfigService.get with correct parameters', () => { - service.canActivate(route); - expect(getSpy).toHaveBeenCalledWith(route.data.plugin, true); - }); - - it('should return true if appConfigService.get returns true', () => { - getSpy.and.returnValue(true); - - expect(service.canActivate(route)).toBeTrue(); - }); + it('should call appConfigService.get with correct parameters', () => { + TestBed.runInInjectionContext(() => PluginEnabledGuard(route, {} as RouterStateSnapshot)); + expect(getSpy).toHaveBeenCalledWith(route.data.plugin, true); + }); - it('should return false if appConfigService.get returns false', () => { - getSpy.and.returnValue(true); + it('should return true if appConfigService.get returns true', () => { + getSpy.and.returnValue(true); + const result = TestBed.runInInjectionContext(() => PluginEnabledGuard(route, {} as RouterStateSnapshot)); - expect(service.canActivate(route)).toBeTrue(); - }); - - it('should navigate to root if plugin is not enabled', () => { - getSpy.and.returnValue(false); - const routerSpy = spyOn(TestBed.inject(Router), 'navigate'); + expect(result).toBeTrue(); + }); - service.canActivate(route); + it('should return false if appConfigService.get returns false', () => { + getSpy.and.returnValue(false); + const result = TestBed.runInInjectionContext(() => PluginEnabledGuard(route, {} as RouterStateSnapshot)); - expect(routerSpy).toHaveBeenCalledWith(['/']); - }); + expect(result).toBeFalse(); }); - describe('canActivateChild', () => { - it('should call canActivate with the same route and return its result', () => { - spyOn(service, 'canActivate').and.callThrough(); - const result = service.canActivateChild(route); + it('should navigate to root if plugin is not enabled', () => { + getSpy.and.returnValue(false); + const routerSpy = spyOn(TestBed.inject(Router), 'navigate'); - expect(service.canActivate).toHaveBeenCalledWith(route); - expect(result).toBe(service.canActivate(route)); - }); + TestBed.runInInjectionContext(() => PluginEnabledGuard(route, {} as RouterStateSnapshot)); + + expect(routerSpy).toHaveBeenCalledWith(['/']); }); }); diff --git a/projects/aca-shared/src/lib/routing/plugin-enabled.guard.ts b/projects/aca-shared/src/lib/routing/plugin-enabled.guard.ts index e8ac2ce284..b2a5a0e657 100644 --- a/projects/aca-shared/src/lib/routing/plugin-enabled.guard.ts +++ b/projects/aca-shared/src/lib/routing/plugin-enabled.guard.ts @@ -22,27 +22,19 @@ * from Hyland Software. If not, see . */ -import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, CanActivateFn } from '@angular/router'; +import { inject } from '@angular/core'; import { AppConfigService } from '@alfresco/adf-core'; -import { Injectable } from '@angular/core'; -@Injectable({ - providedIn: 'root' -}) -export class PluginEnabledGuard implements CanActivate, CanActivateChild { - constructor(private appConfigService: AppConfigService, private router: Router) {} +export const PluginEnabledGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => { + const appConfigService = inject(AppConfigService); + const router = inject(Router); - canActivate(route: ActivatedRouteSnapshot): boolean { - const isPluginEnabled = this.appConfigService.get(route.data.plugin, true); + const isPluginEnabled = appConfigService.get(route.data.plugin, true); - if (!isPluginEnabled) { - this.router.navigate(['/']); - } - - return isPluginEnabled; + if (!isPluginEnabled) { + router.navigate(['/']); } - canActivateChild(route: ActivatedRouteSnapshot): boolean { - return this.canActivate(route); - } -} + return isPluginEnabled; +}; diff --git a/projects/aca-shared/src/lib/routing/shared.guard.spec.ts b/projects/aca-shared/src/lib/routing/shared.guard.spec.ts index 7782e4aa1d..685fb80a9b 100644 --- a/projects/aca-shared/src/lib/routing/shared.guard.spec.ts +++ b/projects/aca-shared/src/lib/routing/shared.guard.spec.ts @@ -22,29 +22,44 @@ * from Hyland Software. If not, see . */ -import { of } from 'rxjs'; +import { TestBed } from '@angular/core/testing'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { isQuickShareEnabled } from '@alfresco/aca-shared/store'; import { AppSharedRuleGuard } from './shared.guard'; describe('AppSharedRuleGuard', () => { - it('should allow activation if quick share is enabled', () => { - const store: any = { - select: () => of(true) - }; - const guard = new AppSharedRuleGuard(store); - const emittedSpy = jasmine.createSpy('emitted'); + let state: RouterStateSnapshot; + const route: ActivatedRouteSnapshot = new ActivatedRouteSnapshot(); + const storeSpy = jasmine.createSpyObj('Store', ['select']); + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{ provide: Store, useValue: storeSpy }] + }); - guard.canActivate().subscribe(emittedSpy); - expect(emittedSpy).toHaveBeenCalledWith(true); + state = { url: 'some-url' } as RouterStateSnapshot; }); - it('should allow child activation if quick share is enabled', () => { - const store: any = { - select: () => of(true) - }; - const guard = new AppSharedRuleGuard(store); - const emittedSpy = jasmine.createSpy('emitted'); + it('should allow activation if quick share is enabled', (done) => { + storeSpy.select.and.returnValue(of(true)); + const guard = TestBed.runInInjectionContext(() => AppSharedRuleGuard(route, state)) as Observable; - guard.canActivateChild().subscribe(emittedSpy); - expect(emittedSpy).toHaveBeenCalledWith(true); + guard.subscribe((response) => { + expect(storeSpy.select).toHaveBeenCalledWith(isQuickShareEnabled); + expect(response).toBeTrue(); + done(); + }); + }); + + it('should not allow activation if quick share is disabled', (done) => { + storeSpy.select.and.returnValue(of(false)); + const guard = TestBed.runInInjectionContext(() => AppSharedRuleGuard(route, state)) as Observable; + + guard.subscribe((response) => { + expect(storeSpy.select).toHaveBeenCalledWith(isQuickShareEnabled); + expect(response).toBeFalse(); + done(); + }); }); }); diff --git a/projects/aca-shared/src/lib/routing/shared.guard.ts b/projects/aca-shared/src/lib/routing/shared.guard.ts index 735c1b1c50..37f2a89c5c 100644 --- a/projects/aca-shared/src/lib/routing/shared.guard.ts +++ b/projects/aca-shared/src/lib/routing/shared.guard.ts @@ -22,27 +22,13 @@ * from Hyland Software. If not, see . */ -import { Injectable } from '@angular/core'; -import { CanActivate } from '@angular/router'; +import { CanActivateFn } from '@angular/router'; +import { inject } from '@angular/core'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppStore, isQuickShareEnabled } from '@alfresco/aca-shared/store'; -@Injectable({ - providedIn: 'root' -}) -export class AppSharedRuleGuard implements CanActivate { - isQuickShareEnabled$: Observable; - - constructor(store: Store) { - this.isQuickShareEnabled$ = store.select(isQuickShareEnabled); - } - - canActivate(): Observable { - return this.isQuickShareEnabled$; - } - - canActivateChild(): Observable { - return this.canActivate(); - } -} +export const AppSharedRuleGuard: CanActivateFn = (): Observable => { + const store = inject(Store); + return store.select(isQuickShareEnabled); +};