From 0696ce82bae59174522d3c10f18817f3b7901355 Mon Sep 17 00:00:00 2001 From: AleksanderSklorz <115619721+AleksanderSklorz@users.noreply.github.com> Date: Thu, 27 Feb 2025 08:05:44 +0100 Subject: [PATCH] [ACS-9344] change permissions error message for opening records management library (#4414) * [ACS-9344] Changed error messages * [ACS-9344] Unit tests * [ACS-9344] Fixed unit test --- projects/aca-content/assets/i18n/en.json | 5 + .../lib/components/files/files.component.html | 4 +- .../components/files/files.component.spec.ts | 180 +++++++++++++++++- .../lib/components/files/files.component.ts | 26 ++- .../lib/store/effects/library.effects.spec.ts | 76 ++++++++ .../src/lib/store/effects/library.effects.ts | 16 +- 6 files changed, 297 insertions(+), 10 deletions(-) create mode 100644 projects/aca-content/src/lib/store/effects/library.effects.spec.ts diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index 288db43ea..7bc908ee2 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -150,6 +150,11 @@ "TOOLTIP": "Access my favorite libraries" } } + }, + "ERRORS": { + "LIBRARY_NOT_FOUND": "This library no longer exists.", + "LIBRARY_NO_PERMISSIONS": "You do not have permission to view this library.", + "LIBRARY_LOADING_ERROR": "There was some problem during loading this library." } }, "SHARED": { diff --git a/projects/aca-content/src/lib/components/files/files.component.html b/projects/aca-content/src/lib/components/files/files.component.html index 5fef7b66b..28d71d37d 100644 --- a/projects/aca-content/src/lib/components/files/files.component.html +++ b/projects/aca-content/src/lib/components/files/files.component.html @@ -16,7 +16,7 @@
- +
@@ -47,7 +47,7 @@ (name-click)="handleNodeClick($event)" (selectedItemsCountChanged)="onSelectedItemsCountChanged($event)" (filterSelection)="onFilterSelected($event)" - (error)="onError()" + (error)="onError($event)" > diff --git a/projects/aca-content/src/lib/components/files/files.component.spec.ts b/projects/aca-content/src/lib/components/files/files.component.spec.ts index d7f16e5a1..d4b509e07 100644 --- a/projects/aca-content/src/lib/components/files/files.component.spec.ts +++ b/projects/aca-content/src/lib/components/files/files.component.spec.ts @@ -29,7 +29,7 @@ import { DocumentListService, FilterSearch, UploadService } from '@alfresco/adf- import { NodeActionsService } from '../../services/node-actions.service'; import { FilesComponent } from './files.component'; import { AppTestingModule } from '../../testing/app-testing.module'; -import { AppExtensionService, ContentApiService, DocumentBasePageService, initialState } from '@alfresco/aca-shared'; +import { AppExtensionService, ContentApiService, DocumentBasePageService, GenericErrorComponent, initialState } from '@alfresco/aca-shared'; import { of, Subject, throwError } from 'rxjs'; import { By } from '@angular/platform-browser'; import { NodeEntry, NodePaging, Node, PathElement } from '@alfresco/js-api'; @@ -38,6 +38,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { testHeader } from '../../testing/document-base-page-utils'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { getCurrentFolder } from '@alfresco/aca-shared/store'; +import { UnitTestingUtils } from '@alfresco/adf-core'; +import { HttpErrorResponse } from '@angular/common/http'; describe('FilesComponent', () => { let node; @@ -57,6 +59,7 @@ describe('FilesComponent', () => { let spyContent = null; let loadFolderByNodeIdSpy: jasmine.Spy; + let unitTestingUtils: UnitTestingUtils; function verifyEmptyFilterTemplate() { const template = fixture.debugElement.query(By.css('.empty-search__block')).nativeElement as HTMLElement; @@ -108,6 +111,7 @@ describe('FilesComponent', () => { extensions = TestBed.inject(AppExtensionService); store = TestBed.inject(MockStore); spyContent = spyOn(contentApi, 'getNode'); + unitTestingUtils = new UnitTestingUtils(fixture.debugElement); }); beforeEach(() => { @@ -122,7 +126,7 @@ describe('FilesComponent', () => { }); it('should be a valid current page', fakeAsync(() => { - spyContent.and.returnValue(throwError(null)); + spyContent.and.returnValue(throwError(() => new HttpErrorResponse({ status: 404 }))); component.ngOnInit(); fixture.detectChanges(); @@ -521,5 +525,177 @@ describe('FilesComponent', () => { }); }); + describe('Generic error', () => { + const getGenericErrorText = () => unitTestingUtils.getByDirective(GenericErrorComponent).componentInstance.text; + + beforeEach(() => { + router.url = '/libraries'; + }); + + describe('error returned by getNode on contentApi', () => { + it('should have set text to library no permission error if user does not have permission and actual url is libraries', () => { + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 403 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS'); + }); + + it('should have set text to library not found error if library does not exist and actual url is libraries', () => { + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 404 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); + }); + + it('should have set text to generic library loading error if there is different problem with loading of library and actual url is libraries', () => { + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 500 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); + }); + + it('should have set text to generic error if user does not have permission and actual url is not libraries', () => { + router.url = '/personal-files'; + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 403 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + + it('should have set text to generic error if library does not exist and actual url is not libraries', () => { + router.url = '/personal-files'; + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 404 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + + it('should have set text to generic error if there is different problem with loading of node and actual url is not libraries', () => { + router.url = '/personal-files'; + spyContent.and.returnValue( + throwError( + () => + new HttpErrorResponse({ + status: 500 + }) + ) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + }); + + describe('error emitted by error event', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + + it('should have set text to library no permission error if user does not have permission issue and actual url is libraries', () => { + component.documentList.error.emit( + new HttpErrorResponse({ + status: 403 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS'); + }); + + it('should have set text to library not found error if library does not exist issue and actual url is libraries', () => { + component.documentList.error.emit( + new HttpErrorResponse({ + status: 404 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); + }); + + it('should have set text to generic library loading error if there is different problem with loading of library and actual url is libraries', () => { + component.documentList.error.emit( + new HttpErrorResponse({ + status: 500 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); + }); + + it('should have set text to generic error if user does not have permission issue and actual url is not libraries', () => { + router.url = '/personal-files'; + component.documentList.error.emit( + new HttpErrorResponse({ + status: 403 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + + it('should have set text to generic error if library does not exist issue and actual url is not libraries', () => { + router.url = '/personal-files'; + component.documentList.error.emit( + new HttpErrorResponse({ + status: 404 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + + it('should have set text to generic error if there is different problem with loading of node and actual url is not libraries', () => { + router.url = '/personal-files'; + component.documentList.error.emit( + new HttpErrorResponse({ + status: 500 + }) + ); + fixture.detectChanges(); + + expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + }); + }); + }); + testHeader(FilesComponent); }); diff --git a/projects/aca-content/src/lib/components/files/files.component.ts b/projects/aca-content/src/lib/components/files/files.component.ts index af635f12c..6a2ca23b5 100644 --- a/projects/aca-content/src/lib/components/files/files.component.ts +++ b/projects/aca-content/src/lib/components/files/files.component.ts @@ -60,6 +60,7 @@ import { DocumentListDirective } from '../../directives/document-list.directive' import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { HttpErrorResponse } from '@angular/common/http'; @Component({ standalone: true, @@ -95,11 +96,16 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { selectedNode: NodeEntry; queryParams = null; showLoader$ = this.store.select(showLoaderSelector); - private nodePath: PathElement[]; - columns: DocumentListPresetRef[] = []; isFilterHeaderActive = false; + private nodePath: PathElement[]; + private _errorTranslationKey = 'APP.MESSAGES.ERRORS.MISSING_CONTENT'; + + get errorTranslationKey(): string { + return this._errorTranslationKey; + } + constructor(private contentApi: ContentApiService, private nodeActionsService: NodeActionsService, private route: ActivatedRoute) { super(); } @@ -132,7 +138,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { }); } }, - () => (this.isValidPath = false) + (error: HttpErrorResponse) => this.onError(error) ); }); @@ -394,7 +400,19 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { void this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap }); } - onError() { + onError(error: HttpErrorResponse) { this.isValidPath = false; + if (this.router.url.includes('libraries')) { + switch (error.status) { + case 403: + this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS'; + break; + case 404: + this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'; + break; + default: + this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'; + } + } } } diff --git a/projects/aca-content/src/lib/store/effects/library.effects.spec.ts b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts new file mode 100644 index 000000000..d53651bfb --- /dev/null +++ b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts @@ -0,0 +1,76 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { AppStore, NavigateLibraryAction } from '@alfresco/aca-shared/store'; +import { Store } from '@ngrx/store'; +import { TestBed } from '@angular/core/testing'; +import { ContentApiService } from '@alfresco/aca-shared'; +import { Subject } from 'rxjs'; +import { HttpErrorResponse } from '@angular/common/http'; +import { NotificationService } from '@alfresco/adf-core'; +import { EffectsModule } from '@ngrx/effects'; +import { LibraryEffects } from './library.effects'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { NodeEntry } from '@alfresco/js-api'; + +describe('LibraryEffects', () => { + let store: Store; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AppTestingModule, EffectsModule.forRoot([LibraryEffects])] + }); + store = TestBed.inject(Store); + }); + + describe('navigateLibrary$', () => { + let notificationService: NotificationService; + let node$: Subject; + + beforeEach(() => { + node$ = new Subject(); + spyOn(TestBed.inject(ContentApiService), 'getNode').and.returnValue(node$); + notificationService = TestBed.inject(NotificationService); + spyOn(notificationService, 'showError'); + }); + + it('should display library no permission error if user does not have permission', () => { + store.dispatch(new NavigateLibraryAction('libraryId')); + node$.error(new HttpErrorResponse({ status: 403 })); + expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS'); + }); + + it('should display library not found error if library does not exist', () => { + store.dispatch(new NavigateLibraryAction('libraryId')); + node$.error(new HttpErrorResponse({ status: 404 })); + expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); + }); + + it('should display generic library loading error if there is different problem than missing permissions or absence of library', () => { + store.dispatch(new NavigateLibraryAction('libraryId')); + node$.error(new HttpErrorResponse({ status: 500 })); + expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); + }); + }); +}); diff --git a/projects/aca-content/src/lib/store/effects/library.effects.ts b/projects/aca-content/src/lib/store/effects/library.effects.ts index cd81c0c27..9b768dcda 100644 --- a/projects/aca-content/src/lib/store/effects/library.effects.ts +++ b/projects/aca-content/src/lib/store/effects/library.effects.ts @@ -40,6 +40,7 @@ import { map, mergeMap, take } from 'rxjs/operators'; import { ContentApiService } from '@alfresco/aca-shared'; import { ContentManagementService } from '../../services/content-management.service'; import { NotificationService } from '@alfresco/adf-core'; +import { HttpErrorResponse } from '@angular/common/http'; @Injectable() export class LibraryEffects { @@ -118,8 +119,19 @@ export class LibraryEffects { const route = action.route ? action.route : 'libraries'; this.store.dispatch(new NavigateRouteAction([route, id])); }, - () => { - this.notificationService.showError('APP.MESSAGES.ERRORS.MISSING_CONTENT'); + (error: HttpErrorResponse) => { + let errorTranslationKey: string; + switch (error.status) { + case 403: + errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS'; + break; + case 404: + errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'; + break; + default: + errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'; + } + this.notificationService.showError(errorTranslationKey); } ); }