mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 09:29:24 +00:00

Added Filewithid type Updated code where file was being used to use filewithid Updated places we identified files by name or composite keys to use UUID Updated places we should have been using quickkey Updated pageeditor issue where we parsed pagenumber from pageid instead of using pagenumber directly --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: James Brunton <jbrunton96@gmail.com>
531 lines
18 KiB
TypeScript
531 lines
18 KiB
TypeScript
/**
|
|
* Integration tests for Convert Tool Smart Detection with real file scenarios
|
|
* Tests the complete flow from file upload through auto-detection to API calls
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { describe, test, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
|
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
|
import { useConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
|
import { FileContextProvider } from '../../contexts/FileContext';
|
|
import { I18nextProvider } from 'react-i18next';
|
|
import i18n from '../../i18n/config';
|
|
import axios from 'axios';
|
|
import { detectFileExtension } from '../../utils/fileUtils';
|
|
import { FIT_OPTIONS } from '../../constants/convertConstants';
|
|
import { createTestStirlingFile, createTestFilesWithId } from '../utils/testFileHelpers';
|
|
import { StirlingFile } from '../../types/fileContext';
|
|
|
|
// Mock axios
|
|
vi.mock('axios');
|
|
const mockedAxios = vi.mocked(axios);
|
|
|
|
// Mock only essential services that are actually called by the tests
|
|
vi.mock('../../services/fileStorage', () => ({
|
|
fileStorage: {
|
|
init: vi.fn().mockResolvedValue(undefined),
|
|
storeFile: vi.fn().mockImplementation((file, thumbnail) => {
|
|
return Promise.resolve({
|
|
id: `mock-id-${file.name}`,
|
|
name: file.name,
|
|
size: file.size,
|
|
type: file.type,
|
|
lastModified: file.lastModified,
|
|
thumbnail: thumbnail
|
|
});
|
|
}),
|
|
getAllFileMetadata: vi.fn().mockResolvedValue([]),
|
|
cleanup: vi.fn().mockResolvedValue(undefined)
|
|
}
|
|
}));
|
|
|
|
vi.mock('../../services/thumbnailGenerationService', () => ({
|
|
thumbnailGenerationService: {
|
|
generateThumbnail: vi.fn().mockResolvedValue('data:image/png;base64,fake-thumbnail'),
|
|
cleanup: vi.fn(),
|
|
destroy: vi.fn()
|
|
}
|
|
}));
|
|
|
|
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<I18nextProvider i18n={i18n}>
|
|
<FileContextProvider>
|
|
{children}
|
|
</FileContextProvider>
|
|
</I18nextProvider>
|
|
);
|
|
|
|
describe('Convert Tool - Smart Detection Integration Tests', () => {
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
// Mock successful API response
|
|
(mockedAxios.post as Mock).mockResolvedValue({
|
|
data: new Blob(['fake converted content'], { type: 'application/pdf' })
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up any blob URLs created during tests
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('Single File Auto-Detection Flow', () => {
|
|
test('should auto-detect PDF from DOCX and convert to PDF', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Create mock DOCX file
|
|
const docxFile = createTestStirlingFile('document.docx', 'docx content', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
|
|
|
// Test auto-detection
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes([docxFile]);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(paramsResult.current.parameters.fromExtension).toBe('docx');
|
|
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
|
expect(paramsResult.current.parameters.isSmartDetection).toBe(false);
|
|
});
|
|
|
|
// Test conversion operation
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
[docxFile]
|
|
);
|
|
});
|
|
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
});
|
|
|
|
test('should handle unknown file type with file-to-pdf fallback', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Create mock unknown file
|
|
const unknownFile = createTestStirlingFile('document.xyz', 'unknown content', 'application/octet-stream');
|
|
|
|
// Test auto-detection
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes([unknownFile]);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(paramsResult.current.parameters.fromExtension).toBe('file-xyz');
|
|
expect(paramsResult.current.parameters.toExtension).toBe('pdf'); // Fallback
|
|
expect(paramsResult.current.parameters.isSmartDetection).toBe(false);
|
|
});
|
|
|
|
// Test conversion operation
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
[unknownFile]
|
|
);
|
|
});
|
|
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Multi-File Smart Detection Flow', () => {
|
|
|
|
test('should detect all images and use img-to-pdf endpoint', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Create mock image files
|
|
const imageFiles = createTestFilesWithId([
|
|
{ name: 'photo1.jpg', content: 'jpg content', type: 'image/jpeg' },
|
|
{ name: 'photo2.png', content: 'png content', type: 'image/png' },
|
|
{ name: 'photo3.gif', content: 'gif content', type: 'image/gif' }
|
|
]);
|
|
|
|
// Test smart detection for all images
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(imageFiles);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(paramsResult.current.parameters.fromExtension).toBe('image');
|
|
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
|
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
|
expect(paramsResult.current.parameters.smartDetectionType).toBe('images');
|
|
});
|
|
|
|
// Test conversion operation
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
imageFiles
|
|
);
|
|
});
|
|
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/img/pdf', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
|
|
// Should send all files in single request
|
|
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
|
const files = formData.getAll('fileInput');
|
|
expect(files).toHaveLength(3);
|
|
});
|
|
|
|
test('should detect mixed file types and use file-to-pdf endpoint', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Create mixed file types
|
|
const mixedFiles = createTestFilesWithId([
|
|
{ name: 'document.pdf', content: 'pdf content', type: 'application/pdf' },
|
|
{ name: 'spreadsheet.xlsx', content: 'docx content', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
|
|
{ name: 'presentation.pptx', content: 'pptx content', type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }
|
|
]);
|
|
|
|
// Test smart detection for mixed types
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(mixedFiles);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(paramsResult.current.parameters.fromExtension).toBe('any');
|
|
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
|
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
|
expect(paramsResult.current.parameters.smartDetectionType).toBe('mixed');
|
|
});
|
|
|
|
// Test conversion operation
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
mixedFiles
|
|
);
|
|
});
|
|
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
});
|
|
|
|
test('should detect all web files and use html-to-pdf endpoint', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Create mock web files
|
|
const webFiles = createTestFilesWithId([
|
|
{ name: 'page1.html', content: '<html>content</html>', type: 'text/html' },
|
|
{ name: 'site.zip', content: 'zip content', type: 'application/zip' }
|
|
]);
|
|
|
|
// Test smart detection for web files
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(webFiles);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(paramsResult.current.parameters.fromExtension).toBe('html');
|
|
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
|
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
|
expect(paramsResult.current.parameters.smartDetectionType).toBe('web');
|
|
});
|
|
|
|
// Test conversion operation
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
webFiles
|
|
);
|
|
});
|
|
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/html/pdf', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
|
|
// Should process files separately for web files
|
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe('Web and Email Conversion Options Integration', () => {
|
|
|
|
test('should send correct HTML parameters for web-to-pdf conversion', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const htmlFile = createTestStirlingFile('page.html', '<html>content</html>', 'text/html');
|
|
|
|
// Set up HTML conversion parameters
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes([htmlFile]);
|
|
paramsResult.current.updateParameter('htmlOptions', {
|
|
zoomLevel: 1.5
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
[htmlFile]
|
|
);
|
|
});
|
|
|
|
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
|
expect(formData.get('zoom')).toBe('1.5');
|
|
});
|
|
|
|
test('should send correct email parameters for eml-to-pdf conversion', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const emlFile = createTestStirlingFile('email.eml', 'email content', 'message/rfc822');
|
|
|
|
// Set up email conversion parameters
|
|
act(() => {
|
|
paramsResult.current.updateParameter('fromExtension', 'eml');
|
|
paramsResult.current.updateParameter('toExtension', 'pdf');
|
|
paramsResult.current.updateParameter('emailOptions', {
|
|
includeAttachments: false,
|
|
maxAttachmentSizeMB: 20,
|
|
downloadHtml: true,
|
|
includeAllRecipients: true
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
[emlFile]
|
|
);
|
|
});
|
|
|
|
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
|
expect(formData.get('includeAttachments')).toBe('false');
|
|
expect(formData.get('maxAttachmentSizeMB')).toBe('20');
|
|
expect(formData.get('downloadHtml')).toBe('true');
|
|
expect(formData.get('includeAllRecipients')).toBe('true');
|
|
});
|
|
|
|
test('should send correct PDF/A parameters for pdf-to-pdfa conversion', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const pdfFile = createTestStirlingFile('document.pdf', 'pdf content', 'application/pdf');
|
|
|
|
// Set up PDF/A conversion parameters
|
|
act(() => {
|
|
paramsResult.current.updateParameter('fromExtension', 'pdf');
|
|
paramsResult.current.updateParameter('toExtension', 'pdfa');
|
|
paramsResult.current.updateParameter('pdfaOptions', {
|
|
outputFormat: 'pdfa'
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
[pdfFile]
|
|
);
|
|
});
|
|
|
|
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
|
expect(formData.get('outputFormat')).toBe('pdfa');
|
|
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/pdf/pdfa', expect.any(FormData), {
|
|
responseType: 'blob'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Image Conversion Options Integration', () => {
|
|
|
|
test('should send correct parameters for image-to-pdf conversion', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const imageFiles = createTestFilesWithId([
|
|
{ name: 'photo1.jpg', content: 'jpg1', type: 'image/jpeg' },
|
|
{ name: 'photo2.jpg', content: 'jpg2', type: 'image/jpeg' }
|
|
]);
|
|
|
|
// Set up image conversion parameters
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(imageFiles);
|
|
paramsResult.current.updateParameter('imageOptions', {
|
|
colorType: 'grayscale',
|
|
dpi: 150,
|
|
singleOrMultiple: 'single',
|
|
fitOption: FIT_OPTIONS.FIT_PAGE,
|
|
autoRotate: false,
|
|
combineImages: true
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
imageFiles
|
|
);
|
|
});
|
|
|
|
const formData = (mockedAxios.post as Mock).mock.calls[0][1] as FormData;
|
|
expect(formData.get('fitOption')).toBe(FIT_OPTIONS.FIT_PAGE);
|
|
expect(formData.get('colorType')).toBe('grayscale');
|
|
expect(formData.get('autoRotate')).toBe('false');
|
|
});
|
|
|
|
test('should process images separately when combineImages is false', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const imageFiles = createTestFilesWithId([
|
|
{ name: 'photo1.jpg', content: 'jpg1', type: 'image/jpeg' },
|
|
{ name: 'photo2.jpg', content: 'jpg2', type: 'image/jpeg' }
|
|
]);
|
|
|
|
// Set up for separate processing
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(imageFiles);
|
|
paramsResult.current.updateParameter('imageOptions', {
|
|
...paramsResult.current.parameters.imageOptions,
|
|
combineImages: false
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
imageFiles
|
|
);
|
|
});
|
|
|
|
// Should make separate API calls for each file
|
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe('Error Scenarios in Smart Detection', () => {
|
|
|
|
|
|
test('should handle partial failures in multi-file processing', async () => {
|
|
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
// Mock one success, one failure
|
|
(mockedAxios.post as Mock)
|
|
.mockResolvedValueOnce({
|
|
data: new Blob(['converted1'], { type: 'application/pdf' })
|
|
})
|
|
.mockRejectedValueOnce(new Error('File 2 failed'));
|
|
|
|
const mixedFiles = createTestFilesWithId([
|
|
{ name: 'doc1.txt', content: 'file1', type: 'text/plain' },
|
|
{ name: 'doc2.xyz', content: 'file2', type: 'application/octet-stream' }
|
|
]);
|
|
|
|
// Set up for separate processing (mixed smart detection)
|
|
act(() => {
|
|
paramsResult.current.analyzeFileTypes(mixedFiles);
|
|
});
|
|
|
|
await act(async () => {
|
|
await operationResult.current.executeOperation(
|
|
paramsResult.current.parameters,
|
|
mixedFiles
|
|
);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
// Should have processed at least one file successfully
|
|
expect(operationResult.current.files.length).toBeGreaterThan(0);
|
|
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Real File Extension Detection', () => {
|
|
|
|
test('should correctly detect various file extensions', async () => {
|
|
const { result } = renderHook(() => useConvertParameters(), {
|
|
wrapper: TestWrapper
|
|
});
|
|
|
|
const testCases = [
|
|
{ filename: 'document.PDF', expected: 'pdf' },
|
|
{ filename: 'image.JPEG', expected: 'jpg' }, // JPEG should normalize to jpg
|
|
{ filename: 'photo.jpeg', expected: 'jpg' }, // jpeg should normalize to jpg
|
|
{ filename: 'archive.tar.gz', expected: 'gz' },
|
|
{ filename: 'file.', expected: '' },
|
|
{ filename: '.hidden', expected: 'hidden' },
|
|
{ filename: 'noextension', expected: '' }
|
|
];
|
|
|
|
testCases.forEach(({ filename, expected }) => {
|
|
const detected = detectFileExtension(filename);
|
|
expect(detected).toBe(expected);
|
|
});
|
|
});
|
|
});
|
|
});
|