Stirling-PDF/frontend/src/utils/fileResponseUtils.test.ts
ConnorYoh 9c9acbfb5b
V2: Convert Tool (#3828)
🔄 Dynamic Processing Strategies

- Adaptive routing: Same tool uses different backend endpoints based on
file analysis
- Combined vs separate processing: Intelligently chooses between merge
operations and individual file processing
- Cross-format workflows: Enable complex conversions like "mixed files →
PDF" that other tools can't handle

  ⚙️ Format-Specific Intelligence

  Each conversion type gets tailored options:
  - HTML/ZIP → PDF: Zoom controls (0.1-3.0 increments) with live preview
  - Email → PDF: Attachment handling, size limits, recipient control
  - PDF → PDF/A: Digital signature detection with warnings
  - Images → PDF: Smart combining vs individual file options

 File Architecture

  Core Implementation:
  ├── Convert.tsx                     # Main stepped workflow UI
├── ConvertSettings.tsx # Centralized settings with smart detection
├── GroupedFormatDropdown.tsx # Enhanced format selector with grouping
├── useConvertParameters.ts # Smart detection & parameter management
  ├── useConvertOperation.ts         # Multi-strategy processing logic
  └── Settings Components:
      ├── ConvertFromWebSettings.tsx      # HTML zoom controls
      ├── ConvertFromEmailSettings.tsx    # Email attachment options
├── ConvertToPdfaSettings.tsx # PDF/A with signature detection
      ├── ConvertFromImageSettings.tsx    # Image PDF options
      └── ConvertToImageSettings.tsx      # PDF to image options

 Utility Layer

  Utils & Services:
├── convertUtils.ts # Format detection & endpoint routing
  ├── fileResponseUtils.ts          # Generic API response handling
└── setupTests.ts # Enhanced test environment with crypto mocks

  Testing & Quality

  Comprehensive Test Coverage

  Test Suite:
├── useConvertParameters.test.ts # Parameter logic & smart detection
  ├── useConvertParametersAutoDetection.test.ts  # File type analysis
├── ConvertIntegration.test.tsx # End-to-end conversion workflows
  ├── ConvertSmartDetectionIntegration.test.tsx  # Mixed file scenarios
  ├── ConvertE2E.spec.ts                     # Playwright browser tests
├── convertUtils.test.ts # Utility function validation
  └── fileResponseUtils.test.ts              # API response handling

  Advanced Test Features

  - Crypto API mocking: Proper test environment for file hashing
  - File.arrayBuffer() polyfills: Complete browser API simulation
  - Multi-file scenario testing: Complex batch processing validation
- CI/CD integration: Vitest runs in GitHub Actions with proper artifacts

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-01 16:08:04 +01:00

147 lines
5.1 KiB
TypeScript

/**
* Unit tests for file response utility functions
*/
import { describe, test, expect } from 'vitest';
import { getFilenameFromHeaders, createFileFromApiResponse } from './fileResponseUtils';
describe('fileResponseUtils', () => {
describe('getFilenameFromHeaders', () => {
test('should extract filename from content-disposition header', () => {
const contentDisposition = 'attachment; filename="document.pdf"';
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe('document.pdf');
});
test('should extract filename without quotes', () => {
const contentDisposition = 'attachment; filename=document.pdf';
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe('document.pdf');
});
test('should handle single quotes', () => {
const contentDisposition = "attachment; filename='document.pdf'";
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe('document.pdf');
});
test('should return null for malformed header', () => {
const contentDisposition = 'attachment; invalid=format';
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe(null);
});
test('should return null for empty header', () => {
const filename = getFilenameFromHeaders('');
expect(filename).toBe(null);
});
test('should return null for undefined header', () => {
const filename = getFilenameFromHeaders();
expect(filename).toBe(null);
});
test('should handle complex filenames with spaces and special chars', () => {
const contentDisposition = 'attachment; filename="My Document (1).pdf"';
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe('My Document (1).pdf');
});
test('should handle filename with extension when downloadHtml is enabled', () => {
const contentDisposition = 'attachment; filename="email_content.html"';
const filename = getFilenameFromHeaders(contentDisposition);
expect(filename).toBe('email_content.html');
});
});
describe('createFileFromApiResponse', () => {
test('should create file using header filename when available', () => {
const responseData = new Uint8Array([1, 2, 3, 4]);
const headers = {
'content-type': 'application/pdf',
'content-disposition': 'attachment; filename="server_filename.pdf"'
};
const fallbackFilename = 'fallback.pdf';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('server_filename.pdf');
expect(file.type).toBe('application/pdf');
expect(file.size).toBe(4);
});
test('should use fallback filename when no header filename', () => {
const responseData = new Uint8Array([1, 2, 3, 4]);
const headers = {
'content-type': 'application/pdf'
};
const fallbackFilename = 'converted_file.pdf';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('converted_file.pdf');
expect(file.type).toBe('application/pdf');
});
test('should handle HTML response when downloadHtml is enabled', () => {
const responseData = '<html><body>Test</body></html>';
const headers = {
'content-type': 'text/html',
'content-disposition': 'attachment; filename="email_content.html"'
};
const fallbackFilename = 'fallback.pdf';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('email_content.html');
expect(file.type).toBe('text/html');
});
test('should handle ZIP response', () => {
const responseData = new Uint8Array([80, 75, 3, 4]); // ZIP file signature
const headers = {
'content-type': 'application/zip',
'content-disposition': 'attachment; filename="converted_files.zip"'
};
const fallbackFilename = 'fallback.pdf';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('converted_files.zip');
expect(file.type).toBe('application/zip');
});
test('should use default content-type when none provided', () => {
const responseData = new Uint8Array([1, 2, 3, 4]);
const headers = {};
const fallbackFilename = 'test.bin';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('test.bin');
expect(file.type).toBe('application/octet-stream');
});
test('should handle null/undefined headers gracefully', () => {
const responseData = new Uint8Array([1, 2, 3, 4]);
const headers = null;
const fallbackFilename = 'test.bin';
const file = createFileFromApiResponse(responseData, headers, fallbackFilename);
expect(file.name).toBe('test.bin');
expect(file.type).toBe('application/octet-stream');
});
});
});