Add tests for sanitize

This commit is contained in:
James 2025-08-05 17:22:03 +01:00
parent 4465130677
commit 25a0252b09
3 changed files with 540 additions and 0 deletions

View File

@ -0,0 +1,194 @@
import { describe, expect, test, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { MantineProvider } from '@mantine/core';
import SanitizeSettings from './SanitizeSettings';
import { SanitizeParameters } from '../../../hooks/tools/sanitize/useSanitizeParameters';
// Mock useTranslation with predictable return values
const mockT = vi.fn((key: string) => `mock-${key}`);
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: mockT })
}));
// Wrapper component to provide Mantine context
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<MantineProvider>{children}</MantineProvider>
);
describe('SanitizeSettings', () => {
const defaultParameters: SanitizeParameters = {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false,
};
const mockOnParameterChange = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
test('should render all sanitization option checkboxes', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
/>
</TestWrapper>
);
// Should render one checkbox for each parameter
const expectedCheckboxCount = Object.keys(defaultParameters).length;
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes).toHaveLength(expectedCheckboxCount);
});
test('should show correct initial checkbox states based on parameters', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
const parameterValues = Object.values(defaultParameters);
parameterValues.forEach((value, index) => {
if (value) {
expect(checkboxes[index]).toBeChecked();
} else {
expect(checkboxes[index]).not.toBeChecked();
}
});
});
test('should call onParameterChange with correct parameters when checkboxes are clicked', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
// Click the first checkbox (removeJavaScript - should toggle from true to false)
fireEvent.click(checkboxes[0]);
expect(mockOnParameterChange).toHaveBeenCalledWith('removeJavaScript', false);
// Click the third checkbox (removeXMPMetadata - should toggle from false to true)
fireEvent.click(checkboxes[2]);
expect(mockOnParameterChange).toHaveBeenCalledWith('removeXMPMetadata', true);
});
test('should disable all checkboxes when disabled prop is true', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
disabled={true}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
checkboxes.forEach(checkbox => {
expect(checkbox).toBeDisabled();
});
});
test('should enable all checkboxes when disabled prop is false or undefined', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
disabled={false}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
checkboxes.forEach(checkbox => {
expect(checkbox).not.toBeDisabled();
});
});
test('should handle different parameter combinations', () => {
const allEnabledParameters: SanitizeParameters = {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: true,
removeMetadata: true,
removeLinks: true,
removeFonts: true,
};
render(
<TestWrapper>
<SanitizeSettings
parameters={allEnabledParameters}
onParameterChange={mockOnParameterChange}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
checkboxes.forEach(checkbox => {
expect(checkbox).toBeChecked();
});
});
test('should call translation function with correct keys', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
/>
</TestWrapper>
);
// Verify that translation keys are being called (just check that it was called, not specific order)
expect(mockT).toHaveBeenCalledWith('sanitize.options.title', 'Sanitization Options');
expect(mockT).toHaveBeenCalledWith('sanitize.options.removeJavaScript', 'Remove JavaScript');
expect(mockT).toHaveBeenCalledWith('sanitize.options.removeEmbeddedFiles', 'Remove Embedded Files');
expect(mockT).toHaveBeenCalledWith('sanitize.options.note', expect.any(String));
});
test('should not call onParameterChange when disabled', () => {
render(
<TestWrapper>
<SanitizeSettings
parameters={defaultParameters}
onParameterChange={mockOnParameterChange}
disabled={true}
/>
</TestWrapper>
);
const checkboxes = screen.getAllByRole('checkbox');
// Verify checkboxes are disabled
checkboxes.forEach(checkbox => {
expect(checkbox).toBeDisabled();
});
// Try to click a disabled checkbox - this might still fire the event in tests
// but we can verify the checkbox state doesn't actually change
const firstCheckbox = checkboxes[0] as HTMLInputElement;
const initialChecked = firstCheckbox.checked;
fireEvent.click(firstCheckbox);
expect(firstCheckbox.checked).toBe(initialChecked);
});
});

View File

