mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
Implement 'Add Password' and 'Change Permissions' tools in V2 (#4195)
# Description of Changes Implement Add Password and Change Permissions tools in V2 (both in one because Change Permissions is a fake endpoint which just calls Add Password behind the scenes). --------- Co-authored-by: James <james@crosscourtanalytics.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { describe, expect, test, vi, beforeEach, MockedFunction } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useAddPasswordOperation } from './useAddPasswordOperation';
|
||||
import type { AddPasswordFullParameters, AddPasswordParameters } from './useAddPasswordParameters';
|
||||
|
||||
// Mock the useToolOperation hook
|
||||
vi.mock('../shared/useToolOperation', () => ({
|
||||
useToolOperation: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock the translation hook
|
||||
const mockT = vi.fn((key: string) => `translated-${key}`);
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: mockT })
|
||||
}));
|
||||
|
||||
// Mock the error handler
|
||||
vi.mock('../../../utils/toolErrorHandler', () => ({
|
||||
createStandardErrorHandler: vi.fn(() => 'error-handler-function')
|
||||
}));
|
||||
|
||||
// Import the mocked function
|
||||
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
|
||||
|
||||
|
||||
describe('useAddPasswordOperation', () => {
|
||||
const mockUseToolOperation = vi.mocked(useToolOperation);
|
||||
|
||||
const getToolConfig = (): ToolOperationConfig<AddPasswordFullParameters> => mockUseToolOperation.mock.calls[0][0];
|
||||
|
||||
const mockToolOperationReturn: ToolOperationHook<unknown> = {
|
||||
files: [],
|
||||
thumbnails: [],
|
||||
downloadUrl: null,
|
||||
downloadFilename: '',
|
||||
isLoading: false,
|
||||
errorMessage: null,
|
||||
status: '',
|
||||
isGeneratingThumbnails: false,
|
||||
progress: null,
|
||||
executeOperation: vi.fn(),
|
||||
resetResults: vi.fn(),
|
||||
clearError: vi.fn(),
|
||||
cancelOperation: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseToolOperation.mockReturnValue(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test('should configure useToolOperation with correct parameters', () => {
|
||||
renderHook(() => useAddPasswordOperation());
|
||||
|
||||
expect(mockUseToolOperation).toHaveBeenCalledWith({
|
||||
operationType: 'addPassword',
|
||||
endpoint: '/api/v1/security/add-password',
|
||||
buildFormData: expect.any(Function),
|
||||
filePrefix: 'translated-addPassword.filenamePrefix_',
|
||||
multiFileEndpoint: false,
|
||||
getErrorMessage: 'error-handler-function'
|
||||
});
|
||||
});
|
||||
|
||||
test('should return the result from useToolOperation', () => {
|
||||
const { result } = renderHook(() => useAddPasswordOperation());
|
||||
|
||||
expect(result.current).toBe(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
description: 'with all parameters filled',
|
||||
password: 'user-password',
|
||||
ownerPassword: 'owner-password',
|
||||
keyLength: 256
|
||||
},
|
||||
{
|
||||
description: 'with empty passwords',
|
||||
password: '',
|
||||
ownerPassword: '',
|
||||
keyLength: 128
|
||||
},
|
||||
{
|
||||
description: 'with 40-bit key length',
|
||||
password: 'test',
|
||||
ownerPassword: '',
|
||||
keyLength: 40
|
||||
}
|
||||
])('should create form data correctly $description', ({ password, ownerPassword, keyLength }) => {
|
||||
renderHook(() => useAddPasswordOperation());
|
||||
|
||||
const callArgs = getToolConfig();
|
||||
const buildFormData = callArgs.buildFormData;
|
||||
|
||||
const testParameters: AddPasswordFullParameters = {
|
||||
password,
|
||||
ownerPassword,
|
||||
keyLength,
|
||||
permissions: {
|
||||
preventAssembly: false,
|
||||
preventExtractContent: false,
|
||||
preventExtractForAccessibility: false,
|
||||
preventFillInForm: false,
|
||||
preventModify: false,
|
||||
preventModifyAnnotations: false,
|
||||
preventPrinting: false,
|
||||
preventPrintingFaithful: false
|
||||
}
|
||||
};
|
||||
|
||||
const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
|
||||
const formData = buildFormData(testParameters, testFile as any /* FIX ME */);
|
||||
|
||||
// Verify the form data contains the file
|
||||
expect(formData.get('fileInput')).toBe(testFile);
|
||||
|
||||
// Verify password parameters
|
||||
expect(formData.get('password')).toBe(password);
|
||||
expect(formData.get('ownerPassword')).toBe(ownerPassword);
|
||||
expect(formData.get('keyLength')).toBe(keyLength.toString());
|
||||
});
|
||||
|
||||
test('should use correct translation for error messages', () => {
|
||||
renderHook(() => useAddPasswordOperation());
|
||||
|
||||
expect(mockT).toHaveBeenCalledWith(
|
||||
'addPassword.error.failed',
|
||||
'An error occurred while encrypting the PDF.'
|
||||
);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ property: 'multiFileEndpoint' as const, expectedValue: false },
|
||||
{ property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' },
|
||||
{ property: 'filePrefix' as const, expectedValue: 'translated-addPassword.filenamePrefix_' },
|
||||
{ property: 'operationType' as const, expectedValue: 'addPassword' }
|
||||
])('should configure $property correctly', ({ property, expectedValue }) => {
|
||||
renderHook(() => useAddPasswordOperation());
|
||||
|
||||
const callArgs = getToolConfig();
|
||||
expect(callArgs[property]).toBe(expectedValue);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { AddPasswordFullParameters } from './useAddPasswordParameters';
|
||||
import { getFormData } from '../changePermissions/useChangePermissionsOperation';
|
||||
|
||||
export const useAddPasswordOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const buildFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
formData.append("password", parameters.password);
|
||||
formData.append("ownerPassword", parameters.ownerPassword);
|
||||
formData.append("keyLength", parameters.keyLength.toString());
|
||||
getFormData(parameters.permissions).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
return formData;
|
||||
};
|
||||
|
||||
return useToolOperation<AddPasswordFullParameters>({
|
||||
operationType: 'addPassword',
|
||||
endpoint: '/api/v1/security/add-password',
|
||||
buildFormData,
|
||||
filePrefix: t('addPassword.filenamePrefix', 'encrypted') + '_',
|
||||
multiFileEndpoint: false,
|
||||
getErrorMessage: createStandardErrorHandler(t('addPassword.error.failed', 'An error occurred while encrypting the PDF.'))
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useAddPasswordParameters, defaultParameters, AddPasswordParametersHook } from './useAddPasswordParameters';
|
||||
import { defaultParameters as defaultChangePermissionsParameters, ChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters';
|
||||
|
||||
describe('useAddPasswordParameters', () => {
|
||||
test('should initialize with default parameters', () => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultParameters);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ paramName: 'password' as const, value: 'test-password' },
|
||||
{ paramName: 'ownerPassword' as const, value: 'owner-password' },
|
||||
{ paramName: 'keyLength' as const, value: 256 }
|
||||
])('should update parameter $paramName', ({ paramName, value }) => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.updateParameter(paramName, value);
|
||||
});
|
||||
|
||||
expect(result.current.parameters[paramName]).toBe(value);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ paramName: 'preventAssembly' as const },
|
||||
{ paramName: 'preventPrinting' as const }
|
||||
])('should update boolean permission parameter $paramName', ({ paramName }) => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.permissions.updateParameter(paramName, true);
|
||||
});
|
||||
|
||||
expect(result.current.permissions.parameters[paramName]).toBe(true);
|
||||
});
|
||||
|
||||
test('should reset parameters to defaults', () => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
// First, change some parameters
|
||||
act(() => {
|
||||
result.current.updateParameter('password', 'test');
|
||||
result.current.updateParameter('keyLength', 256);
|
||||
result.current.permissions.updateParameter('preventAssembly', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.password).toBe('test');
|
||||
expect(result.current.parameters.keyLength).toBe(256);
|
||||
expect(result.current.permissions.parameters.preventAssembly).toBe(true);
|
||||
|
||||
// Then reset
|
||||
act(() => {
|
||||
result.current.resetParameters();
|
||||
});
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultParameters);
|
||||
});
|
||||
|
||||
test('should return correct endpoint name', () => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
expect(result.current.getEndpointName()).toBe('add-password');
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
description: 'with user password only',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('password', 'user-password');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with owner password only',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('ownerPassword', 'owner-password');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with both passwords',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('password', 'user-password');
|
||||
hook.updateParameter('ownerPassword', 'owner-password');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with whitespace only password',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('password', ' \t ');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with whitespace only owner password',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('ownerPassword', ' \t ');
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with restrictions only',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.permissions.updateParameter('preventAssembly', true);
|
||||
hook.permissions.updateParameter('preventPrinting', true);
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'with passwords and restrictions',
|
||||
setup: (hook: AddPasswordParametersHook) => {
|
||||
hook.updateParameter('password', 'test-password');
|
||||
hook.permissions.updateParameter('preventAssembly', true);
|
||||
}
|
||||
}
|
||||
])('should validate parameters correctly $description', ({ setup }) => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
// Default state should be valid
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
|
||||
// Apply the test scenario setup
|
||||
act(() => {
|
||||
setup(result.current);
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
});
|
||||
|
||||
test.each(Object.keys(defaultChangePermissionsParameters) as Array<keyof ChangePermissionsParameters>)('should handle boolean restriction parameter %s', (param) => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.resetParameters();
|
||||
result.current.permissions.updateParameter(param, true);
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle mixed parameter types in updateParameter', () => {
|
||||
const { result } = renderHook(() => useAddPasswordParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.updateParameter('password', 'test-string');
|
||||
result.current.updateParameter('keyLength', 40);
|
||||
result.current.permissions.updateParameter('preventAssembly', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.password).toBe('test-string');
|
||||
expect(result.current.parameters.keyLength).toBe(40);
|
||||
expect(result.current.permissions.parameters.preventAssembly).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useState } from 'react';
|
||||
import { ChangePermissionsParameters, ChangePermissionsParametersHook, useChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters';
|
||||
|
||||
export interface AddPasswordParameters {
|
||||
password: string;
|
||||
ownerPassword: string;
|
||||
keyLength: number;
|
||||
}
|
||||
|
||||
export interface AddPasswordFullParameters extends AddPasswordParameters {
|
||||
permissions: ChangePermissionsParameters;
|
||||
}
|
||||
|
||||
export interface AddPasswordParametersHook {
|
||||
fullParameters: AddPasswordFullParameters;
|
||||
parameters: AddPasswordParameters;
|
||||
permissions: ChangePermissionsParametersHook;
|
||||
updateParameter: <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => void;
|
||||
resetParameters: () => void;
|
||||
validateParameters: () => boolean;
|
||||
getEndpointName: () => string;
|
||||
}
|
||||
|
||||
export const defaultParameters: AddPasswordParameters = {
|
||||
password: '',
|
||||
ownerPassword: '',
|
||||
keyLength: 128,
|
||||
};
|
||||
|
||||
export const useAddPasswordParameters = (): AddPasswordParametersHook => {
|
||||
const [parameters, setParameters] = useState<AddPasswordParameters>(defaultParameters);
|
||||
const permissions = useChangePermissionsParameters();
|
||||
const fullParameters: AddPasswordFullParameters = {
|
||||
...parameters,
|
||||
permissions: permissions.parameters,
|
||||
};
|
||||
|
||||
const updateParameter = <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => {
|
||||
setParameters(prev => ({
|
||||
...prev,
|
||||
[parameter]: value,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const resetParameters = () => {
|
||||
setParameters(defaultParameters);
|
||||
permissions.resetParameters();
|
||||
};
|
||||
|
||||
const validateParameters = () => {
|
||||
// No required parameters for Add Password. Defer to permissions validation.
|
||||
return permissions.validateParameters();
|
||||
};
|
||||
|
||||
const getEndpointName = () => {
|
||||
return 'add-password';
|
||||
};
|
||||
|
||||
return {
|
||||
fullParameters,
|
||||
parameters,
|
||||
permissions,
|
||||
updateParameter,
|
||||
resetParameters,
|
||||
validateParameters,
|
||||
getEndpointName,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useChangePermissionsOperation } from './useChangePermissionsOperation';
|
||||
import type { ChangePermissionsParameters } from './useChangePermissionsParameters';
|
||||
|
||||
// Mock the useToolOperation hook
|
||||
vi.mock('../shared/useToolOperation', () => ({
|
||||
useToolOperation: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock the translation hook
|
||||
const mockT = vi.fn((key: string) => `translated-${key}`);
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: mockT })
|
||||
}));
|
||||
|
||||
// Mock the error handler
|
||||
vi.mock('../../../utils/toolErrorHandler', () => ({
|
||||
createStandardErrorHandler: vi.fn(() => 'error-handler-function')
|
||||
}));
|
||||
|
||||
// Import the mocked function
|
||||
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
|
||||
|
||||
describe('useChangePermissionsOperation', () => {
|
||||
const mockUseToolOperation = vi.mocked(useToolOperation);
|
||||
|
||||
const getToolConfig = (): ToolOperationConfig<ChangePermissionsParameters> => mockUseToolOperation.mock.calls[0][0];
|
||||
|
||||
const mockToolOperationReturn: ToolOperationHook<unknown> = {
|
||||
files: [],
|
||||
thumbnails: [],
|
||||
downloadUrl: null,
|
||||
downloadFilename: '',
|
||||
isLoading: false,
|
||||
errorMessage: null,
|
||||
status: '',
|
||||
isGeneratingThumbnails: false,
|
||||
progress: null,
|
||||
executeOperation: vi.fn(),
|
||||
resetResults: vi.fn(),
|
||||
clearError: vi.fn(),
|
||||
cancelOperation: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseToolOperation.mockReturnValue(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test('should configure useToolOperation with correct parameters', () => {
|
||||
renderHook(() => useChangePermissionsOperation());
|
||||
|
||||
expect(mockUseToolOperation).toHaveBeenCalledWith({
|
||||
operationType: 'changePermissions',
|
||||
endpoint: '/api/v1/security/add-password',
|
||||
buildFormData: expect.any(Function),
|
||||
filePrefix: 'permissions_',
|
||||
multiFileEndpoint: false,
|
||||
getErrorMessage: 'error-handler-function'
|
||||
});
|
||||
});
|
||||
|
||||
test('should return the result from useToolOperation', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsOperation());
|
||||
|
||||
expect(result.current).toBe(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
preventAssembly: false,
|
||||
preventExtractContent: false,
|
||||
preventExtractForAccessibility: false,
|
||||
preventFillInForm: false,
|
||||
preventModify: false,
|
||||
preventModifyAnnotations: false,
|
||||
preventPrinting: false,
|
||||
preventPrintingFaithful: false,
|
||||
},
|
||||
{
|
||||
preventAssembly: true,
|
||||
preventExtractContent: false,
|
||||
preventExtractForAccessibility: true,
|
||||
preventFillInForm: false,
|
||||
preventModify: true,
|
||||
preventModifyAnnotations: false,
|
||||
preventPrinting: true,
|
||||
preventPrintingFaithful: false,
|
||||
},
|
||||
{
|
||||
preventAssembly: true,
|
||||
preventExtractContent: true,
|
||||
preventExtractForAccessibility: true,
|
||||
preventFillInForm: true,
|
||||
preventModify: true,
|
||||
preventModifyAnnotations: true,
|
||||
preventPrinting: true,
|
||||
preventPrintingFaithful: true,
|
||||
},
|
||||
])('should create form data correctly', (testParameters: ChangePermissionsParameters) => {
|
||||
renderHook(() => useChangePermissionsOperation());
|
||||
|
||||
const callArgs = getToolConfig();
|
||||
const buildFormData = callArgs.buildFormData;
|
||||
|
||||
const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
|
||||
const formData = buildFormData(testParameters, testFile as any /* FIX ME */);
|
||||
|
||||
// Verify the form data contains the file
|
||||
expect(formData.get('fileInput')).toBe(testFile);
|
||||
|
||||
(Object.keys(testParameters) as Array<keyof ChangePermissionsParameters>).forEach(key => {
|
||||
expect(formData.get(key), `Parameter ${key} should be set correctly`).toBe(testParameters[key].toString());
|
||||
});
|
||||
});
|
||||
|
||||
test('should use correct translation for error messages', () => {
|
||||
renderHook(() => useChangePermissionsOperation());
|
||||
|
||||
expect(mockT).toHaveBeenCalledWith(
|
||||
'changePermissions.error.failed',
|
||||
'An error occurred while changing PDF permissions.'
|
||||
);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ property: 'multiFileEndpoint' as const, expectedValue: false },
|
||||
{ property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' },
|
||||
{ property: 'filePrefix' as const, expectedValue: 'permissions_' },
|
||||
{ property: 'operationType' as const, expectedValue: 'changePermissions' }
|
||||
])('should configure $property correctly', ({ property, expectedValue }) => {
|
||||
renderHook(() => useChangePermissionsOperation());
|
||||
|
||||
const callArgs = getToolConfig();
|
||||
expect(callArgs[property]).toBe(expectedValue);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import type { ChangePermissionsParameters } from './useChangePermissionsParameters';
|
||||
|
||||
export const getFormData = ((parameters: ChangePermissionsParameters) =>
|
||||
Object.entries(parameters).map(([key, value]) =>
|
||||
[key, value.toString()]
|
||||
) as string[][]
|
||||
);
|
||||
|
||||
export const useChangePermissionsOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const buildFormData = (parameters: ChangePermissionsParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
|
||||
// Add all permission parameters
|
||||
getFormData(parameters).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
|
||||
return formData;
|
||||
};
|
||||
|
||||
return useToolOperation({
|
||||
operationType: 'changePermissions',
|
||||
endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool
|
||||
buildFormData,
|
||||
filePrefix: 'permissions_',
|
||||
multiFileEndpoint: false,
|
||||
getErrorMessage: createStandardErrorHandler(
|
||||
t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.')
|
||||
)
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useChangePermissionsParameters, defaultParameters, ChangePermissionsParameters } from './useChangePermissionsParameters';
|
||||
|
||||
describe('useChangePermissionsParameters', () => {
|
||||
test('should initialize with default parameters', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultParameters);
|
||||
});
|
||||
|
||||
test('should update individual boolean parameters', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.updateParameter('preventAssembly', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.preventAssembly).toBe(true);
|
||||
expect(result.current.parameters.preventPrinting).toBe(false); // Other parameters should remain unchanged
|
||||
|
||||
act(() => {
|
||||
result.current.updateParameter('preventPrinting', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.preventPrinting).toBe(true);
|
||||
expect(result.current.parameters.preventAssembly).toBe(true);
|
||||
});
|
||||
|
||||
test('should update all permission parameters', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
|
||||
|
||||
// Set all to true
|
||||
act(() => {
|
||||
permissionKeys.forEach(key => {
|
||||
result.current.updateParameter(key, true);
|
||||
});
|
||||
});
|
||||
|
||||
permissionKeys.forEach(key => {
|
||||
expect(result.current.parameters[key]).toBe(true);
|
||||
});
|
||||
|
||||
// Set all to false
|
||||
act(() => {
|
||||
permissionKeys.forEach(key => {
|
||||
result.current.updateParameter(key, false);
|
||||
});
|
||||
});
|
||||
|
||||
permissionKeys.forEach(key => {
|
||||
expect(result.current.parameters[key]).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('should reset parameters to defaults', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
// First, change some parameters
|
||||
act(() => {
|
||||
result.current.updateParameter('preventAssembly', true);
|
||||
result.current.updateParameter('preventPrinting', true);
|
||||
result.current.updateParameter('preventModify', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.preventAssembly).toBe(true);
|
||||
expect(result.current.parameters.preventPrinting).toBe(true);
|
||||
expect(result.current.parameters.preventModify).toBe(true);
|
||||
|
||||
// Then reset
|
||||
act(() => {
|
||||
result.current.resetParameters();
|
||||
});
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultParameters);
|
||||
});
|
||||
|
||||
test('should return correct endpoint name', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
expect(result.current.getEndpointName()).toBe('add-password');
|
||||
});
|
||||
|
||||
test('should always validate as true', () => {
|
||||
const { result } = renderHook(() => useChangePermissionsParameters());
|
||||
|
||||
// Default state should be valid
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
|
||||
// Set some restrictions - should still be valid
|
||||
act(() => {
|
||||
result.current.updateParameter('preventAssembly', true);
|
||||
result.current.updateParameter('preventPrinting', true);
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
|
||||
// Set all restrictions - should still be valid
|
||||
act(() => {
|
||||
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
|
||||
permissionKeys.forEach(key => {
|
||||
result.current.updateParameter(key, true);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface ChangePermissionsParameters {
|
||||
preventAssembly: boolean;
|
||||
preventExtractContent: boolean;
|
||||
preventExtractForAccessibility: boolean;
|
||||
preventFillInForm: boolean;
|
||||
preventModify: boolean;
|
||||
preventModifyAnnotations: boolean;
|
||||
preventPrinting: boolean;
|
||||
preventPrintingFaithful: boolean;
|
||||
}
|
||||
|
||||
export interface ChangePermissionsParametersHook {
|
||||
parameters: ChangePermissionsParameters;
|
||||
updateParameter: (parameter: keyof ChangePermissionsParameters, value: boolean) => void;
|
||||
resetParameters: () => void;
|
||||
validateParameters: () => boolean;
|
||||
getEndpointName: () => string;
|
||||
}
|
||||
|
||||
export const defaultParameters: ChangePermissionsParameters = {
|
||||
preventAssembly: false,
|
||||
preventExtractContent: false,
|
||||
preventExtractForAccessibility: false,
|
||||
preventFillInForm: false,
|
||||
preventModify: false,
|
||||
preventModifyAnnotations: false,
|
||||
preventPrinting: false,
|
||||
preventPrintingFaithful: false,
|
||||
};
|
||||
|
||||
export const useChangePermissionsParameters = (): ChangePermissionsParametersHook => {
|
||||
const [parameters, setParameters] = useState<ChangePermissionsParameters>(defaultParameters);
|
||||
|
||||
const updateParameter = <K extends keyof ChangePermissionsParameters>(parameter: K, value: ChangePermissionsParameters[K]) => {
|
||||
setParameters(prev => ({
|
||||
...prev,
|
||||
[parameter]: value,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const resetParameters = () => {
|
||||
setParameters(defaultParameters);
|
||||
};
|
||||
|
||||
const validateParameters = () => {
|
||||
// Always valid - any combination of permissions is allowed
|
||||
return true;
|
||||
};
|
||||
|
||||
const getEndpointName = () => {
|
||||
return 'add-password'; // Change Permissions is a fake endpoint for the Add Password tool
|
||||
};
|
||||
|
||||
return {
|
||||
parameters,
|
||||
updateParameter,
|
||||
resetParameters,
|
||||
validateParameters,
|
||||
getEndpointName,
|
||||
};
|
||||
};
|
||||
@@ -36,7 +36,7 @@ export interface ToolOperationConfig<TParams = void> {
|
||||
* - (params, files: File[]) => FormData: Multi-file processing
|
||||
* Not used when customProcessor is provided.
|
||||
*/
|
||||
buildFormData: ((params: TParams, file: File) => FormData) | ((params: TParams, files: File[]) => FormData);
|
||||
buildFormData: ((params: TParams, file: File) => FormData) | ((params: TParams, files: File[]) => FormData); /* FIX ME */
|
||||
|
||||
/** Prefix added to processed filenames (e.g., 'compressed_', 'split_') */
|
||||
filePrefix: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { SplitParameters } from '../../../components/tools/split/SplitSettings';
|
||||
import { SplitParameters } from './useSplitParameters';
|
||||
import { SPLIT_MODES } from '../../../constants/splitConstants';
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { useState } from 'react';
|
||||
import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode } from '../../../constants/splitConstants';
|
||||
import { SplitParameters } from '../../../components/tools/split/SplitSettings';
|
||||
import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode, SplitType } from '../../../constants/splitConstants';
|
||||
|
||||
export interface SplitParameters {
|
||||
mode: SplitMode | '';
|
||||
pages: string;
|
||||
hDiv: string;
|
||||
vDiv: string;
|
||||
merge: boolean;
|
||||
splitType: SplitType | '';
|
||||
splitValue: string;
|
||||
bookmarkLevel: string;
|
||||
includeMetadata: boolean;
|
||||
allowDuplicates: boolean;
|
||||
}
|
||||
|
||||
export interface SplitParametersHook {
|
||||
parameters: SplitParameters;
|
||||
|
||||
@@ -5,6 +5,7 @@ import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
|
||||
import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
||||
import ApiIcon from "@mui/icons-material/Api";
|
||||
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
||||
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
|
||||
|
||||
@@ -85,6 +86,24 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
||||
description: "Remove potentially harmful elements from PDF files",
|
||||
endpoints: ["sanitize-pdf"]
|
||||
},
|
||||
addPassword: {
|
||||
id: "addPassword",
|
||||
icon: <LockIcon />,
|
||||
component: React.lazy(() => import("../tools/AddPassword")),
|
||||
maxFiles: -1,
|
||||
category: "security",
|
||||
description: "Add password protection and restrictions to PDF files",
|
||||
endpoints: ["add-password"]
|
||||
},
|
||||
changePermissions: {
|
||||
id: "changePermissions",
|
||||
icon: <LockIcon />,
|
||||
component: React.lazy(() => import("../tools/ChangePermissions")),
|
||||
maxFiles: -1,
|
||||
category: "security",
|
||||
description: "Change document restrictions and permissions",
|
||||
endpoints: ["add-password"]
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user