master_trainer = $this->factory->user->create( array( 'role' => 'hvac_master_trainer', 'user_login' => 'master_trainer', 'user_email' => 'master@example.com', ) ); $this->regular_trainer = $this->factory->user->create( array( 'role' => 'hvac_trainer', 'user_login' => 'regular_trainer', 'user_email' => 'trainer@example.com', ) ); // Add capabilities HVAC_Announcements_Permissions::add_capabilities(); // Create test announcements $this->test_announcements['published'] = wp_insert_post( array( 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'post_title' => 'Published Announcement', 'post_content' => 'Published content', 'post_status' => 'publish', 'post_author' => $this->master_trainer, ) ); $this->test_announcements['draft'] = wp_insert_post( array( 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'post_title' => 'Draft Announcement', 'post_content' => 'Draft content', 'post_status' => 'draft', 'post_author' => $this->master_trainer, ) ); $this->test_announcements['private'] = wp_insert_post( array( 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'post_title' => 'Private Announcement', 'post_content' => 'Private content', 'post_status' => 'private', 'post_author' => $this->master_trainer, ) ); // Get ajax handler instance $this->ajax_handler = HVAC_Announcements_Ajax::get_instance(); } /** * Teardown after each test */ public function tearDown() { parent::tearDown(); // Clean up test announcements foreach ( $this->test_announcements as $post_id ) { wp_delete_post( $post_id, true ); } // Clean up test users wp_delete_user( $this->master_trainer ); wp_delete_user( $this->regular_trainer ); // Remove capabilities HVAC_Announcements_Permissions::remove_capabilities(); } /** * Test rate limiting */ public function test_rate_limiting() { wp_set_current_user( $this->master_trainer ); $reflection = new ReflectionClass( $this->ajax_handler ); $method = $reflection->getMethod( 'check_rate_limit' ); $method->setAccessible( true ); // First 30 requests should pass for ( $i = 1; $i <= 30; $i++ ) { $result = $method->invoke( $this->ajax_handler ); $this->assertTrue( $result ); } // 31st request should be rate limited // Note: This would normally exit, so we can't test directly // Instead, check the transient $transient_key = 'hvac_ajax_rate_' . $this->master_trainer; $request_count = get_transient( $transient_key ); $this->assertEquals( 30, $request_count ); } /** * Test cache key generation */ public function test_cache_key_generation() { // Test that cache keys are different for different parameters $cache_version = wp_cache_get( 'announcements_cache_version', 'hvac_announcements' ); if ( false === $cache_version ) { $cache_version = 1; } $key1 = 'hvac_announcements_v' . $cache_version . '_' . md5( serialize( array( 'page' => 1, 'per_page' => 20, 'status' => 'publish', 'search' => '', 'is_master' => true ) ) ); $key2 = 'hvac_announcements_v' . $cache_version . '_' . md5( serialize( array( 'page' => 2, 'per_page' => 20, 'status' => 'publish', 'search' => '', 'is_master' => true ) ) ); $this->assertNotEquals( $key1, $key2 ); // Test that cache keys are different for different user roles $key_master = 'hvac_announcements_v' . $cache_version . '_' . md5( serialize( array( 'page' => 1, 'per_page' => 20, 'status' => 'any', 'search' => '', 'is_master' => true ) ) ); $key_regular = 'hvac_announcements_v' . $cache_version . '_' . md5( serialize( array( 'page' => 1, 'per_page' => 20, 'status' => 'any', 'search' => '', 'is_master' => false ) ) ); $this->assertNotEquals( $key_master, $key_regular ); } /** * Test cache invalidation */ public function test_cache_invalidation() { // Get current cache version $version_before = wp_cache_get( 'announcements_cache_version', 'hvac_announcements' ); if ( false === $version_before ) { $version_before = 1; } // Clear cache $this->ajax_handler->clear_announcements_cache(); // Get new cache version $version_after = wp_cache_get( 'announcements_cache_version', 'hvac_announcements' ); // Version should be incremented $this->assertEquals( $version_before + 1, $version_after ); } /** * Test content sanitization in get_announcements */ public function test_content_sanitization() { // Create announcement with potentially malicious content $malicious_id = wp_insert_post( array( 'post_type' => HVAC_Announcements_CPT::get_post_type(), 'post_title' => 'Test Title', 'post_excerpt' => 'Test excerpt', 'post_content' => 'Test content with ', 'post_status' => 'publish', 'post_author' => $this->master_trainer, ) ); // The excerpt should be sanitized with wp_kses_post $post = get_post( $malicious_id ); $excerpt = wp_kses_post( get_the_excerpt( $post ) ); // Should not contain script tags $this->assertStringNotContainsString( '', $excerpt ); // Clean up wp_delete_post( $malicious_id, true ); } /** * Test permission checks for different endpoints */ public function test_permission_checks() { // Test that get_categories checks permissions $reflection = new ReflectionClass( $this->ajax_handler ); $get_categories = $reflection->getMethod( 'get_categories' ); $get_categories->setAccessible( true ); // As regular trainer (should fail for creation endpoints) wp_set_current_user( $this->regular_trainer ); $_POST['nonce'] = wp_create_nonce( 'hvac_announcements_nonce' ); // This would normally send JSON and exit, so we can't test directly // Instead, test the permission check function $this->assertFalse( HVAC_Announcements_Permissions::current_user_can_create() ); // As master trainer (should pass) wp_set_current_user( $this->master_trainer ); $this->assertTrue( HVAC_Announcements_Permissions::current_user_can_create() ); } /** * Test view announcement permission check */ public function test_view_announcement_permissions() { // Regular trainer should be able to view published announcements wp_set_current_user( $this->regular_trainer ); $this->assertTrue( HVAC_Announcements_Permissions::current_user_can_read() ); // But should not see draft/private status in queries $is_master = HVAC_Announcements_Permissions::is_master_trainer( $this->regular_trainer ); $this->assertFalse( $is_master ); } /** * Test singleton pattern */ public function test_singleton_pattern() { $instance1 = HVAC_Announcements_Ajax::get_instance(); $instance2 = HVAC_Announcements_Ajax::get_instance(); $this->assertSame( $instance1, $instance2 ); } /** * Test nonce verification requirement */ public function test_nonce_verification() { wp_set_current_user( $this->master_trainer ); // Without nonce, requests should fail unset( $_POST['nonce'] ); // Test with valid nonce $_POST['nonce'] = wp_create_nonce( 'hvac_announcements_nonce' ); $valid_nonce = check_ajax_referer( 'hvac_announcements_nonce', 'nonce', false ); $this->assertTrue( $valid_nonce ); // Test with invalid nonce $_POST['nonce'] = 'invalid_nonce'; $invalid_nonce = check_ajax_referer( 'hvac_announcements_nonce', 'nonce', false ); $this->assertFalse( $invalid_nonce ); } }