diff --git a/src/app/core/resolves/lean-folder-resolve.service.spec.ts b/src/app/core/resolves/lean-folder-resolve.service.spec.ts new file mode 100644 index 000000000..c94c804e5 --- /dev/null +++ b/src/app/core/resolves/lean-folder-resolve.service.spec.ts @@ -0,0 +1,274 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { ApiService } from '@shared/services/api/api.service'; +import { AccountService } from '@shared/services/account/account.service'; +import { MessageService } from '@shared/services/message/message.service'; +import { FolderResponse } from '@shared/services/api/index.repo'; +import { FolderVO } from '@root/app/models'; +import { LeanFolderResolveService } from './lean-folder-resolve.service'; + +const buildMockFolderResponse = (folderVO: Partial = {}) => + new FolderResponse({ + isSuccessful: true, + Results: [ + { + data: [ + { + FolderVO: new FolderVO({ + folderId: 'folder-1', + type: 'type.folder.generic', + ChildItemVOs: [], + ...folderVO, + }), + }, + ], + status: true, + message: ['OK'], + resultDT: new Date().toISOString(), + createdDT: null, + updatedDT: null, + }, + ], + }); + +const buildRoute = ( + params: Record = {}, + parentData: Record = {}, +): ActivatedRouteSnapshot => + ({ + params, + parent: { data: parentData }, + }) as any; + +const buildState = (url: string): RouterStateSnapshot => ({ url }) as any; + +describe('LeanFolderResolveService', () => { + let service: LeanFolderResolveService; + let getWithChildrenSpy: jasmine.Spy; + let accountService: { getRootFolder: jasmine.Spy; logOut: jasmine.Spy }; + let messageService: { showError: jasmine.Spy }; + let router: { navigate: jasmine.Spy }; + + const privateFolder = new FolderVO({ + folderId: 'private-root', + folder_linkId: 1, + archiveNbr: '0001-0001', + type: 'type.folder.root.private', + }); + + const appsFolder = new FolderVO({ + folderId: 'apps-root', + folder_linkId: 2, + archiveNbr: '0002-0001', + type: 'type.folder.root.app', + }); + + beforeEach(() => { + getWithChildrenSpy = jasmine + .createSpy('getWithChildren') + .and.resolveTo(buildMockFolderResponse()); + + accountService = { + getRootFolder: jasmine.createSpy('getRootFolder').and.returnValue( + new FolderVO({ + ChildItemVOs: [privateFolder, appsFolder], + }), + ), + logOut: jasmine.createSpy('logOut').and.resolveTo(undefined), + }; + + messageService = { showError: jasmine.createSpy('showError') }; + router = { navigate: jasmine.createSpy('navigate') }; + + TestBed.configureTestingModule({ + providers: [ + LeanFolderResolveService, + { + provide: ApiService, + useValue: { folder: { getWithChildren: getWithChildrenSpy } }, + }, + { provide: AccountService, useValue: accountService }, + { provide: MessageService, useValue: messageService }, + { provide: Router, useValue: router }, + ], + }); + + service = TestBed.inject(LeanFolderResolveService); + }); + + describe('route branches', () => { + it('should call getWithChildren with a FolderVO built from URL params', async () => { + const route = buildRoute({ + archiveNbr: '0001-0001', + folderLinkId: '123', + }); + const state = buildState('/private/0001-0001/123'); + + await service.resolve(route, state); + + const calledWith = getWithChildrenSpy.calls.mostRecent() + .args[0][0] as FolderVO; + + expect(calledWith.archiveNbr).toBe('0001-0001'); + expect(String(calledWith.folder_linkId)).toBe('123'); + }); + + it('should call getWithChildren with the apps folder when url is /apps', async () => { + const route = buildRoute({}); + const state = buildState('/apps'); + + await service.resolve(route, state); + + const calledWith = getWithChildrenSpy.calls.mostRecent() + .args[0][0] as FolderVO; + + expect(calledWith.type).toBe('type.folder.root.app'); + }); + + it('should call getWithChildren with the shared folder when in a /share/ route with a folder', async () => { + const sharedFolder = new FolderVO({ + folderId: 'shared-folder', + type: 'type.folder.generic', + }); + const route = buildRoute( + {}, + { sharePreviewVO: { FolderVO: sharedFolder, RecordVO: null } }, + ); + const state = buildState('/share/token123'); + + await service.resolve(route, state); + + const calledWith = getWithChildrenSpy.calls.mostRecent() + .args[0][0] as FolderVO; + + expect(calledWith.folderId).toBe('shared-folder'); + }); + + it('should return the folder directly without an API call when in a /share/ route with a record', async () => { + const currentFolder = new FolderVO({ + folderId: 'current', + pathAsArchiveNbr: ['root'], + pathAsText: ['My Files'], + pathAsFolder_linkId: [0], + ChildItemVOs: [], + }); + const sharedRecord = { recordId: 999, isRecord: true }; + const route = buildRoute( + {}, + { + sharePreviewVO: { FolderVO: null, RecordVO: sharedRecord }, + currentFolder, + }, + ); + const state = buildState('/share/token123'); + + const result = await service.resolve(route, state); + + expect(getWithChildrenSpy).not.toHaveBeenCalled(); + expect(result.ChildItemVOs).toContain(sharedRecord as any); + }); + + it('should call getWithChildren with the private root folder for the default route', async () => { + const route = buildRoute({}); + const state = buildState('/private'); + + await service.resolve(route, state); + + const calledWith = getWithChildrenSpy.calls.mostRecent() + .args[0][0] as FolderVO; + + expect(calledWith.type).toBe('type.folder.root.private'); + }); + + it('should return the FolderVO from the response', async () => { + getWithChildrenSpy.and.resolveTo( + buildMockFolderResponse({ folderId: 'resolved' }), + ); + + const route = buildRoute({}); + const state = buildState('/private'); + + const result = await service.resolve(route, state); + + expect(result.folderId).toBe('resolved'); + }); + }); + + describe('error handling', () => { + // mockError simulates what getWithChildren would throw if it rejects. + // In the catch block, response.getMessage() is called. + const mockError = { + getMessage: () => 'Folder not found', + }; + + it('should show an error message when getWithChildren rejects', async () => { + getWithChildrenSpy.and.rejectWith(mockError); + + const route = buildRoute({}); + const state = buildState('/private'); + + await service.resolve(route, state).catch(() => {}); + + expect(messageService.showError).toHaveBeenCalledWith({ + message: 'Folder not found', + translate: true, + }); + }); + + it('should log out and navigate to /login when a root folder fails', async () => { + // Default branch → privateFolder with type 'type.folder.root.private' + // which includes 'root' → logOut is called + getWithChildrenSpy.and.rejectWith(mockError); + + const route = buildRoute({}); + const state = buildState('/private'); + + await service.resolve(route, state).catch(() => {}); + + expect(accountService.logOut).toHaveBeenCalled(); + }); + + it('should log out and navigate to /login when the apps root folder fails', async () => { + // Apps branch → appsFolder with type 'type.folder.root.app' + // which also includes 'root' → logOut is called, not navigate(['/apps']) + getWithChildrenSpy.and.rejectWith(mockError); + + const route = buildRoute({}); + const state = buildState('/apps'); + + await service.resolve(route, state).catch(() => {}); + + expect(accountService.logOut).toHaveBeenCalled(); + }); + + it('should navigate to /private when a non-root shared folder fails', async () => { + // Share branch → sharedFolder with type 'type.folder.generic' (no 'root') + // state.url does not include 'apps' → navigate(['/private']) + getWithChildrenSpy.and.rejectWith(mockError); + + const sharedFolder = new FolderVO({ + folderId: 'shared-folder', + type: 'type.folder.generic', + }); + const route = buildRoute( + {}, + { sharePreviewVO: { FolderVO: sharedFolder, RecordVO: null } }, + ); + const state = buildState('/share/token123'); + + await service.resolve(route, state).catch(() => {}); + + expect(router.navigate).toHaveBeenCalledWith(['/private']); + }); + + it('should return a rejected promise when the API fails', async () => { + getWithChildrenSpy.and.rejectWith(mockError); + + const route = buildRoute({}); + const state = buildState('/private'); + + await expectAsync(service.resolve(route, state)).toBeRejected(); + }); + }); +}); diff --git a/src/app/core/resolves/lean-folder-resolve.service.ts b/src/app/core/resolves/lean-folder-resolve.service.ts index 72bbb904a..ba4cd3509 100644 --- a/src/app/core/resolves/lean-folder-resolve.service.ts +++ b/src/app/core/resolves/lean-folder-resolve.service.ts @@ -4,8 +4,6 @@ import { RouterStateSnapshot, Router, } from '@angular/router'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; import { find, cloneDeep } from 'lodash'; import { ApiService } from '@shared/services/api/api.service'; import { AccountService } from '@shared/services/account/account.service'; @@ -24,10 +22,10 @@ export class LeanFolderResolveService { private router: Router, ) {} - resolve( + async resolve( route: ActivatedRouteSnapshot, state: RouterStateSnapshot, - ): Observable | Promise { + ): Promise { let targetFolder; if (route.params.archiveNbr && route.params.folderLinkId) { @@ -51,7 +49,7 @@ export class LeanFolderResolveService { folder.pathAsText.unshift('Shares', 'Record'); folder.pathAsFolder_linkId.unshift(0, 0); folder.ChildItemVOs = [sharedRecord]; - return Promise.resolve(folder); + return await Promise.resolve(folder); } } else { const myFiles = find(this.accountService.getRootFolder().ChildItemVOs, { @@ -60,38 +58,32 @@ export class LeanFolderResolveService { targetFolder = new FolderVO(myFiles); } - return this.api.folder - .navigateLean(targetFolder) - .pipe( - map((response: FolderResponse) => { - if (!response.isSuccessful) { - throw response; - } + try { + const response: FolderResponse = await this.api.folder.getWithChildren([ + targetFolder, + ]); - return response.getFolderVO(true); - }), - ) - .toPromise() - .catch(async (response: FolderResponse) => { - this.message.showError({ - message: response.getMessage(), - translate: true, - }); - if (targetFolder.type.includes('root')) { - this.accountService - .logOut() - .then(() => { - this.router.navigate(['/login']); - }) - .catch(() => { - this.router.navigate(['/login']); - }); - } else if (state.url.includes('apps')) { - this.router.navigate(['/apps']); - } else { - this.router.navigate(['/private']); - } - return await Promise.reject(false); + return response.getFolderVO(true); + } catch (response) { + this.message.showError({ + message: response.getMessage(), + translate: true, }); + if (targetFolder.type.includes('root')) { + this.accountService + .logOut() + .then(() => { + this.router.navigate(['/login']); + }) + .catch(() => { + this.router.navigate(['/login']); + }); + } else if (state.url.includes('apps')) { + this.router.navigate(['/apps']); + } else { + this.router.navigate(['/private']); + } + return await Promise.reject(false); + } } } diff --git a/src/app/file-browser/components/publish/publish.component.ts b/src/app/file-browser/components/publish/publish.component.ts index 42bc4db41..38f288a4a 100644 --- a/src/app/file-browser/components/publish/publish.component.ts +++ b/src/app/file-browser/components/publish/publish.component.ts @@ -8,7 +8,6 @@ import { PublicLinkPipe } from '@shared/pipes/public-link.pipe'; import { AccountService } from '@shared/services/account/account.service'; import { GoogleAnalyticsService } from '@shared/services/google-analytics/google-analytics.service'; import { EVENTS } from '@shared/services/google-analytics/events'; -import { FolderResponse } from '@shared/services/api/index.repo'; import { PublicRoutePipe } from '@shared/pipes/public-route.pipe'; import { Router } from '@angular/router'; import { PublishIaData } from '@models/publish-ia-vo'; @@ -80,9 +79,9 @@ export class PublishComponent { let tries = 0; while (!this.publicItem && tries < 10) { tries += 1; - const publicRootResponse = (await this.api.folder - .navigateLean(publicRoot) - .toPromise()) as FolderResponse; + const publicRootResponse = await this.api.folder.getWithChildren([ + publicRoot, + ]); const publicRootFull = publicRootResponse.getFolderVO(true); const publicFolders: FolderVO[] = publicRootFull.ChildItemVOs.filter( (i) => i instanceof FolderVO, diff --git a/src/app/filesystem/filesystem-api.service.spec.ts b/src/app/filesystem/filesystem-api.service.spec.ts index 1ee86709e..e390b3fbd 100644 --- a/src/app/filesystem/filesystem-api.service.spec.ts +++ b/src/app/filesystem/filesystem-api.service.spec.ts @@ -3,7 +3,6 @@ import { FolderResponse } from '@shared/services/api/folder.repo'; import { FolderVO } from '@models/index'; import { DataStatus } from '@models/data-status.enum'; import { ApiService } from '@shared/services/api/api.service'; -import { of } from 'rxjs'; import { ShareLinksService } from '../share-links/services/share-links.service'; import { FilesystemApiService } from './filesystem-api.service'; @@ -11,34 +10,35 @@ const folderId = 42; const mockFolderVO = { folderId, - displayName: 'Unlisted Folder', + displayName: 'Test Folder', ChildItemVOs: [], dataStatus: DataStatus.Lean, }; -const mockResponse = new FolderResponse({ + +const mockSuccessResponse = new FolderResponse({ isSuccessful: true, Results: [ { - data: [ - { - FolderVO: mockFolderVO, - }, - ], + data: [{ FolderVO: mockFolderVO }], + status: true, + message: ['OK'], + resultDT: new Date().toISOString(), + createdDT: null, + updatedDT: null, }, ], }); +const mockFailureResponse = new FolderResponse({ + isSuccessful: false, + Results: [], +}); + const mockApiService = { folder: { getWithChildren: jasmine .createSpy('getWithChildren') - .and.returnValue(Promise.resolve(mockResponse)), - navigateLean: jasmine.createSpy('navigateLean').and.returnValue( - of({ - isSuccessful: true, - getFolderVO: () => mockFolderVO, - }), - ), + .and.resolveTo(mockSuccessResponse), }, }; @@ -47,6 +47,10 @@ describe('FilesystemApiService', () => { let shareLinksServiceSpy: jasmine.SpyObj; beforeEach(() => { + mockApiService.folder.getWithChildren = jasmine + .createSpy('getWithChildren') + .and.resolveTo(mockSuccessResponse); + shareLinksServiceSpy = jasmine.createSpyObj('ShareLinksService', [ 'isUnlistedShare', 'currentShareToken', @@ -67,27 +71,21 @@ describe('FilesystemApiService', () => { expect(service).toBeTruthy(); }); - it('should navigate using navigateLean', async () => { + it('should navigate using getWithChildren with null shareToken when not in an unlisted share', async () => { shareLinksServiceSpy.isUnlistedShare.and.resolveTo(false); - mockApiService.folder.navigateLean.and.returnValue( - of({ - isSuccessful: true, - getFolderVO: () => mockFolderVO, - }), - ); const folder = await service.navigate({ folderId }); - expect(mockApiService.folder.navigateLean).toHaveBeenCalledWith( - jasmine.any(FolderVO), + expect(mockApiService.folder.getWithChildren).toHaveBeenCalledWith( + [jasmine.any(FolderVO)], + null, ); expect(folder.folderId).toBe(folderId); - expect(folder.displayName).toBe('Unlisted Folder'); expect(folder.dataStatus).toBe(DataStatus.Lean); }); - it('should navigate using getWithChildren when in unlisted share', async () => { + it('should navigate using getWithChildren with shareToken when in an unlisted share', async () => { shareLinksServiceSpy.isUnlistedShare.and.resolveTo(true); shareLinksServiceSpy.currentShareToken = 'mock-token'; @@ -99,23 +97,13 @@ describe('FilesystemApiService', () => { ); expect(folder.folderId).toBe(folderId); - expect(folder.displayName).toBe('Unlisted Folder'); expect(folder.dataStatus).toBe(DataStatus.Lean); }); - it('should throw FolderResponse error if response is unsuccessful', async () => { + it('should throw when the response is unsuccessful', async () => { shareLinksServiceSpy.isUnlistedShare.and.resolveTo(false); - mockApiService.folder.navigateLean.and.resolveTo( - of({ isSuccessful: false }), - ); - - const promise = service.navigate({ folderId: 0 }); + mockApiService.folder.getWithChildren.and.resolveTo(mockFailureResponse); - try { - await promise; - fail('Expected promise to reject'); - } catch (error) { - expect(error).toBeDefined(); - } + await expectAsync(service.navigate({ folderId })).toBeRejected(); }); }); diff --git a/src/app/filesystem/filesystem-api.service.ts b/src/app/filesystem/filesystem-api.service.ts index 2026554df..c384702e9 100644 --- a/src/app/filesystem/filesystem-api.service.ts +++ b/src/app/filesystem/filesystem-api.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { firstValueFrom } from 'rxjs'; import { FolderVO, RecordVO } from '@models/index'; import { ApiService } from '@shared/services/api/api.service'; @@ -24,17 +23,13 @@ export class FilesystemApiService implements FilesystemApi { public async navigate(folder: FolderIdentifier): Promise { const isUnlistedShare = await this.shareLinksService.isUnlistedShare(); - let response: FolderResponse = null; - if (isUnlistedShare) { - response = await this.api.folder.getWithChildren( - [new FolderVO(folder)], - this.shareLinksService.currentShareToken, - ); - } else { - response = await firstValueFrom( - this.api.folder.navigateLean(new FolderVO(folder)), - ); - } + const shareToken = isUnlistedShare + ? this.shareLinksService.currentShareToken + : null; + const response: FolderResponse = await this.api.folder.getWithChildren( + [new FolderVO(folder)], + shareToken, + ); if (!response.isSuccessful) { throw response; } diff --git a/src/app/shared/services/api/folder.repo.spec.ts b/src/app/shared/services/api/folder.repo.spec.ts index f5e9a41e3..944affeb9 100644 --- a/src/app/shared/services/api/folder.repo.spec.ts +++ b/src/app/shared/services/api/folder.repo.spec.ts @@ -1,10 +1,10 @@ import { TestBed } from '@angular/core/testing'; import { FolderVO } from '@models/index'; -import { of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { ShareLink } from '@root/app/share-links/models/share-link'; import { HttpV2Service } from '../http-v2/http-v2.service'; import { HttpService } from '../http/http.service'; -import { FolderRepo } from './folder.repo'; +import { FolderRepo, FolderResponse } from './folder.repo'; const emptyResponse = { items: [] }; const fakeFolderResponse = { @@ -27,6 +27,40 @@ const fakeChildrenResponse = { ], }; +const buildStelaFolderResponse = (overrides: Record = {}) => ({ + items: [ + { + folderId: '42', + archiveNumber: 'ARCH-001', + archive: { id: 'arch-id', name: 'Test Archive' }, + folderLinkId: 100, + folderLinkType: 'type.folder.link.private', + parentFolderLinkId: 0, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-06-01T00:00:00Z', + description: 'Test', + displayTimestamp: '2024-01-01T00:00:00Z', + displayEndTimestamp: null, + displayName: 'Test Folder', + downloadName: 'Test Folder', + imageRatio: 1, + paths: { names: ['Test Folder'] }, + publicAt: null, + sort: null, + thumbnailUrls: null, + type: 'type.folder.generic', + status: 'status.generic.ok', + view: 'grid', + size: 0, + location: null, + parentFolder: { id: 'parent-id' }, + shares: null, + tags: null, + ...overrides, + }, + ], +}); + describe('Folder repo', () => { let folderRepo: FolderRepo; let httpSpy: jasmine.SpyObj; @@ -156,6 +190,100 @@ describe('Folder repo', () => { expect(result.Results[0].data[0].FolderVO).toBeDefined(); }); + describe('getWithChildren error handling', () => { + it('should return a FolderResponse with isSuccessful falsy when the Stela API throws', async () => { + const folderVO = new FolderVO({ folderId: 42 }); + const apiError = { error: { error: 'Internal server error' } }; + + httpV2Spy.get.and.returnValue( + new Observable((subscriber) => subscriber.error(apiError)), + ); + + const result = await folderRepo.getWithChildren([folderVO]); + + expect(result.isSuccessful).toBeFalsy(); + }); + + it('should surface the error message from err.error.error via getMessage()', async () => { + const folderVO = new FolderVO({ folderId: 42 }); + const apiError = { error: { error: 'Folder not found' } }; + + httpV2Spy.get.and.returnValue( + new Observable((subscriber) => subscriber.error(apiError)), + ); + + const result = await folderRepo.getWithChildren([folderVO]); + + expect(result.getMessage()).toBe('Folder not found'); + }); + + it('should return an empty error message when err.error.error is absent', async () => { + const folderVO = new FolderVO({ folderId: 42 }); + + httpV2Spy.get.and.returnValue( + new Observable((subscriber) => subscriber.error({})), + ); + + const result = await folderRepo.getWithChildren([folderVO]); + + expect(result.getMessage()).toBeUndefined(); + }); + }); + + describe('resolveFolderId', () => { + it('should not call the legacy /folder/get endpoint when folderId is already present', async () => { + const folderVO = new FolderVO({ folderId: 42 }); + + httpV2Spy.get.and.returnValues( + of([buildStelaFolderResponse()]), + of([{ items: [] }]), + ); + + await folderRepo.getWithChildren([folderVO]); + + expect(httpSpy.sendRequestPromise).not.toHaveBeenCalled(); + }); + + it('should call legacy /folder/get to resolve folderId when it is missing', async () => { + const folderVO = new FolderVO({ + archiveNbr: '0001-0001', + folder_linkId: 123, + }); + + const resolvedFolderResponse = new FolderResponse({ + isSuccessful: true, + Results: [ + { + data: [{ FolderVO: { folderId: 99 } }], + status: true, + message: ['OK'], + resultDT: new Date().toISOString(), + createdDT: null, + updatedDT: null, + }, + ], + }); + + httpSpy.sendRequestPromise.and.resolveTo(resolvedFolderResponse); + httpV2Spy.get.and.returnValues( + of([buildStelaFolderResponse({ folderId: '99' })]), + of([{ items: [] }]), + ); + + await folderRepo.getWithChildren([folderVO]); + + expect(httpSpy.sendRequestPromise).toHaveBeenCalledWith( + '/folder/get', + jasmine.any(Array), + jasmine.any(Object), + ); + + expect(httpV2Spy.get).toHaveBeenCalledWith('v2/folder', { + folderIds: [99], + }); + }); + }); + describe('getFolderShareLink', () => { const mockShareLink: ShareLink = { id: 'link1', diff --git a/src/app/shared/services/api/folder.repo.ts b/src/app/shared/services/api/folder.repo.ts index e9f1e8966..155e6fe7c 100644 --- a/src/app/shared/services/api/folder.repo.ts +++ b/src/app/shared/services/api/folder.repo.ts @@ -55,6 +55,10 @@ interface StelaFolder { id: string; name: string; }; + archiveNumber: string; + folderLinkId: number; + folderLinkType: string; + parentFolderLinkId: number; createdAt: string; updatedAt: string; description: string; @@ -102,6 +106,10 @@ const convertStelaFolderToFolderVO = (stelaFolder: StelaFolder): FolderVO => { ...stelaFolder, folderId: stelaFolder.folderId, archiveId: stelaFolder.archive?.id, + archiveNbr: stelaFolder.archiveNumber, + folder_linkId: stelaFolder.folderLinkId, + folder_linkType: stelaFolder.folderLinkType, + parentFolder_linkId: stelaFolder.parentFolderLinkId, displayName: stelaFolder.displayName, displayDT: stelaFolder.displayTimestamp, displayEndDT: stelaFolder.displayEndTimestamp, @@ -124,6 +132,8 @@ const convertStelaFolderToFolderVO = (stelaFolder: StelaFolder): FolderVO => { thumbnail256: stelaFolder.thumbnailUrls?.['256'], thumbnail256CloudPath: stelaFolder.thumbnailUrls?.['256'], status: stelaFolder.status, + createdDT: stelaFolder.createdAt, + updatedDT: stelaFolder.updatedAt, publicDT: stelaFolder.publicAt, parentFolderId: stelaFolder.parentFolder?.id, pathAsText: stelaFolder.paths?.names, @@ -137,6 +147,7 @@ const convertStelaFolderToFolderVO = (stelaFolder: StelaFolder): FolderVO => { ), ChildItemVOs: [...childRecordVOs, ...childFolderVOs], ShareVOs: (stelaFolder.shares ?? []).map(convertStelaSharetoShareVO), + accessRole: 'access.role.owner', // TODO: Replace with BE value once Stela returns accessRole isFolder: true, }); }; @@ -263,47 +274,72 @@ export class FolderRepo extends BaseRepo { return response[0].items; } + private async resolveFolderId(folderVO: FolderVO): Promise { + if (folderVO.folderId) { + return folderVO; + } + const response = await this.get([folderVO]); + const resolvedFolder = response.getFolderVO(); + return new FolderVO({ ...folderVO, folderId: resolvedFolder.folderId }); + } + public async getWithChildren( folderVOs: FolderVO[], shareToken: string = null, ): Promise { - // Stela has two separate endpoints -- one for loading the folder, one for loading the children. - const requests = folderVOs.map(async (folderVO) => { - const stelaFolder = await this.getStelaFolder(folderVO, shareToken); - const stelaFolderChildren = await this.getStelaFolderChildren( - folderVO, - shareToken, + try { + // Stela has two separate endpoints -- one for loading the folder, one for loading the children. + const requests = folderVOs.map(async (folderVO) => { + const resolvedFolderVO = await this.resolveFolderId(folderVO); + const stelaFolder = await this.getStelaFolder( + resolvedFolderVO, + shareToken, + ); + const stelaFolderChildren = await this.getStelaFolderChildren( + resolvedFolderVO, + shareToken, + ); + return { + ...stelaFolder, + children: stelaFolderChildren, + }; + }); + + const stelaFolders = (await Promise.all(requests)).flat(); + + // We need the `Results` to look the way v1 results look, for now. + const simulatedV1FolderResponseResults = stelaFolders.map( + (stelaFolder) => ({ + data: [ + { + FolderVO: convertStelaFolderToFolderVO(stelaFolder), + }, + ], + message: ['Folder retrieved'], + status: true, + resultDT: new Date().toISOString(), + createdDT: null, + updatedDT: null, + }), ); - return { - ...stelaFolder, - children: stelaFolderChildren, - }; - }); - - const stelaFolders = (await Promise.all(requests)).flat(); - - // We need the `Results` to look the way v1 results look, for now. - const simulatedV1FolderResponseResults = stelaFolders.map( - (stelaFolder) => ({ - data: [ - { - FolderVO: convertStelaFolderToFolderVO(stelaFolder), - }, - ], - message: ['Folder retrieved'], - status: true, - resultDT: new Date().toISOString(), - createdDT: null, - updatedDT: null, - }), - ); - const folderResponse = new FolderResponse({ - isSuccessful: true, - isSystemUp: true, - Results: simulatedV1FolderResponseResults, - }); - return folderResponse; + const folderResponse = new FolderResponse({ + isSuccessful: true, + isSystemUp: true, + Results: simulatedV1FolderResponseResults, + }); + return folderResponse; + } catch (err) { + // We need the error to look the way v1 errors look too, + // Changing all the error handlers would be errror prone + const errorFolderResponse = new FolderResponse(); + errorFolderResponse.Results = [ + { + message: [err?.error?.error], + }, + ]; + return errorFolderResponse; + } } public navigate(folderVO: FolderVO): Observable { diff --git a/src/app/shared/services/data/data.service.spec.ts b/src/app/shared/services/data/data.service.spec.ts index 247c59ffe..9baa20dc3 100644 --- a/src/app/shared/services/data/data.service.spec.ts +++ b/src/app/shared/services/data/data.service.spec.ts @@ -260,7 +260,10 @@ describe('DataService', () => { it('should add items to thumbRefreshQueue that meet the criteria', (done) => { const service = TestBed.inject(DataService); - const httpMock = TestBed.inject(HttpTestingController); + const api = TestBed.inject(ApiService); + spyOn(api.folder, 'getWithChildren').and.returnValue( + Promise.resolve(new FolderResponse(getLeanItemsData)), + ); const navigateResponse = new FolderResponse(navigateMinData); const currentFolder = navigateResponse.getFolderVO(true); service.setCurrentFolder(currentFolder); @@ -278,10 +281,5 @@ describe('DataService', () => { done(); }) .catch(done.fail); - - const req = httpMock.expectOne( - `${environment.apiUrl}/v2/folder?folderIds[]=149612`, - ); - req.flush(getLeanItemsData); }); }); diff --git a/src/app/views/components/timeline-view/timeline-view.component.ts b/src/app/views/components/timeline-view/timeline-view.component.ts index c67f46154..4ae0c9f03 100644 --- a/src/app/views/components/timeline-view/timeline-view.component.ts +++ b/src/app/views/components/timeline-view/timeline-view.component.ts @@ -485,9 +485,7 @@ export class TimelineViewComponent implements OnInit, AfterViewInit, OnDestroy { if (folder.isFetching) { await folder.fetched; } - const folderResponse = await this.api.folder - .navigateLean(folder) - .toPromise(); + const folderResponse = await this.api.folder.getWithChildren([folder]); this.dataService.setCurrentFolder(folderResponse.getFolderVO(true)); this.isNavigating = false; }