# QDR Temporary Shared Storage - Testing Guide This document outlines a comprehensive testing strategy for the QDR Temporary Shared Storage plugin to ensure all components function correctly after the latest updates and refactoring. ## Manual Testing Procedures ### 1. Installation and Activation Testing - [ ] Upload plugin to a test WordPress site (WordPress 6.8.1+ and PHP 8.2+) - [ ] Activate the plugin - [ ] Verify that activation creates: - [ ] The custom database table with proper schema (including `fileid` field) - [ ] The required upload directories with `.htaccess` protection - [ ] The download page with `[qdr_tss_download]` shortcode - [ ] Default plugin settings including auto-generated API key - [ ] Scheduled cron job for cleanup ### 2. Admin Interface Testing #### 2.1 Settings Page (Settings > Shared Storage) - [ ] Navigate to Settings > Shared Storage - [ ] Verify all settings fields are displayed correctly: - [ ] API Key field with current key displayed - [ ] Media Category field - [ ] Maximum Storage Size field with current usage display - [ ] Test generating a new API key using "Generate New Key" button - [ ] Change settings values and save - [ ] Refresh page and verify settings were saved correctly - [ ] Test the "Purge Expired Files" button functionality - [ ] Verify storage usage bar displays correctly #### 2.2 Media Page (Media > Shared Storage) - [ ] Navigate to Media > Shared Storage - [ ] Verify the table loads correctly (initially empty) - [ ] Upload files through the REST API (see REST API testing section) - [ ] Verify files appear in the table with correct data: - [ ] File ID (new `fileid` format) - [ ] Original file name - [ ] Description and message - [ ] Active from/to dates - [ ] Reference - [ ] Upload date - [ ] Download count - [ ] Last download - [ ] Test sorting by different columns (click column headers) - [ ] Test pagination with multiple files - [ ] Test searching by filename and reference - [ ] **Test new action buttons**: - [ ] **Copy Permalink button** - verify it copies the correct URL to clipboard - [ ] **Force Activate button** - verify it resets the active period from current time - [ ] View Details button - verify modal opens with complete file information - [ ] Edit button - verify modal allows property editing - [ ] Delete button - verify confirmation dialog and file deletion #### 2.3 Enhanced Admin Features - [ ] Test responsive design on mobile devices - [ ] Verify modal dialogs work correctly - [ ] Test AJAX operations don't cause page refreshes - [ ] Verify loading indicators appear during operations - [ ] Test error handling in admin interface ### 3. REST API Testing #### 3.1 Authentication - [ ] Attempt API requests without an API key (should return 403) - [ ] Attempt API requests with an invalid API key (should return 403) - [ ] Attempt API requests with the correct API key (should succeed) - [ ] Test API key validation with various header formats #### 3.2 File Upload (Chunked) Test the complete chunked upload process: - [ ] **Initialize upload** (`POST /upload/init`): - [ ] Valid file name and size - [ ] Invalid parameters (missing fields) - [ ] File size exceeding storage limit - [ ] **Upload chunks** (`POST /upload/chunk`): - [ ] Sequential chunk upload - [ ] Out-of-order chunk upload - [ ] Missing chunks - [ ] Invalid chunk data - [ ] **Finalize upload** (`POST /upload/finalize`): - [ ] All required fields provided - [ ] Missing required fields - [ ] Invalid date formats - [ ] Verify file stored in correct date-based directory structure - [ ] Verify database record created with new `fileid` format - [ ] Verify response includes `fileid`, `permalink`, and `shortcode` Test with various file types and sizes: - [ ] Small file (< 1MB) - should work with single chunk - [ ] Medium file (1-10MB) - test multiple chunks - [ ] Large file (> 10MB) - test many chunks - [ ] PDF documents - [ ] Image files (JPEG, PNG, GIF) - [ ] Office documents (DOCX, XLSX, PPTX) - [ ] Archive files (ZIP, RAR) - [ ] Text files - [ ] Video files (if within size limits) #### 3.3 File Management API - [ ] **Get file details** (`GET /media/{fileid}`): - [ ] Valid file ID - [ ] Invalid/non-existent file ID - [ ] Verify password is not included in response - [ ] **Update file properties** (`PUT /media/{fileid}`): - [ ] Update message - [ ] Update active dates - [ ] Update reference - [ ] Update description - [ ] Invalid date formats - [ ] Empty update request - [ ] **Delete file** (`DELETE /media/{fileid}`): - [ ] Valid file ID - [ ] Invalid/non-existent file ID - [ ] Verify file deleted from storage - [ ] Verify database record removed - [ ] **List files** (`GET /media`): - [ ] Default pagination - [ ] Custom pagination parameters - [ ] Sorting by different fields - [ ] Filtering by filename - [ ] Filtering by reference - [ ] Combined filters and sorting ### 4. Public Interface Testing #### 4.1 Download Page Functionality - [ ] **Access download page** at `/shared-file-download/` - [ ] **Direct file access** via `/shared-file-download/{fileid}` - [ ] **Download form testing**: - [ ] Correct password and reference (should allow download) - [ ] Incorrect password (should show error) - [ ] Incorrect reference when required (should show error) - [ ] Missing password (should show error) - [ ] Empty form submission - [ ] **File status testing**: - [ ] Active file (current time between active_from and active_to) - [ ] Expired file (current time > active_to) - [ ] Future file (current time < active_from) - [ ] **Download process**: - [ ] Verify correct file headers are sent - [ ] Verify original filename is preserved - [ ] Verify download count increments - [ ] Verify last download time updates - [ ] **Shortcode testing**: - [ ] `[qdr_tss_download]` - generic form - [ ] `[qdr_tss_download id="fileid"]` - specific file form #### 4.2 URL Rewriting and Pretty URLs - [ ] Test pretty URLs: `/shared-file-download/{fileid}` - [ ] Test legacy URLs with query parameters - [ ] Test URL redirection and rewrite rules - [ ] Verify 404 handling for invalid file IDs ### 5. Scheduled Tasks and Cleanup Testing - [ ] **Manual cron trigger**: - [ ] Use admin "Purge Expired Files" button - [ ] Verify expired files are deleted from storage - [ ] Verify expired records are deleted from database - [ ] Verify non-expired files are not affected - [ ] **Automatic cron job**: - [ ] Wait for hourly cron execution or trigger manually - [ ] Test with files having different expiration dates - [ ] Verify empty directories are cleaned up - [ ] **Storage management**: - [ ] Test storage limit enforcement - [ ] Verify storage usage calculations - [ ] Test behavior when approaching storage limit ### 6. Security Testing #### 6.1 File Access Security - [ ] **Direct file access attempts**: - [ ] Try to access files directly via URL (should be blocked) - [ ] Verify `.htaccess` rules are working - [ ] Test directory browsing prevention - [ ] **Download security**: - [ ] Password protection enforcement - [ ] Reference code validation - [ ] Time-based access control - [ ] CSRF protection on download forms #### 6.2 API Security - [ ] **Authentication bypass attempts**: - [ ] Missing API key header - [ ] Invalid API key values - [ ] API key in wrong header format - [ ] **Input validation**: - [ ] SQL injection attempts in API parameters - [ ] XSS attempts in file names and descriptions - [ ] Path traversal attempts in file operations - [ ] Large payload attacks - [ ] **AJAX Security**: - [ ] Nonce verification for admin AJAX requests - [ ] Permission checks for admin operations - [ ] Rate limiting considerations ### 7. Edge Cases and Error Handling #### 7.1 File System Edge Cases - [ ] **Storage scenarios**: - [ ] Storage limit reached during upload - [ ] Disk space full - [ ] Permission issues on upload directory - [ ] Corrupted chunk uploads - [ ] **Concurrent operations**: - [ ] Multiple simultaneous uploads - [ ] Upload while cleanup is running - [ ] Edit file while someone is downloading #### 7.2 Database Edge Cases - [ ] **Data integrity**: - [ ] Database connection failures during upload - [ ] Orphaned files (in storage but not in database) - [ ] Orphaned records (in database but no file) - [ ] Character encoding issues in file names #### 7.3 Network and Performance - [ ] **Network issues**: - [ ] Interrupted chunk uploads - [ ] Slow network connections - [ ] Timeout scenarios - [ ] **Performance**: - [ ] Large file uploads (test with largest possible file) - [ ] High number of files in storage - [ ] Database query performance with many records ### 8. Internationalization Testing - [ ] **Translation testing**: - [ ] Switch WordPress language to Czech (cs_CZ) - [ ] Verify admin interface displays Czech translations - [ ] Test download page in Czech - [ ] Verify JavaScript strings are translated - [ ] **Character encoding**: - [ ] Upload files with non-ASCII names - [ ] Test descriptions with special characters - [ ] Test various character sets ## Automated Testing ### Setting Up the Test Environment 1. **Install PHPUnit and Dependencies**: ```bash composer require --dev phpunit/phpunit composer require --dev brain/monkey composer require --dev mockery/mockery ``` 2. **WordPress Test Library Setup**: ```bash bash bin/install-wp-tests.sh wordpress_test root '' localhost latest ``` 3. **PHPUnit Configuration** (`phpunit.xml`): ```xml ./tests/ ./includes/ ./admin/ ./public/ ./rest-api/ ``` ### Unit Test Examples #### Database Manager Tests (`tests/test-db-manager.php`) ```php db_manager = QDR_TSS_DB_Manager::instance(); } public function test_create_table() { global $wpdb; $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE; $wpdb->query("DROP TABLE IF EXISTS $table_name"); $result = $this->db_manager->create_table(); $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name; $this->assertTrue($table_exists); // Test table structure $columns = $wpdb->get_results("DESCRIBE $table_name"); $column_names = wp_list_pluck($columns, 'Field'); $this->assertContains('fileid', $column_names); $this->assertContains('original_file_name', $column_names); $this->assertContains('active_from', $column_names); $this->assertContains('active_to', $column_names); } public function test_insert_and_get_item() { $data = array( 'fileid' => 'test123456789', 'original_file_name' => 'test.pdf', 'description' => 'Test description', 'message' => 'Test message', 'active_from' => current_time('mysql'), 'active_to' => date('Y-m-d H:i:s', strtotime('+30 days')), 'password' => wp_hash_password('test_password'), 'reference' => 'TEST123', 'file_path' => 'test/path/test.pdf', 'file_size' => 1024 ); $item_id = $this->db_manager->insert_item($data); $this->assertNotFalse($item_id); $this->assertGreaterThan(0, $item_id); // Test get by ID $item = $this->db_manager->get_item($item_id); $this->assertEquals($data['fileid'], $item->fileid); $this->assertEquals($data['original_file_name'], $item->original_file_name); // Test get by fileid $item_by_fileid = $this->db_manager->get_item_by_fileid($data['fileid']); $this->assertEquals($item_id, $item_by_fileid->id); } public function test_get_items_with_pagination() { // Insert test data for ($i = 1; $i <= 15; $i++) { $this->db_manager->insert_item([ 'fileid' => 'test' . $i, 'original_file_name' => "test{$i}.pdf", 'description' => "Test file {$i}", 'active_from' => current_time('mysql'), 'active_to' => date('Y-m-d H:i:s', strtotime('+30 days')), 'password' => wp_hash_password('test'), 'file_path' => "test/test{$i}.pdf", 'file_size' => 1024 * $i ]); } // Test pagination $result = $this->db_manager->get_items(['per_page' => 10, 'page' => 1]); $this->assertEquals(10, count($result['items'])); $this->assertEquals(15, $result['total']); $this->assertEquals(2, $result['total_pages']); // Test second page $result = $this->db_manager->get_items(['per_page' => 10, 'page' => 2]); $this->assertEquals(5, count($result['items'])); } } ``` #### File Manager Tests (`tests/test-file-manager.php`) ```php file_manager = QDR_TSS_File_Manager::instance(); } public function test_init_file_system() { $result = $this->file_manager->init_file_system(); $this->assertTrue($result); $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR)); $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR . 'tmp/')); $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR . '.htaccess')); } public function test_generate_fileid() { $fileid1 = $this->file_manager->generate_fileid(); $fileid2 = $this->file_manager->generate_fileid(); $this->assertNotEquals($fileid1, $fileid2); $this->assertTrue(strlen($fileid1) > 40); $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]+$/', $fileid1); } public function test_chunked_upload_process() { // Initialize upload $upload_id = $this->file_manager->init_chunked_upload(); $this->assertNotFalse($upload_id); // Create test chunk data $chunk_data = 'This is test chunk data'; $temp_file = tempnam(sys_get_temp_dir(), 'test_chunk'); file_put_contents($temp_file, $chunk_data); $file_array = [ 'tmp_name' => $temp_file, 'name' => 'test_chunk', 'size' => strlen($chunk_data) ]; // Store chunk $result = $this->file_manager->store_chunk($upload_id, 0, $file_array); $this->assertTrue($result); // Check all chunks (should return true for single chunk) $all_chunks = $this->file_manager->check_all_chunks($upload_id, 1); $this->assertTrue($all_chunks); // Finalize upload $final_result = $this->file_manager->finalize_chunked_upload($upload_id, 1, 'test.txt'); $this->assertIsArray($final_result); $this->assertArrayHasKey('file_path', $final_result); $this->assertArrayHasKey('file_size', $final_result); // Cleanup unlink($temp_file); } } ``` #### REST API Tests (`tests/test-rest-api.php`) ```php server = $wp_rest_server = new WP_REST_Server; do_action('rest_api_init'); // Set up API key $settings = QDR_TSS_Settings::instance(); $this->api_key = $settings->generate_api_key(); $settings->update_setting('api_key', $this->api_key); } public function test_unauthorized_access() { $request = new WP_REST_Request('GET', '/qdr-tss/v1/media'); $response = $this->server->dispatch($request); $this->assertEquals(403, $response->get_status()); } public function test_authorized_access() { $request = new WP_REST_Request('GET', '/qdr-tss/v1/media'); $request->add_header('X-Api-Key', $this->api_key); $response = $this->server->dispatch($request); $this->assertEquals(200, $response->get_status()); } public function test_upload_init() { $request = new WP_REST_Request('POST', '/qdr-tss/v1/upload/init'); $request->add_header('X-Api-Key', $this->api_key); $request->set_param('original_file_name', 'test.pdf'); $request->set_param('file_size', 1024); $response = $this->server->dispatch($request); $this->assertEquals(200, $response->get_status()); $data = $response->get_data(); $this->assertArrayHasKey('upload_id', $data); $this->assertArrayHasKey('status', $data); $this->assertEquals('initialized', $data['status']); } } ``` ### Integration Testing Scripts #### API Client Test Script (`tests/integration/api-client-test.php`) ```php api_url = rtrim($api_url, '/'); $this->api_key = $api_key; } public function run_all_tests() { echo "Starting QDR TSS API Integration Tests\n"; echo "=====================================\n\n"; try { $this->test_complete_upload_workflow(); $this->test_file_management(); $this->test_error_handling(); echo "\n✅ All tests passed!\n"; } catch (Exception $e) { echo "\n❌ Test failed: " . $e->getMessage() . "\n"; } } private function test_complete_upload_workflow() { echo "Testing complete upload workflow...\n"; // Create test file $test_content = "This is a test file for QDR TSS integration testing."; $temp_file = tempnam(sys_get_temp_dir(), 'qdr_test'); file_put_contents($temp_file, $test_content); try { $result = $this->upload_file($temp_file, 'Integration Test File', 'test_password', 'INT_TEST_001'); echo " ✓ File uploaded successfully\n"; echo " ✓ File ID: {$result['fileid']}\n"; echo " ✓ Permalink: {$result['permalink']}\n"; // Store fileid for later tests $this->test_fileid = $result['fileid']; } finally { unlink($temp_file); } } private function test_file_management() { echo "Testing file management operations...\n"; if (!isset($this->test_fileid)) { throw new Exception("No test file ID available"); } // Test get file details $details = $this->get_file_details($this->test_fileid); echo " ✓ Retrieved file details\n"; // Test update file $updated = $this->update_file($this->test_fileid, [ 'message' => 'Updated via integration test' ]); echo " ✓ Updated file properties\n"; // Test delete file $this->delete_file($this->test_fileid); echo " ✓ Deleted file\n"; } private function test_error_handling() { echo "Testing error handling...\n"; // Test with invalid API key $old_key = $this->api_key; $this->api_key = 'invalid_key'; try { $this->get_file_list(); throw new Exception("Should have failed with invalid API key"); } catch (Exception $e) { if (strpos($e->getMessage(), '403') !== false) { echo " ✓ Invalid API key properly rejected\n"; } else { throw $e; } } $this->api_key = $old_key; // Test with non-existent file try { $this->get_file_details('nonexistent123'); throw new Exception("Should have failed with non-existent file"); } catch (Exception $e) { if (strpos($e->getMessage(), '404') !== false) { echo " ✓ Non-existent file properly handled\n"; } else { throw $e; } } } // API helper methods... private function upload_file($file_path, $description, $password, $reference) { // Implementation similar to previous examples // ... (chunked upload implementation) } private function get_file_details($fileid) { // ... (implementation) } private function update_file($fileid, $data) { // ... (implementation) } private function delete_file($fileid) { // ... (implementation) } private function get_file_list() { // ... (implementation) } } // Run tests $tester = new QDR_TSS_Integration_Test( 'https://your-test-site.com/wp-json/qdr-tss/v1', 'your-test-api-key' ); $tester->run_all_tests(); ``` ## Performance Testing ### Load Testing Script ```bash #!/bin/bash # Simple load test for file uploads API_URL="https://your-test-site.com/wp-json/qdr-tss/v1" API_KEY="your-test-api-key" CONCURRENT_UPLOADS=5 TEST_FILE_SIZE=1048576 # 1MB echo "Starting load test with $CONCURRENT_UPLOADS concurrent uploads" for i in $(seq 1 $CONCURRENT_UPLOADS); do ( # Create test file dd if=/dev/zero of="test_file_$i.bin" bs=$TEST_FILE_SIZE count=1 2>/dev/null # Upload file echo "Starting upload $i" start_time=$(date +%s) # Your upload implementation here # ... end_time=$(date +%s) duration=$((end_time - start_time)) echo "Upload $i completed in ${duration}s" # Cleanup rm "test_file_$i.bin" ) & done wait echo "All uploads completed" ``` ## Browser Testing Checklist ### Admin Interface Browser Testing Test the admin interface across different browsers: - [ ] **Chrome (latest)** - [ ] **Firefox (latest)** - [ ] **Safari (latest)** - [ ] **Edge (latest)** - [ ] **Mobile Chrome** - [ ] **Mobile Safari** For each browser, test: - [ ] Media page layout and functionality - [ ] Settings page functionality - [ ] Modal dialogs - [ ] Action buttons (Copy Permalink, Force Activate) - [ ] AJAX operations - [ ] JavaScript error console (should be clean) ## Final Testing Checklist Before deploying to production: - [ ] All manual tests pass - [ ] Unit tests pass (`./vendor/bin/phpunit`) - [ ] Integration tests pass - [ ] Security tests pass - [ ] Performance is acceptable - [ ] Documentation is updated - [ ] Translation files are current - [ ] Browser compatibility verified - [ ] Mobile responsiveness tested - [ ] Error logging reviewed - [ ] Backup and restore procedures tested ## Troubleshooting Common Issues ### Database Issues - **Problem**: Table not created during activation - **Solution**: Check database user permissions, verify WordPress table prefix ### File Upload Issues - **Problem**: Chunks not uploading - **Solution**: Check file permissions, PHP memory limits, server timeout settings ### API Issues - **Problem**: 403 errors with valid API key - **Solution**: Verify header format, check for mod_security rules, test with different HTTP clients ### Download Issues - **Problem**: Files not downloading - **Solution**: Check file permissions, verify .htaccess rules, test rewrite rules This comprehensive testing approach ensures the plugin works reliably across all components and integrates properly with WordPress and client applications.