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 );
}
}