Skip to content
Open
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
86 changes: 58 additions & 28 deletions src/app/core/guards/is-file-provider.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,79 @@
import { ParamMap, UrlSegment } from '@angular/router';
import { MockProvider } from 'ng-mocks';

import { Mock } from 'vitest';

import { HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { Route, UrlSegment } from '@angular/router';

import { FileProviderRegistryService } from '@core/services/file-provider-registry.service';
import { FileProvider } from '@osf/features/files/constants';

import { isFileProvider } from './is-file-provider.guard';

describe('isFileProvider', () => {
const createMockParamMap = (): ParamMap => ({
get: () => null,
getAll: () => [],
has: () => false,
keys: [],
let registry: { isValidProvider: Mock };

const FOREIGN_PROVIDER = 's3compat';
const route: Route = {};

const createSegments = (...paths: string[]): UrlSegment[] => paths.map((path) => new UrlSegment(path, {}));

const runGuard = (segments: UrlSegment[]) => TestBed.runInInjectionContext(() => isFileProvider(route, segments));

beforeEach(() => {
const validProviders: string[] = [...Object.values(FileProvider), FOREIGN_PROVIDER];

registry = {
isValidProvider: vi.fn(async (providerName: string) => validProviders.includes(providerName)),
};

TestBed.configureTestingModule({
providers: [MockProvider(FileProviderRegistryService, registry)],
});
});

const createMockSegment = (path: string): UrlSegment => ({
path,
parameters: {},
parameterMap: createMockParamMap(),
it('should return true when id matches a built-in FileProvider value', async () => {
for (const provider of Object.values(FileProvider)) {
await expect(runGuard(createSegments(provider))).resolves.toBe(true);
expect(registry.isValidProvider).toHaveBeenCalledWith(provider);
}
});

const createMockSegments = (path: string) => [createMockSegment(path)];
it('should return true when id matches an external provider registered in gravyvalet', async () => {
await expect(runGuard(createSegments(FOREIGN_PROVIDER))).resolves.toBe(true);
expect(registry.isValidProvider).toHaveBeenCalledWith(FOREIGN_PROVIDER);
});

it('should return true when id matches a FileProvider value', () => {
Object.values(FileProvider).forEach((provider) => {
const result = isFileProvider({} as any, createMockSegments(provider));
expect(result).toBe(true);
});
it('should return false when id does not match any registered provider', async () => {
await expect(runGuard(createSegments('invalid-provider'))).resolves.toBe(false);
expect(registry.isValidProvider).toHaveBeenCalledWith('invalid-provider');
});

it('should return false when the registry cannot ask gravyvalet', async () => {
registry.isValidProvider.mockRejectedValue(new HttpErrorResponse({ status: 503 }));

await expect(runGuard(createSegments(FOREIGN_PROVIDER))).resolves.toBe(false);
});

it('should return false when id does not match any FileProvider value', () => {
const result = isFileProvider({} as any, createMockSegments('invalid-provider'));
expect(result).toBe(false);
it('should only check the first segment', async () => {
await expect(runGuard(createSegments(FileProvider.GoogleDrive, 'subfolder', 'file.txt'))).resolves.toBe(true);
expect(registry.isValidProvider).toHaveBeenCalledTimes(1);
expect(registry.isValidProvider).toHaveBeenCalledWith(FileProvider.GoogleDrive);
});

it('should return false when segments array is empty', () => {
const result = isFileProvider({} as any, []);
expect(result).toBe(false);
it('should return false when segments array is empty', async () => {
await expect(runGuard([])).resolves.toBe(false);
expect(registry.isValidProvider).not.toHaveBeenCalled();
});

it('should return false when first segment has no path', () => {
const result = isFileProvider({} as any, [createMockSegment('')]);
expect(result).toBe(false);
it('should return false when first segment has no path', async () => {
await expect(runGuard(createSegments(''))).resolves.toBe(false);
expect(registry.isValidProvider).not.toHaveBeenCalled();
});

it('should return false when first segment is undefined', () => {
const result = isFileProvider({} as any, [undefined as any]);
expect(result).toBe(false);
it('should return false when first segment is undefined', async () => {
await expect(runGuard([undefined as unknown as UrlSegment])).resolves.toBe(false);
expect(registry.isValidProvider).not.toHaveBeenCalled();
});
});
21 changes: 18 additions & 3 deletions src/app/core/guards/is-file-provider.guard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { inject } from '@angular/core';
import { CanMatchFn, Route, UrlSegment } from '@angular/router';

import { FileProvider } from '@osf/features/files/constants';
import { FileProviderRegistryService } from '@core/services/file-provider-registry.service';

export const isFileProvider: CanMatchFn = (route: Route, segments: UrlSegment[]) => {
/**
* Route guard that checks if a file provider is valid.
* Supports both built-in providers (osfstorage, googledrive, etc.) and
* dynamically discovered external storage services (foreign addons like s3compat).
*/
export const isFileProvider: CanMatchFn = async (route: Route, segments: UrlSegment[]) => {
const id = segments[0]?.path;
if (!id) {
return false;
}

return !!(id && Object.values(FileProvider).some((provider) => provider === id));
const registry = inject(FileProviderRegistryService);

try {
return await registry.isValidProvider(id);
} catch {
return false;
}
};
Loading