@ -0,0 +1,236 @@
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useSanitizeOperation } from './useSanitizeOperation';
// Mock useTranslation
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, fallback?: string, options?: any) => {
if (key === 'sanitize.error' && options?.error) {
return `Sanitization failed: ${options.error}`;
}
if (key === 'error.noFilesSelected') {
return 'No files selected';
}
if (key === 'sanitize.error.generic') {
return 'Sanitization failed';
}
return fallback || key;
}
})
}));
// Mock fetch
const mockFetch = vi.fn();
globalThis.fetch = mockFetch;
// Mock URL.createObjectURL and revokeObjectURL
globalThis.URL.createObjectURL = vi.fn(() => 'mock-blob-url');
globalThis.URL.revokeObjectURL = vi.fn();
describe('useSanitizeOperation', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
});
test('should initialize with default state', () => {
const { result } = renderHook(() => useSanitizeOperation());
expect(result.current.isLoading).toBe(false);
expect(result.current.errorMessage).toBe(null);
expect(result.current.downloadUrl).toBe(null);
expect(result.current.status).toBe(null);
});
test('should execute sanitization operation successfully', async () => {
const mockBlob = new Blob(['test'], { type: 'application/pdf' });
const mockResponse = {
ok: true,
blob: () => Promise.resolve(mockBlob)
};
mockFetch.mockResolvedValueOnce(mockResponse);
const { result } = renderHook(() => useSanitizeOperation());
const parameters = {
removeJavaScript: true,
removeEmbeddedFiles: false,
removeXMPMetadata: true,
removeMetadata: false,
removeLinks: false,
removeFonts: false
};
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
await act(async () => {
await result.current.executeOperation(parameters, [testFile]);
});
expect(mockFetch).toHaveBeenCalledWith('/api/v1/security/sanitize-pdf', {
method: 'POST',
body: expect.any(FormData)
});
expect(result.current.isLoading).toBe(false);
expect(result.current.downloadUrl).toBe('mock-blob-url');
expect(result.current.status).toBe('Sanitization completed successfully');
expect(result.current.errorMessage).toBe(null);
});
test('should handle API errors correctly', async () => {
const mockResponse = {
ok: false,
text: () => Promise.resolve('Server error')
};
mockFetch.mockResolvedValueOnce(mockResponse);
const { result } = renderHook(() => useSanitizeOperation());
const parameters = {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false
};
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
await act(async () => {
try {
await result.current.executeOperation(parameters, [testFile]);
} catch (error) {
// Expected to throw
}
});
expect(result.current.isLoading).toBe(false);
expect(result.current.errorMessage).toBe('Sanitization failed: Server error');
expect(result.current.downloadUrl).toBe(null);
expect(result.current.status).toBe(null);
});
test('should handle no files selected error', async () => {
const { result } = renderHook(() => useSanitizeOperation());
const parameters = {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false
};
let thrownError: Error | null = null;
await act(async () => {
try {
await result.current.executeOperation(parameters, []);
} catch (error) {
thrownError = error as Error;
}
});
// The error should be thrown
expect(thrownError).toBeInstanceOf(Error);
expect(thrownError!.message).toBe('No files selected');
expect(mockFetch).not.toHaveBeenCalled();
});
test('should send correct form data to API', async () => {
const mockBlob = new Blob(['test'], { type: 'application/pdf' });
const mockResponse = {
ok: true,
blob: () => Promise.resolve(mockBlob)
};
mockFetch.mockResolvedValueOnce(mockResponse);
const { result } = renderHook(() => useSanitizeOperation());
const parameters = {
removeJavaScript: true,
removeEmbeddedFiles: false,
removeXMPMetadata: true,
removeMetadata: false,
removeLinks: true,
removeFonts: false
};
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
await act(async () => {
await result.current.executeOperation(parameters, [testFile]);
});
const [url, options] = mockFetch.mock.calls[0];
expect(url).toBe('/api/v1/security/sanitize-pdf');
expect(options.method).toBe('POST');
const formData = options.body as FormData;
expect(formData.get('removeJavaScript')).toBe('true');
expect(formData.get('removeEmbeddedFiles')).toBe('false');
expect(formData.get('removeXMPMetadata')).toBe('true');
expect(formData.get('removeMetadata')).toBe('false');
expect(formData.get('removeLinks')).toBe('true');
expect(formData.get('removeFonts')).toBe('false');
expect(formData.get('fileInput')).toBe(testFile);
});
test('should reset results correctly', () => {
const { result } = renderHook(() => useSanitizeOperation());
act(() => {
result.current.resetResults();
});
expect(result.current.downloadUrl).toBe(null);
expect(result.current.errorMessage).toBe(null);
expect(result.current.status).toBe(null);
expect(globalThis.URL.revokeObjectURL).not.toHaveBeenCalled(); // No URL to revoke initially
});
test('should clear error message', async () => {
// Mock a failed API response
const mockResponse = {
ok: false,
text: () => Promise.resolve('API Error')
};
mockFetch.mockResolvedValueOnce(mockResponse);
const { result } = renderHook(() => useSanitizeOperation());
const parameters = {
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false
};
const testFile = new File(['test'], 'test.pdf', { type: 'application/pdf' });
// Trigger an API error
await act(async () => {
try {
await result.current.executeOperation(parameters, [testFile]);
} catch (error) {
// Expected to throw
}
});
expect(result.current.errorMessage).toBeTruthy();
act(() => {
result.current.clearError();
});
expect(result.current.errorMessage).toBe(null);
});
});

