Skip to content
Draft
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
274 changes: 274 additions & 0 deletions src/app/core/resolves/lean-folder-resolve.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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<FolderVO> = {}) =>
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<string, string> = {},
parentData: Record<string, unknown> = {},
): 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();
});
});
});
64 changes: 28 additions & 36 deletions src/app/core/resolves/lean-folder-resolve.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,10 +22,10 @@ export class LeanFolderResolveService {
private router: Router,
) {}

resolve(
async resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<any> | Promise<any> {
): Promise<any> {
let targetFolder;

if (route.params.archiveNbr && route.params.folderLinkId) {
Expand All @@ -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, {
Expand All @@ -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);
}
}
}
7 changes: 3 additions & 4 deletions src/app/file-browser/components/publish/publish.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading