diff --git a/packages/presentation/src/___tests___/file-blob.test.ts b/packages/presentation/src/___tests___/file-blob.test.ts new file mode 100644 index 0000000000..6ff8b7d8fc --- /dev/null +++ b/packages/presentation/src/___tests___/file-blob.test.ts @@ -0,0 +1,99 @@ +// +// Copyright © 2026 Hardcore Engineering Inc. +// Copyright © 2026 Exaviz. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { setMetadata } from '@hcengineering/platform' + +import { authorizationHeaders, getFileBlobUrl, isLocalObjectUrl } from '../file-blob' +import plugin from '../plugin' + +describe('isLocalObjectUrl', () => { + it('accepts blob and data URLs', () => { + expect(isLocalObjectUrl('blob:https://huly.app/abc')).toBe(true) + expect(isLocalObjectUrl('data:application/pdf;base64,AAA')).toBe(true) + }) + + it('rejects remote and relative URLs', () => { + expect(isLocalObjectUrl('https://dl.huly.app/blob/ws/file')).toBe(false) + expect(isLocalObjectUrl('/files/ws/file')).toBe(false) + expect(isLocalObjectUrl('')).toBe(false) + }) +}) + +describe('authorizationHeaders', () => { + it('omits Authorization when the token is empty', () => { + expect(authorizationHeaders(undefined)).toEqual({}) + expect(authorizationHeaders('')).toEqual({}) + }) + + it('sends a Bearer token', () => { + expect(authorizationHeaders('abc.def')).toEqual({ Authorization: 'Bearer abc.def' }) + }) +}) + +describe('getFileBlobUrl', () => { + const originalFetch = globalThis.fetch + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalCreate = URL.createObjectURL + const createObjectUrl = jest.fn(() => 'blob:https://huly.app/generated') + + beforeEach(() => { + globalThis.fetch = jest.fn() + createObjectUrl.mockClear() + URL.createObjectURL = createObjectUrl + setMetadata(plugin.metadata.Token, 'tok') + }) + + afterEach(() => { + globalThis.fetch = originalFetch + URL.createObjectURL = originalCreate + setMetadata(plugin.metadata.Token, undefined) + }) + + it('returns local object URLs without fetching', async () => { + const src = 'blob:https://huly.app/existing' + await expect(getFileBlobUrl(src)).resolves.toEqual({ url: src, owned: false }) + expect(globalThis.fetch).not.toHaveBeenCalled() + }) + + it('fetches remote files with the session token and wraps the body in a blob URL', async () => { + const body = new Blob(['%PDF-1.4'], { type: 'application/pdf' }) + ;(globalThis.fetch as jest.Mock).mockResolvedValue({ ok: true, blob: async () => body }) + + await expect(getFileBlobUrl('https://dl.huly.app/blob/ws/file')).resolves.toEqual({ + url: 'blob:https://huly.app/generated', + owned: true + }) + expect(globalThis.fetch).toHaveBeenCalledWith('https://dl.huly.app/blob/ws/file', { + headers: { Authorization: 'Bearer tok' }, + signal: undefined + }) + expect(createObjectUrl).toHaveBeenCalledWith(body) + }) + + it('sends no Authorization header when there is no session token', async () => { + setMetadata(plugin.metadata.Token, undefined) + ;(globalThis.fetch as jest.Mock).mockResolvedValue({ ok: true, blob: async () => new Blob(['x']) }) + + await getFileBlobUrl('/files/ws/file') + expect(globalThis.fetch).toHaveBeenCalledWith('/files/ws/file', { headers: {}, signal: undefined }) + }) + + it('throws when the file server rejects the request', async () => { + ;(globalThis.fetch as jest.Mock).mockResolvedValue({ ok: false, status: 401 }) + await expect(getFileBlobUrl('https://dl.huly.app/blob/ws/file')).rejects.toThrow('Failed to fetch file: 401') + expect(createObjectUrl).not.toHaveBeenCalled() + }) +}) diff --git a/packages/presentation/src/components/PDFViewer.svelte b/packages/presentation/src/components/PDFViewer.svelte index c22e9ef147..9a1d88f08c 100644 --- a/packages/presentation/src/components/PDFViewer.svelte +++ b/packages/presentation/src/components/PDFViewer.svelte @@ -1,5 +1,6 @@ -{#if iframeSrc} - -{:else} - -{/if} + + diff --git a/packages/ui/src/components/EmbeddedPDF.svelte b/packages/ui/src/components/EmbeddedPDF.svelte index db4c12cb6c..1cdf53ca4c 100644 --- a/packages/ui/src/components/EmbeddedPDF.svelte +++ b/packages/ui/src/components/EmbeddedPDF.svelte @@ -1,5 +1,6 @@ -