View File

@ -0,0 +1,110 @@
import { describe, expect, test } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useSanitizeParameters } from './useSanitizeParameters';
describe('useSanitizeParameters', () => {
test('should initialize with default parameters', () => {
const { result } = renderHook(() => useSanitizeParameters());
expect(result.current.parameters).toEqual({
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false,
});
});
test('should update individual parameters', () => {
const { result } = renderHook(() => useSanitizeParameters());
act(() => {
result.current.updateParameter('removeXMPMetadata', true);
});
expect(result.current.parameters.removeXMPMetadata).toBe(true);
expect(result.current.parameters.removeJavaScript).toBe(true); // Other params unchanged
expect(result.current.parameters.removeLinks).toBe(false); // Other params unchanged
});
test('should reset parameters to defaults', () => {
const { result } = renderHook(() => useSanitizeParameters());
// First, change some parameters
act(() => {
result.current.updateParameter('removeXMPMetadata', true);
result.current.updateParameter('removeJavaScript', false);
});
expect(result.current.parameters.removeXMPMetadata).toBe(true);
expect(result.current.parameters.removeJavaScript).toBe(false);
// Then reset
act(() => {
result.current.resetParameters();
});
expect(result.current.parameters).toEqual({
removeJavaScript: true,
removeEmbeddedFiles: true,
removeXMPMetadata: false,
removeMetadata: false,
removeLinks: false,
removeFonts: false,
});
});
test('should return correct endpoint name', () => {
const { result } = renderHook(() => useSanitizeParameters());
expect(result.current.getEndpointName()).toBe('sanitize-pdf');
});
test('should validate parameters correctly', () => {
const { result } = renderHook(() => useSanitizeParameters());
// Default state should be valid (has removeJavaScript and removeEmbeddedFiles enabled)
expect(result.current.validateParameters()).toBe(true);
// Turn off all parameters - should be invalid
act(() => {
result.current.updateParameter('removeJavaScript', false);
result.current.updateParameter('removeEmbeddedFiles', false);
});
expect(result.current.validateParameters()).toBe(false);
// Turn on one parameter - should be valid again
act(() => {
result.current.updateParameter('removeLinks', true);
});
expect(result.current.validateParameters()).toBe(true);
});
test('should handle all parameter types correctly', () => {
const { result } = renderHook(() => useSanitizeParameters());
const allParameters = [
'removeJavaScript',
'removeEmbeddedFiles',
'removeXMPMetadata',
'removeMetadata',
'removeLinks',
'removeFonts'
] as const;
allParameters.forEach(param => {
act(() => {
result.current.updateParameter(param, true);
});
expect(result.current.parameters[param]).toBe(true);
act(() => {
result.current.updateParameter(param, false);
});
expect(result.current.parameters[param]).toBe(false);
});
});
});