chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
388
care-booking-block/tests/integration/test-css-injection.php
Normal file
388
care-booking-block/tests/integration/test-css-injection.php
Normal file
@@ -0,0 +1,388 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for CSS injection on wp_head
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test CSS injection functionality on wp_head hook
|
||||
*/
|
||||
class Test_CSS_Injection extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test wp_head hook is registered for CSS injection
|
||||
*/
|
||||
public function test_wp_head_hook_registered()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions');
|
||||
|
||||
// Check if our specific CSS injection hook is registered
|
||||
$wp_head_callbacks = $GLOBALS['wp_filter']['wp_head']->callbacks;
|
||||
$found_css_injection = false;
|
||||
|
||||
foreach ($wp_head_callbacks as $priority => $callbacks) {
|
||||
foreach ($callbacks as $callback) {
|
||||
if (is_array($callback['function']) &&
|
||||
isset($callback['function'][0]) &&
|
||||
is_object($callback['function'][0]) &&
|
||||
method_exists($callback['function'][0], 'inject_restriction_css')) {
|
||||
$found_css_injection = true;
|
||||
$this->assertEquals(20, $priority, 'CSS injection should have priority 20 (after theme styles)');
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection generates correct styles for blocked doctors
|
||||
*/
|
||||
public function test_css_injection_blocked_doctors()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, true);
|
||||
$this->create_test_doctor_restriction(997, false); // Not blocked
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Should contain CSS for blocked doctors
|
||||
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output,
|
||||
'Should contain CSS selector for blocked doctor 999');
|
||||
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="998"]', $head_output,
|
||||
'Should contain CSS selector for blocked doctor 998');
|
||||
$this->assertStringNotContainsString('.kivicare-doctor[data-doctor-id="997"]', $head_output,
|
||||
'Should NOT contain CSS selector for non-blocked doctor 997');
|
||||
|
||||
// Should contain display: none directive
|
||||
$this->assertStringContainsString('display: none !important;', $head_output,
|
||||
'Should contain display: none !important directive');
|
||||
|
||||
// Should be wrapped in style tags with proper data attribute
|
||||
$this->assertStringContainsString('<style data-care-booking>', $head_output,
|
||||
'Should contain opening style tag with data attribute');
|
||||
$this->assertStringContainsString('</style>', $head_output,
|
||||
'Should contain closing style tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection generates correct styles for blocked services
|
||||
*/
|
||||
public function test_css_injection_blocked_services()
|
||||
{
|
||||
// Create test service restrictions
|
||||
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
|
||||
$this->create_test_service_restriction(887, 998, true); // Block service 887 for doctor 998
|
||||
$this->create_test_service_restriction(886, 999, false); // Don't block service 886 for doctor 999
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Should contain CSS for blocked services with doctor context
|
||||
$this->assertStringContainsString('.kivicare-service[data-service-id="888"][data-doctor-id="999"]', $head_output,
|
||||
'Should contain CSS selector for service 888 blocked for doctor 999');
|
||||
$this->assertStringContainsString('.kivicare-service[data-service-id="887"][data-doctor-id="998"]', $head_output,
|
||||
'Should contain CSS selector for service 887 blocked for doctor 998');
|
||||
|
||||
// Should NOT contain CSS for non-blocked service
|
||||
$this->assertStringNotContainsString('[data-service-id="886"]', $head_output,
|
||||
'Should NOT contain CSS selector for non-blocked service 886');
|
||||
|
||||
// Should contain display: none directive
|
||||
$this->assertStringContainsString('display: none !important;', $head_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection includes fallback selectors
|
||||
*/
|
||||
public function test_css_injection_fallback_selectors()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Should include fallback ID selectors
|
||||
$this->assertStringContainsString('#doctor-999', $head_output,
|
||||
'Should include fallback ID selector for doctor');
|
||||
$this->assertStringContainsString('#service-888-doctor-999', $head_output,
|
||||
'Should include fallback ID selector for service');
|
||||
|
||||
// Should include fallback option selectors
|
||||
$this->assertStringContainsString('.doctor-selection option[value="999"]', $head_output,
|
||||
'Should include fallback option selector for doctor');
|
||||
$this->assertStringContainsString('.service-selection option[value="888"]', $head_output,
|
||||
'Should include fallback option selector for service');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection handles empty restrictions
|
||||
*/
|
||||
public function test_css_injection_empty_restrictions()
|
||||
{
|
||||
// No restrictions created
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Should still output style tags but with minimal content
|
||||
if (strpos($head_output, '<style data-care-booking>') !== false) {
|
||||
$this->assertStringContainsString('<style data-care-booking>', $head_output);
|
||||
$this->assertStringContainsString('</style>', $head_output);
|
||||
|
||||
// Content should be minimal (just comments or empty)
|
||||
$style_content = $this->extract_style_content($head_output);
|
||||
$this->assertLessThan(100, strlen(trim($style_content)),
|
||||
'Style content should be minimal when no restrictions exist');
|
||||
} else {
|
||||
// Or no style output at all is also acceptable
|
||||
$this->assertStringNotContainsString('data-care-booking', $head_output,
|
||||
'No CSS should be output when no restrictions exist');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection uses cache for performance
|
||||
*/
|
||||
public function test_css_injection_uses_cache()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Pre-populate cache
|
||||
$blocked_doctors = [999];
|
||||
$blocked_services = [];
|
||||
set_transient('care_booking_doctors_blocked', $blocked_doctors, 3600);
|
||||
|
||||
// Measure performance with cache
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should be very fast with cache (under 50ms)
|
||||
$this->assertLessThan(50, $execution_time, 'CSS injection should be fast with cache');
|
||||
|
||||
// Should contain correct CSS
|
||||
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection handles database errors gracefully
|
||||
*/
|
||||
public function test_css_injection_handles_database_errors()
|
||||
{
|
||||
// Create test restrictions first
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Mock database error
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
// Clear cache to force database query
|
||||
delete_transient('care_booking_doctors_blocked');
|
||||
|
||||
// CSS injection should handle error gracefully
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
// Should not throw fatal errors
|
||||
$this->assertTrue(true, 'CSS injection should handle database errors without fatal errors');
|
||||
|
||||
// May contain minimal or no CSS output due to error
|
||||
if (strpos($head_output, '<style') !== false) {
|
||||
$this->assertStringContainsString('<style', $head_output, 'Should contain style tags even on error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection output is properly escaped and secure
|
||||
*/
|
||||
public function test_css_injection_security()
|
||||
{
|
||||
// Create test restrictions with edge case IDs
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Should not contain any unescaped content
|
||||
$this->assertStringNotContainsString('<script', $head_output, 'Should not contain script tags');
|
||||
$this->assertStringNotContainsString('javascript:', $head_output, 'Should not contain javascript: protocol');
|
||||
$this->assertStringNotContainsString('expression(', $head_output, 'Should not contain CSS expressions');
|
||||
|
||||
// Should contain proper CSS syntax
|
||||
$this->assertRegExp('/\{[^}]*display:\s*none\s*!important[^}]*\}/', $head_output,
|
||||
'Should contain proper CSS syntax for display:none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection only occurs on frontend pages
|
||||
*/
|
||||
public function test_css_injection_frontend_only()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Test admin context
|
||||
set_current_screen('edit-post');
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$admin_output = ob_get_clean();
|
||||
|
||||
// Test frontend context
|
||||
set_current_screen('front');
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$frontend_output = ob_get_clean();
|
||||
|
||||
// CSS should be injected on frontend but policy may vary for admin
|
||||
// At minimum, it should work on frontend
|
||||
if (strpos($frontend_output, '<style data-care-booking>') !== false) {
|
||||
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $frontend_output,
|
||||
'CSS should be injected on frontend');
|
||||
}
|
||||
|
||||
// Admin behavior may vary based on implementation
|
||||
$this->assertTrue(true, 'CSS injection should handle admin vs frontend context appropriately');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection performance with large restriction sets
|
||||
*/
|
||||
public function test_css_injection_performance_large_dataset()
|
||||
{
|
||||
// Create many restrictions
|
||||
for ($i = 1000; $i <= 1100; $i++) {
|
||||
$this->create_test_doctor_restriction($i, true);
|
||||
}
|
||||
|
||||
for ($i = 2000; $i <= 2050; $i++) {
|
||||
$this->create_test_service_restriction($i, 1000, true);
|
||||
}
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete in reasonable time even with large datasets (under 200ms)
|
||||
$this->assertLessThan(200, $execution_time, 'CSS injection should handle large datasets efficiently');
|
||||
|
||||
// Should contain CSS for many restrictions
|
||||
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="1000"]', $head_output);
|
||||
$this->assertStringContainsString('.kivicare-service[data-service-id="2000"]', $head_output);
|
||||
|
||||
// CSS should be reasonably sized (under 100KB)
|
||||
$this->assertLessThan(100000, strlen($head_output), 'Generated CSS should be reasonably sized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection minification and optimization
|
||||
*/
|
||||
public function test_css_injection_optimization()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, true);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
$style_content = $this->extract_style_content($head_output);
|
||||
|
||||
// Should combine selectors efficiently
|
||||
$doctor_selectors = substr_count($style_content, '.kivicare-doctor');
|
||||
$this->assertGreaterThan(0, $doctor_selectors, 'Should contain doctor selectors');
|
||||
|
||||
// Should minimize redundant CSS
|
||||
$display_none_count = substr_count($style_content, 'display: none !important');
|
||||
$this->assertGreaterThan(0, $display_none_count, 'Should contain display:none declarations');
|
||||
|
||||
// Should not contain excessive whitespace if minified
|
||||
if (strpos($style_content, ' ') === false) {
|
||||
$this->assertTrue(true, 'CSS appears to be minified');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection cache invalidation
|
||||
*/
|
||||
public function test_css_injection_cache_invalidation()
|
||||
{
|
||||
// Create initial restriction
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Generate initial CSS
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$initial_output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('data-doctor-id="999"', $initial_output);
|
||||
|
||||
// Add new restriction (should invalidate cache)
|
||||
$this->create_test_doctor_restriction(998, true);
|
||||
|
||||
// Simulate cache invalidation
|
||||
delete_transient('care_booking_doctors_blocked');
|
||||
delete_transient('care_booking_restrictions_hash');
|
||||
|
||||
// Generate CSS again
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$updated_output = ob_get_clean();
|
||||
|
||||
// Should now include both doctors
|
||||
$this->assertStringContainsString('data-doctor-id="999"', $updated_output);
|
||||
$this->assertStringContainsString('data-doctor-id="998"', $updated_output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract style content from HTML
|
||||
*/
|
||||
private function extract_style_content($html)
|
||||
{
|
||||
if (preg_match('/<style[^>]*data-care-booking[^>]*>(.*?)<\/style>/s', $html, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
354
care-booking-block/tests/integration/test-doctor-filtering.php
Normal file
354
care-booking-block/tests/integration/test-doctor-filtering.php
Normal file
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for KiviCare doctor filtering
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test KiviCare doctor filtering functionality
|
||||
*/
|
||||
class Test_Doctor_Filtering extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test KiviCare doctor filter hook is registered
|
||||
*/
|
||||
public function test_doctor_filter_hook_registered()
|
||||
{
|
||||
$this->assertTrue(has_filter('kc_get_doctors_for_booking'), 'Doctor filter hook should be registered');
|
||||
|
||||
// Verify correct priority
|
||||
$priority = has_filter('kc_get_doctors_for_booking');
|
||||
$this->assertEquals(10, $priority, 'Filter should have priority 10');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering removes blocked doctors
|
||||
*/
|
||||
public function test_doctor_filtering_removes_blocked_doctors()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true); // Block doctor 999
|
||||
$this->create_test_doctor_restriction(998, false); // Don't block doctor 998
|
||||
|
||||
// Mock KiviCare doctor list
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'],
|
||||
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com']
|
||||
];
|
||||
|
||||
// Apply the filter
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
|
||||
// Verify blocked doctor was removed
|
||||
$doctor_ids = array_column($filtered_doctors, 'id');
|
||||
$this->assertNotContains(999, $doctor_ids, 'Blocked doctor should be removed from list');
|
||||
$this->assertContains(998, $doctor_ids, 'Non-blocked doctor should remain in list');
|
||||
$this->assertContains(997, $doctor_ids, 'Doctor without restriction should remain in list');
|
||||
|
||||
// Verify structure is preserved
|
||||
$this->assertCount(2, $filtered_doctors, 'Should return 2 doctors (excluding blocked one)');
|
||||
|
||||
foreach ($filtered_doctors as $doctor) {
|
||||
$this->assertArrayHasKey('id', $doctor);
|
||||
$this->assertArrayHasKey('name', $doctor);
|
||||
$this->assertArrayHasKey('email', $doctor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering preserves original array when no restrictions
|
||||
*/
|
||||
public function test_doctor_filtering_preserves_original_when_no_restrictions()
|
||||
{
|
||||
// No restrictions created
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
|
||||
];
|
||||
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
|
||||
$this->assertEquals($doctors, $filtered_doctors, 'Original array should be preserved when no restrictions');
|
||||
$this->assertCount(2, $filtered_doctors, 'All doctors should remain when no restrictions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering with empty input array
|
||||
*/
|
||||
public function test_doctor_filtering_with_empty_input()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$empty_doctors = [];
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $empty_doctors);
|
||||
|
||||
$this->assertEmpty($filtered_doctors, 'Empty input should return empty output');
|
||||
$this->assertIsArray($filtered_doctors, 'Should return array even with empty input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering with malformed input
|
||||
*/
|
||||
public function test_doctor_filtering_with_malformed_input()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Test with non-array input
|
||||
$non_array_input = "invalid_input";
|
||||
$filtered_result = apply_filters('kc_get_doctors_for_booking', $non_array_input);
|
||||
|
||||
$this->assertEquals($non_array_input, $filtered_result, 'Non-array input should be returned unchanged');
|
||||
|
||||
// Test with doctors missing required fields
|
||||
$malformed_doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva'], // Missing email
|
||||
['name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'], // Missing id
|
||||
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com'] // Complete
|
||||
];
|
||||
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $malformed_doctors);
|
||||
|
||||
// Should handle malformed entries gracefully
|
||||
$this->assertIsArray($filtered_doctors, 'Should return array even with malformed input');
|
||||
|
||||
// Complete entry should be processed correctly
|
||||
$doctor_ids = array_column($filtered_doctors, 'id');
|
||||
$this->assertNotContains(999, $doctor_ids, 'Blocked doctor should be removed even with malformed entries');
|
||||
$this->assertContains(997, $doctor_ids, 'Valid non-blocked doctor should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering uses cache for performance
|
||||
*/
|
||||
public function test_doctor_filtering_uses_cache()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, true);
|
||||
|
||||
// Manually set cache to test cache usage
|
||||
$cached_blocked_doctors = [999, 998];
|
||||
set_transient('care_booking_doctors_blocked', $cached_blocked_doctors, 3600);
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'],
|
||||
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com']
|
||||
];
|
||||
|
||||
// Apply filter multiple times to test cache efficiency
|
||||
$start_time = microtime(true);
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
}
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should be very fast with caching (under 10ms for 5 operations)
|
||||
$this->assertLessThan(10, $execution_time, 'Multiple filter operations should be fast with caching');
|
||||
|
||||
// Verify filtering worked correctly
|
||||
$doctor_ids = array_column($filtered_doctors, 'id');
|
||||
$this->assertNotContains(999, $doctor_ids);
|
||||
$this->assertNotContains(998, $doctor_ids);
|
||||
$this->assertContains(997, $doctor_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering falls back to database when cache miss
|
||||
*/
|
||||
public function test_doctor_filtering_database_fallback()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Ensure cache is clear
|
||||
delete_transient('care_booking_doctors_blocked');
|
||||
$this->assertFalse(get_transient('care_booking_doctors_blocked'), 'Cache should be empty');
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
|
||||
];
|
||||
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
|
||||
// Should still work correctly without cache
|
||||
$doctor_ids = array_column($filtered_doctors, 'id');
|
||||
$this->assertNotContains(999, $doctor_ids, 'Should filter correctly even without cache');
|
||||
$this->assertContains(998, $doctor_ids);
|
||||
|
||||
// Cache should be populated after database query
|
||||
$this->assertNotFalse(get_transient('care_booking_doctors_blocked'), 'Cache should be populated after query');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering performance with large dataset
|
||||
*/
|
||||
public function test_doctor_filtering_performance_large_dataset()
|
||||
{
|
||||
// Create multiple restrictions
|
||||
for ($i = 1000; $i <= 1050; $i++) {
|
||||
$this->create_test_doctor_restriction($i, $i % 2 === 0); // Block even IDs
|
||||
}
|
||||
|
||||
// Create large doctor dataset
|
||||
$doctors = [];
|
||||
for ($i = 1000; $i <= 1100; $i++) {
|
||||
$doctors[] = [
|
||||
'id' => $i,
|
||||
'name' => "Dr. Test $i",
|
||||
'email' => "test$i@clinic.com"
|
||||
];
|
||||
}
|
||||
|
||||
$start_time = microtime(true);
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete filtering in reasonable time (under 100ms)
|
||||
$this->assertLessThan(100, $execution_time, 'Large dataset filtering should complete under 100ms');
|
||||
|
||||
// Verify correct filtering
|
||||
$filtered_count = count($filtered_doctors);
|
||||
$this->assertGreaterThan(0, $filtered_count, 'Should return some doctors');
|
||||
$this->assertLessThan(count($doctors), $filtered_count, 'Some doctors should be filtered out');
|
||||
|
||||
// Verify no blocked doctors remain
|
||||
$filtered_ids = array_column($filtered_doctors, 'id');
|
||||
foreach ($filtered_ids as $id) {
|
||||
if ($id >= 1000 && $id <= 1050) {
|
||||
$this->assertTrue($id % 2 !== 0, "Doctor $id should not be blocked (odd IDs only)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering with concurrent filter applications
|
||||
*/
|
||||
public function test_doctor_filtering_concurrent_applications()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
|
||||
];
|
||||
|
||||
// Simulate concurrent filter applications
|
||||
$results = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$results[] = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
}
|
||||
|
||||
// All results should be identical
|
||||
$first_result = $results[0];
|
||||
foreach ($results as $result) {
|
||||
$this->assertEquals($first_result, $result, 'All concurrent applications should return identical results');
|
||||
}
|
||||
|
||||
// Verify correct filtering in all results
|
||||
foreach ($results as $result) {
|
||||
$doctor_ids = array_column($result, 'id');
|
||||
$this->assertNotContains(999, $doctor_ids);
|
||||
$this->assertContains(998, $doctor_ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering preserves array keys and structure
|
||||
*/
|
||||
public function test_doctor_filtering_preserves_structure()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$doctors = [
|
||||
'first_doctor' => ['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com', 'specialty' => 'Cardiology'],
|
||||
'second_doctor' => ['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com', 'specialty' => 'Neurology'],
|
||||
'third_doctor' => ['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com', 'specialty' => 'Pediatrics']
|
||||
];
|
||||
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
|
||||
// Should preserve associative keys
|
||||
$this->assertArrayHasKey('second_doctor', $filtered_doctors);
|
||||
$this->assertArrayHasKey('third_doctor', $filtered_doctors);
|
||||
$this->assertArrayNotHasKey('first_doctor', $filtered_doctors, 'Blocked doctor key should be removed');
|
||||
|
||||
// Should preserve all fields in remaining doctors
|
||||
$this->assertArrayHasKey('specialty', $filtered_doctors['second_doctor']);
|
||||
$this->assertEquals('Neurology', $filtered_doctors['second_doctor']['specialty']);
|
||||
$this->assertArrayHasKey('specialty', $filtered_doctors['third_doctor']);
|
||||
$this->assertEquals('Pediatrics', $filtered_doctors['third_doctor']['specialty']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering handles database errors gracefully
|
||||
*/
|
||||
public function test_doctor_filtering_handles_database_errors()
|
||||
{
|
||||
// Mock database error
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
|
||||
];
|
||||
|
||||
// Filter should handle database error gracefully
|
||||
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
// Should return original array when database error occurs
|
||||
$this->assertEquals($doctors, $filtered_doctors, 'Should return original array when database error occurs');
|
||||
|
||||
// No PHP errors should be thrown
|
||||
$this->assertTrue(true, 'Filter should handle database errors without throwing exceptions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering integration with WordPress admin vs frontend
|
||||
*/
|
||||
public function test_doctor_filtering_admin_vs_frontend_context()
|
||||
{
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
|
||||
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
|
||||
];
|
||||
|
||||
// Test in admin context (should filter)
|
||||
set_current_screen('edit-post');
|
||||
$admin_filtered = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
$admin_ids = array_column($admin_filtered, 'id');
|
||||
|
||||
// Test in frontend context (should filter)
|
||||
set_current_screen('front');
|
||||
$frontend_filtered = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
$frontend_ids = array_column($frontend_filtered, 'id');
|
||||
|
||||
// Both contexts should apply filtering
|
||||
$this->assertNotContains(999, $admin_ids, 'Admin context should filter blocked doctors');
|
||||
$this->assertNotContains(999, $frontend_ids, 'Frontend context should filter blocked doctors');
|
||||
$this->assertContains(998, $admin_ids, 'Admin context should keep non-blocked doctors');
|
||||
$this->assertContains(998, $frontend_ids, 'Frontend context should keep non-blocked doctors');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for enhanced CSS injection (T031, T033)
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test enhanced CSS injection with optimization and caching
|
||||
*/
|
||||
class Test_Enhanced_CSS_Injection extends Care_Booking_Test_Case
|
||||
{
|
||||
/**
|
||||
* Test wp_head hook has correct priority
|
||||
*/
|
||||
public function test_wp_head_hook_priority()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions');
|
||||
|
||||
// Check priority is 15 (after theme styles)
|
||||
$wp_head_callbacks = $GLOBALS['wp_filter']['wp_head']->callbacks;
|
||||
$found_css_injection = false;
|
||||
|
||||
foreach ($wp_head_callbacks as $priority => $callbacks) {
|
||||
foreach ($callbacks as $callback) {
|
||||
if (is_array($callback['function']) &&
|
||||
isset($callback['function'][0]) &&
|
||||
is_object($callback['function'][0]) &&
|
||||
method_exists($callback['function'][0], 'inject_restriction_css')) {
|
||||
$found_css_injection = true;
|
||||
$this->assertEquals(15, $priority, 'CSS injection should have priority 15');
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enhanced CSS generation with caching
|
||||
*/
|
||||
public function test_enhanced_css_generation_with_caching()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('generate_restriction_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Clear cache first
|
||||
delete_transient('care_booking_css_' . md5(serialize([[999], [['service_id' => 888, 'doctor_id' => 999]]])));
|
||||
|
||||
// First call should generate and cache CSS
|
||||
$start_time = microtime(true);
|
||||
$css1 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
|
||||
$end_time = microtime(true);
|
||||
$time1 = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Second call should use cache
|
||||
$start_time = microtime(true);
|
||||
$css2 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
|
||||
$end_time = microtime(true);
|
||||
$time2 = ($end_time - $start_time) * 1000;
|
||||
|
||||
$this->assertEquals($css1, $css2, 'Cached CSS should be identical');
|
||||
$this->assertLessThan($time1, $time2, 'Cached call should be faster');
|
||||
$this->assertLessThan(10, $time2, 'Cached call should be very fast');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enhanced CSS selectors for KiviCare 3.0+
|
||||
*/
|
||||
public function test_enhanced_css_selectors()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('generate_restriction_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$css = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
|
||||
|
||||
// Check for KiviCare 3.0+ selectors
|
||||
$this->assertStringContainsString('.kc-doctor-item[data-id="999"]', $css, 'Should contain KiviCare 3.0 doctor selector');
|
||||
$this->assertStringContainsString('.doctor-card[data-doctor="999"]', $css, 'Should contain modern doctor card selector');
|
||||
$this->assertStringContainsString('.kc-service-item[data-service="888"][data-doctor="999"]', $css, 'Should contain KiviCare 3.0 service selector');
|
||||
|
||||
// Check for form selectors
|
||||
$this->assertStringContainsString('select[name=\'doctor_id\'] option[value="999"]', $css, 'Should contain form option selector');
|
||||
$this->assertStringContainsString('.service-selection[data-doctor="999"] option[value="888"]', $css, 'Should contain contextual service selector');
|
||||
|
||||
// Check for booking form selectors
|
||||
$this->assertStringContainsString('.booking-doctor-999', $css, 'Should contain booking doctor selector');
|
||||
$this->assertStringContainsString('.appointment-service-888.doctor-999', $css, 'Should contain appointment service selector');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS chunking for performance
|
||||
*/
|
||||
public function test_css_chunking_performance()
|
||||
{
|
||||
// Create many doctor restrictions
|
||||
$doctor_ids = [];
|
||||
for ($i = 1000; $i <= 1150; $i++) {
|
||||
$this->create_test_doctor_restriction($i, true);
|
||||
$doctor_ids[] = $i;
|
||||
}
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('generate_restriction_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$css = $method->invoke($integration, $doctor_ids, []);
|
||||
|
||||
// Should contain multiple CSS rules (chunked)
|
||||
$rule_count = substr_count($css, 'display: none !important;');
|
||||
$this->assertGreaterThan(1, $rule_count, 'Should chunk selectors into multiple CSS rules');
|
||||
$this->assertLessThan(10, $rule_count, 'Should not create too many rules');
|
||||
|
||||
// CSS should be reasonably sized
|
||||
$this->assertLessThan(100000, strlen($css), 'Generated CSS should be reasonably sized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS minification in production
|
||||
*/
|
||||
public function test_css_minification()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('minify_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$verbose_css = "/* Comment */\n.test {\n display: none !important;\n visibility: hidden !important;\n}";
|
||||
$minified = $method->invoke($integration, $verbose_css);
|
||||
|
||||
$this->assertStringNotContainsString('/*', $minified, 'Comments should be removed');
|
||||
$this->assertStringNotContainsString("\n", $minified, 'Line breaks should be removed');
|
||||
$this->assertStringNotContainsString(" ", $minified, 'Multiple spaces should be removed');
|
||||
$this->assertStringContainsString('display:none!important', $minified, 'Properties should be compressed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test conditional CSS injection based on page content
|
||||
*/
|
||||
public function test_conditional_css_injection()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('should_inject_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Mock KiviCare as active
|
||||
$reflection_active = new ReflectionClass($integration);
|
||||
$active_method = $reflection_active->getMethod('is_kivicare_active');
|
||||
$active_method->setAccessible(true);
|
||||
|
||||
// Test with page that should load scripts
|
||||
global $post;
|
||||
$post = (object) ['post_content' => '[kivicare_booking]'];
|
||||
|
||||
// Should inject CSS
|
||||
$should_inject = $method->invoke($integration);
|
||||
// Note: This might be false if KiviCare is not actually active in test environment
|
||||
|
||||
// Test with page that shouldn't load scripts
|
||||
$post = (object) ['post_content' => 'Regular page content'];
|
||||
|
||||
$should_not_inject = $method->invoke($integration);
|
||||
// This test depends on KiviCare being active, so we'll just ensure method doesn't crash
|
||||
$this->assertTrue(is_bool($should_not_inject), 'should_inject_css should return boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test graceful degradation CSS classes
|
||||
*/
|
||||
public function test_graceful_degradation_css()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('generate_restriction_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$css = $method->invoke($integration, [999], []);
|
||||
|
||||
// Should contain fallback classes
|
||||
$this->assertStringContainsString('.care-booking-fallback', $css, 'Should contain fallback class');
|
||||
$this->assertStringContainsString('.care-booking-loading::after', $css, 'Should contain loading class');
|
||||
$this->assertStringContainsString('opacity: 0.7', $css, 'Should contain fallback styling');
|
||||
$this->assertStringContainsString('pointer-events: none', $css, 'Should disable pointer events for fallback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection with version attribute
|
||||
*/
|
||||
public function test_css_injection_versioning()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Capture CSS output
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
if (strpos($head_output, 'care-booking-restrictions') !== false) {
|
||||
// Should contain version attribute
|
||||
$this->assertStringContainsString('data-version="' . CARE_BOOKING_BLOCK_VERSION . '"', $head_output,
|
||||
'Should contain version attribute');
|
||||
|
||||
// Should contain proper ID
|
||||
$this->assertStringContainsString('id="care-booking-restrictions"', $head_output,
|
||||
'Should contain proper style ID');
|
||||
|
||||
// Should contain HTML comments for debugging
|
||||
$this->assertStringContainsString('<!-- Care Booking Block Styles -->', $head_output,
|
||||
'Should contain start comment');
|
||||
$this->assertStringContainsString('<!-- End Care Booking Block Styles -->', $head_output,
|
||||
'Should contain end comment');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error handling in CSS injection
|
||||
*/
|
||||
public function test_css_injection_error_handling()
|
||||
{
|
||||
// Create test restrictions first
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Force an error by mocking a database issue
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
// Clear cache to force database query
|
||||
delete_transient('care_booking_doctors_blocked');
|
||||
|
||||
// CSS injection should handle error gracefully
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
// Should not contain PHP errors
|
||||
$this->assertStringNotContainsString('Fatal error', $head_output, 'Should not contain fatal errors');
|
||||
$this->assertStringNotContainsString('Warning:', $head_output, 'Should not contain warnings');
|
||||
|
||||
// In debug mode, should contain error comment
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
$this->assertStringContainsString('CSS injection failed', $head_output,
|
||||
'Should contain error comment in debug mode');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS injection performance
|
||||
*/
|
||||
public function test_css_injection_performance()
|
||||
{
|
||||
// Create moderate number of restrictions
|
||||
for ($i = 1000; $i <= 1050; $i++) {
|
||||
$this->create_test_doctor_restriction($i, true);
|
||||
}
|
||||
|
||||
for ($i = 2000; $i <= 2025; $i++) {
|
||||
$this->create_test_service_restriction($i, 1000, true);
|
||||
}
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
do_action('wp_head');
|
||||
$head_output = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete quickly (under 200ms)
|
||||
$this->assertLessThan(200, $execution_time, 'CSS injection should be performant');
|
||||
|
||||
// Generated CSS should be reasonable size
|
||||
if (strpos($head_output, 'care-booking-restrictions') !== false) {
|
||||
$this->assertLessThan(50000, strlen($head_output), 'Generated CSS should be reasonably sized');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS cache invalidation
|
||||
*/
|
||||
public function test_css_cache_invalidation()
|
||||
{
|
||||
// Create initial restriction
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('generate_restriction_css');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Generate initial CSS
|
||||
$css1 = $method->invoke($integration, [999], []);
|
||||
$this->assertStringContainsString('999', $css1, 'Initial CSS should contain doctor 999');
|
||||
|
||||
// Add new restriction
|
||||
$this->create_test_doctor_restriction(998, true);
|
||||
|
||||
// Cache should be invalidated and new CSS should include both doctors
|
||||
$css2 = $method->invoke($integration, [999, 998], []);
|
||||
$this->assertStringContainsString('999', $css2, 'Updated CSS should contain doctor 999');
|
||||
$this->assertStringContainsString('998', $css2, 'Updated CSS should contain doctor 998');
|
||||
|
||||
$this->assertNotEquals($css1, $css2, 'CSS should be different after adding restriction');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for enhanced doctor filtering hooks (T029)
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test enhanced doctor filtering with multiple KiviCare hooks
|
||||
*/
|
||||
class Test_Enhanced_Doctor_Filtering extends Care_Booking_Test_Case
|
||||
{
|
||||
/**
|
||||
* Test multiple doctor filter hooks are registered
|
||||
*/
|
||||
public function test_multiple_doctor_hooks_registered()
|
||||
{
|
||||
$hooks_to_test = [
|
||||
'kc_get_doctors_for_booking',
|
||||
'kivicare_doctors_list',
|
||||
'kivicare_get_doctors'
|
||||
];
|
||||
|
||||
foreach ($hooks_to_test as $hook) {
|
||||
$this->assertTrue(has_filter($hook), "Hook {$hook} should be registered");
|
||||
|
||||
// Check that our callback is registered
|
||||
$callbacks = $GLOBALS['wp_filter'][$hook]->callbacks[10] ?? [];
|
||||
$found_callback = false;
|
||||
|
||||
foreach ($callbacks as $callback) {
|
||||
if (is_array($callback['function']) &&
|
||||
isset($callback['function'][0]) &&
|
||||
is_object($callback['function'][0]) &&
|
||||
method_exists($callback['function'][0], 'filter_doctors')) {
|
||||
$found_callback = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($found_callback, "filter_doctors callback should be registered for {$hook}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor filtering works with different data formats
|
||||
*/
|
||||
public function test_doctor_filtering_data_formats()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, false);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Test array format
|
||||
$doctors_array = [
|
||||
['id' => 999, 'name' => 'Dr. Blocked'],
|
||||
['id' => 998, 'name' => 'Dr. Available'],
|
||||
['id' => 997, 'name' => 'Dr. Other']
|
||||
];
|
||||
|
||||
$filtered_array = $integration->filter_doctors($doctors_array);
|
||||
|
||||
$this->assertCount(2, $filtered_array, 'Should filter out blocked doctor from array format');
|
||||
$this->assertArrayNotHasKey(0, $filtered_array, 'Blocked doctor should be removed');
|
||||
|
||||
// Test object format
|
||||
$doctor_objects = [
|
||||
(object) ['id' => 999, 'name' => 'Dr. Blocked'],
|
||||
(object) ['id' => 998, 'name' => 'Dr. Available'],
|
||||
(object) ['id' => 997, 'name' => 'Dr. Other']
|
||||
];
|
||||
|
||||
$filtered_objects = $integration->filter_doctors($doctor_objects);
|
||||
|
||||
$this->assertCount(2, $filtered_objects, 'Should filter out blocked doctor from object format');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test REST API doctor filtering
|
||||
*/
|
||||
public function test_rest_api_doctor_filtering()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock REST request
|
||||
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
|
||||
|
||||
// Mock REST response
|
||||
$response_data = [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
['id' => 999, 'name' => 'Dr. Blocked'],
|
||||
['id' => 998, 'name' => 'Dr. Available']
|
||||
]
|
||||
];
|
||||
|
||||
$response = new WP_REST_Response($response_data);
|
||||
|
||||
// Test filtering
|
||||
$result = $integration->filter_rest_api_response(false, $response, $request, null);
|
||||
|
||||
// Should return original served value (false)
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Check if response data was filtered
|
||||
$filtered_data = $response->get_data();
|
||||
$this->assertCount(1, $filtered_data['data'], 'REST API should filter blocked doctors');
|
||||
$this->assertEquals(998, $filtered_data['data'][1]['id'], 'Available doctor should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test KiviCare endpoint detection
|
||||
*/
|
||||
public function test_kivicare_endpoint_detection()
|
||||
{
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('is_kivicare_rest_endpoint');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test KiviCare endpoints
|
||||
$kivicare_request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
|
||||
$this->assertTrue($method->invoke($integration, $kivicare_request));
|
||||
|
||||
$kc_request = new WP_REST_Request('GET', '/kc/v1/services');
|
||||
$this->assertTrue($method->invoke($integration, $kc_request));
|
||||
|
||||
// Test non-KiviCare endpoint
|
||||
$other_request = new WP_REST_Request('GET', '/wp/v2/posts');
|
||||
$this->assertFalse($method->invoke($integration, $other_request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin bypass in REST API filtering
|
||||
*/
|
||||
public function test_rest_api_admin_bypass()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Set admin user
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock admin context
|
||||
set_current_screen('edit-post');
|
||||
|
||||
// Mock REST request
|
||||
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
|
||||
|
||||
$response_data = [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
['id' => 999, 'name' => 'Dr. Blocked'],
|
||||
['id' => 998, 'name' => 'Dr. Available']
|
||||
]
|
||||
];
|
||||
|
||||
$response = new WP_REST_Response($response_data);
|
||||
|
||||
// Test filtering - should bypass for admin
|
||||
$integration->filter_rest_api_response(false, $response, $request, null);
|
||||
|
||||
$filtered_data = $response->get_data();
|
||||
$this->assertCount(2, $filtered_data['data'], 'Admin should see all doctors in REST API');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error handling in REST API filtering
|
||||
*/
|
||||
public function test_rest_api_error_handling()
|
||||
{
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock malformed request
|
||||
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
|
||||
|
||||
// Mock malformed response
|
||||
$response = new WP_REST_Response(null);
|
||||
|
||||
// Should not throw errors
|
||||
$result = $integration->filter_rest_api_response(false, $response, $request, null);
|
||||
|
||||
$this->assertFalse($result, 'Should handle malformed responses gracefully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test caching works with multiple hooks
|
||||
*/
|
||||
public function test_caching_with_multiple_hooks()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Pre-populate cache
|
||||
set_transient('care_booking_doctors_blocked', [999], 3600);
|
||||
|
||||
$doctors = [
|
||||
['id' => 999, 'name' => 'Dr. Blocked'],
|
||||
['id' => 998, 'name' => 'Dr. Available']
|
||||
];
|
||||
|
||||
// Test multiple hook calls use same cache
|
||||
$start_time = microtime(true);
|
||||
|
||||
$filtered1 = apply_filters('kc_get_doctors_for_booking', $doctors);
|
||||
$filtered2 = apply_filters('kivicare_doctors_list', $doctors);
|
||||
$filtered3 = apply_filters('kivicare_get_doctors', $doctors);
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should be very fast with cache (under 50ms for all three calls)
|
||||
$this->assertLessThan(50, $execution_time, 'Multiple hook calls should use cached data');
|
||||
|
||||
// All should return same filtered results
|
||||
$this->assertEquals($filtered1, $filtered2, 'All hooks should return same results');
|
||||
$this->assertEquals($filtered2, $filtered3, 'All hooks should return same results');
|
||||
|
||||
// Should filter blocked doctor
|
||||
$this->assertCount(1, $filtered1, 'Should filter blocked doctor');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test performance with large doctor datasets
|
||||
*/
|
||||
public function test_performance_large_doctor_dataset()
|
||||
{
|
||||
// Create many restrictions
|
||||
for ($i = 1000; $i <= 1100; $i++) {
|
||||
$this->create_test_doctor_restriction($i, true);
|
||||
}
|
||||
|
||||
// Create large doctor dataset
|
||||
$doctors = [];
|
||||
for ($i = 900; $i <= 1200; $i++) {
|
||||
$doctors[] = ['id' => $i, 'name' => "Dr. Test {$i}"];
|
||||
}
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
$start_time = microtime(true);
|
||||
$filtered = $integration->filter_doctors($doctors);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete in reasonable time (under 100ms)
|
||||
$this->assertLessThan(100, $execution_time, 'Should handle large datasets efficiently');
|
||||
|
||||
// Should filter out blocked doctors (1000-1100)
|
||||
$this->assertLessThanOrEqual(200, count($filtered), 'Should filter out blocked doctors');
|
||||
|
||||
// Available doctors (900-999, 1101-1200) should remain
|
||||
$this->assertGreaterThanOrEqual(200, count($filtered), 'Available doctors should remain');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for enhanced service filtering hooks (T030)
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test enhanced service filtering with multiple KiviCare hooks
|
||||
*/
|
||||
class Test_Enhanced_Service_Filtering extends Care_Booking_Test_Case
|
||||
{
|
||||
/**
|
||||
* Test multiple service filter hooks are registered
|
||||
*/
|
||||
public function test_multiple_service_hooks_registered()
|
||||
{
|
||||
$hooks_to_test = [
|
||||
'kc_get_services_by_doctor',
|
||||
'kivicare_services_list',
|
||||
'kivicare_get_services'
|
||||
];
|
||||
|
||||
foreach ($hooks_to_test as $hook) {
|
||||
$this->assertTrue(has_filter($hook), "Hook {$hook} should be registered");
|
||||
|
||||
// Check that our callback is registered
|
||||
$callbacks = $GLOBALS['wp_filter'][$hook]->callbacks[10] ?? [];
|
||||
$found_callback = false;
|
||||
|
||||
foreach ($callbacks as $callback) {
|
||||
if (is_array($callback['function']) &&
|
||||
isset($callback['function'][0]) &&
|
||||
is_object($callback['function'][0]) &&
|
||||
method_exists($callback['function'][0], 'filter_services')) {
|
||||
$found_callback = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($found_callback, "filter_services callback should be registered for {$hook}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering without doctor ID extraction
|
||||
*/
|
||||
public function test_service_filtering_with_context_extraction()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
$this->create_test_service_restriction(887, 999, false);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Test services with doctor_id in array
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999],
|
||||
['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998]
|
||||
];
|
||||
|
||||
$filtered = $integration->filter_services($services);
|
||||
|
||||
$this->assertCount(2, $filtered, 'Should filter out blocked service');
|
||||
|
||||
// Check remaining services
|
||||
$remaining_ids = array_column($filtered, 'id');
|
||||
$this->assertNotContains(888, $remaining_ids, 'Blocked service should be filtered');
|
||||
$this->assertContains(887, $remaining_ids, 'Available service should remain');
|
||||
$this->assertContains(886, $remaining_ids, 'Service for different doctor should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test doctor ID extraction from URL parameters
|
||||
*/
|
||||
public function test_doctor_id_extraction_from_url()
|
||||
{
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('extract_doctor_id_from_context');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test extraction from GET parameters
|
||||
$_GET['doctor_id'] = '999';
|
||||
|
||||
$result = $method->invoke($integration, []);
|
||||
$this->assertEquals(999, $result, 'Should extract doctor ID from GET parameters');
|
||||
|
||||
unset($_GET['doctor_id']);
|
||||
|
||||
// Test extraction from POST parameters
|
||||
$_POST['doctor_id'] = '998';
|
||||
|
||||
$result = $method->invoke($integration, []);
|
||||
$this->assertEquals(998, $result, 'Should extract doctor ID from POST parameters');
|
||||
|
||||
unset($_POST['doctor_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with blocked doctors
|
||||
*/
|
||||
public function test_service_filtering_with_blocked_doctors()
|
||||
{
|
||||
// Create blocked doctor and service restrictions
|
||||
$this->create_test_doctor_restriction(999, true); // Block doctor 999
|
||||
$this->create_test_service_restriction(888, 998, true); // Block service 888 for doctor 998
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999], // Should be filtered (blocked doctor)
|
||||
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999], // Should be filtered (blocked doctor)
|
||||
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 998], // Should be filtered (blocked service)
|
||||
['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998] // Should remain
|
||||
];
|
||||
|
||||
$filtered = $integration->filter_services($services);
|
||||
|
||||
$this->assertCount(1, $filtered, 'Should filter services from blocked doctor and blocked services');
|
||||
|
||||
$remaining_service = reset($filtered);
|
||||
$this->assertEquals(886, $remaining_service['id'], 'Only non-blocked service should remain');
|
||||
$this->assertEquals(998, $remaining_service['doctor_id'], 'Service should be for non-blocked doctor');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test REST API service filtering
|
||||
*/
|
||||
public function test_rest_api_service_filtering()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock REST request with doctor_id parameter
|
||||
$request = new WP_REST_Request('GET', '/kivicare/v1/services');
|
||||
$request->set_param('doctor_id', 999);
|
||||
|
||||
// Mock REST response
|
||||
$response_data = [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
['id' => 888, 'name' => 'Blocked Service'],
|
||||
['id' => 887, 'name' => 'Available Service']
|
||||
]
|
||||
];
|
||||
|
||||
$response = new WP_REST_Response($response_data);
|
||||
|
||||
// Test filtering
|
||||
$result = $integration->filter_rest_api_response(false, $response, $request, null);
|
||||
|
||||
// Should return original served value (false)
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Check if response data was filtered
|
||||
$filtered_data = $response->get_data();
|
||||
$this->assertCount(1, $filtered_data['data'], 'REST API should filter blocked services');
|
||||
$this->assertEquals(887, $filtered_data['data'][1]['id'], 'Available service should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with object format
|
||||
*/
|
||||
public function test_service_filtering_object_format()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Test object format
|
||||
$services = [
|
||||
(object) ['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
|
||||
(object) ['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999],
|
||||
(object) ['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998]
|
||||
];
|
||||
|
||||
$filtered = $integration->filter_services($services, 999);
|
||||
|
||||
$this->assertCount(2, $filtered, 'Should filter out blocked service from object format');
|
||||
|
||||
// Check that blocked service is not present
|
||||
$remaining_ids = [];
|
||||
foreach ($filtered as $service) {
|
||||
$remaining_ids[] = $service->id;
|
||||
}
|
||||
|
||||
$this->assertNotContains(888, $remaining_ids, 'Blocked service should be filtered from objects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with empty or invalid input
|
||||
*/
|
||||
public function test_service_filtering_invalid_input()
|
||||
{
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Test with non-array input
|
||||
$result = $integration->filter_services('not an array');
|
||||
$this->assertEquals('not an array', $result, 'Should return original input if not array');
|
||||
|
||||
$result = $integration->filter_services(null);
|
||||
$this->assertNull($result, 'Should return null input unchanged');
|
||||
|
||||
// Test with empty array
|
||||
$result = $integration->filter_services([]);
|
||||
$this->assertEquals([], $result, 'Should return empty array unchanged');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin bypass in service filtering
|
||||
*/
|
||||
public function test_service_filtering_admin_bypass()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Set admin user
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock admin context
|
||||
set_current_screen('edit-post');
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
$filtered = $integration->filter_services($services, 999);
|
||||
|
||||
$this->assertCount(2, $filtered, 'Admin should see all services including blocked ones');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test performance with large service datasets
|
||||
*/
|
||||
public function test_performance_large_service_dataset()
|
||||
{
|
||||
// Create many restrictions
|
||||
for ($i = 2000; $i <= 2050; $i++) {
|
||||
$this->create_test_service_restriction($i, 1000, true);
|
||||
}
|
||||
|
||||
// Create large service dataset
|
||||
$services = [];
|
||||
for ($i = 1900; $i <= 2100; $i++) {
|
||||
$services[] = ['id' => $i, 'name' => "Service {$i}", 'doctor_id' => 1000];
|
||||
}
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
$start_time = microtime(true);
|
||||
$filtered = $integration->filter_services($services, 1000);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete in reasonable time (under 100ms)
|
||||
$this->assertLessThan(100, $execution_time, 'Should handle large service datasets efficiently');
|
||||
|
||||
// Should filter out blocked services (2000-2050)
|
||||
$this->assertLessThanOrEqual(150, count($filtered), 'Should filter out blocked services');
|
||||
|
||||
// Available services (1900-1999, 2051-2100) should remain
|
||||
$this->assertGreaterThanOrEqual(150, count($filtered), 'Available services should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with mixed data formats
|
||||
*/
|
||||
public function test_service_filtering_mixed_formats()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mixed array and object format (edge case)
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999], // Array - blocked
|
||||
(object) ['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999], // Object - available
|
||||
['id' => 886] // Array missing doctor_id
|
||||
];
|
||||
|
||||
$filtered = $integration->filter_services($services, 999);
|
||||
|
||||
// Should handle mixed formats gracefully
|
||||
$this->assertGreaterThan(0, count($filtered), 'Should handle mixed formats');
|
||||
|
||||
// Blocked service should be removed
|
||||
$has_blocked_service = false;
|
||||
foreach ($filtered as $service) {
|
||||
$service_id = is_array($service) ? ($service['id'] ?? 0) : ($service->id ?? 0);
|
||||
if ($service_id == 888) {
|
||||
$has_blocked_service = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertFalse($has_blocked_service, 'Blocked service should not be in filtered results');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test caching with service filtering
|
||||
*/
|
||||
public function test_service_filtering_caching()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Pre-populate cache
|
||||
set_transient('care_booking_services_blocked_999', [888], 3600);
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Service 1'],
|
||||
['id' => 887, 'name' => 'Service 2']
|
||||
];
|
||||
|
||||
$start_time = microtime(true);
|
||||
$filtered = $integration->filter_services($services, 999);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should be very fast with cache
|
||||
$this->assertLessThan(50, $execution_time, 'Service filtering should be fast with cache');
|
||||
|
||||
// Should filter blocked service
|
||||
$this->assertCount(1, $filtered, 'Should filter blocked service using cache');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for frontend JavaScript graceful degradation (T032)
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test frontend JavaScript enqueuing and configuration
|
||||
*/
|
||||
class Test_Frontend_JavaScript extends Care_Booking_Test_Case
|
||||
{
|
||||
/**
|
||||
* Test frontend scripts are enqueued on appropriate pages
|
||||
*/
|
||||
public function test_frontend_scripts_enqueued()
|
||||
{
|
||||
// Mock KiviCare as active
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare_booking]'
|
||||
];
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// Check if script is registered
|
||||
$this->assertTrue(wp_script_is('care-booking-frontend', 'registered'),
|
||||
'Frontend script should be registered');
|
||||
|
||||
// Note: wp_script_is('enqueued') might not work in test environment
|
||||
// as it depends on KiviCare being active and page content detection
|
||||
}
|
||||
|
||||
/**
|
||||
* Test script localization with correct configuration
|
||||
*/
|
||||
public function test_script_localization()
|
||||
{
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock wp_localize_script to capture data
|
||||
$localized_data = null;
|
||||
|
||||
add_filter('wp_localize_script_care-booking-frontend_careBookingConfig', function($data) use (&$localized_data) {
|
||||
$localized_data = $data;
|
||||
return $data;
|
||||
});
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// If script was enqueued and localized, check the data
|
||||
if ($localized_data) {
|
||||
$this->assertArrayHasKey('ajaxurl', $localized_data, 'Should include AJAX URL');
|
||||
$this->assertArrayHasKey('nonce', $localized_data, 'Should include nonce');
|
||||
$this->assertArrayHasKey('debug', $localized_data, 'Should include debug flag');
|
||||
$this->assertArrayHasKey('fallbackEnabled', $localized_data, 'Should include fallback flag');
|
||||
$this->assertArrayHasKey('retryAttempts', $localized_data, 'Should include retry attempts');
|
||||
$this->assertArrayHasKey('selectors', $localized_data, 'Should include selectors');
|
||||
|
||||
// Check selectors structure
|
||||
$this->assertArrayHasKey('doctors', $localized_data['selectors'], 'Should include doctor selectors');
|
||||
$this->assertArrayHasKey('services', $localized_data['selectors'], 'Should include service selectors');
|
||||
$this->assertArrayHasKey('forms', $localized_data['selectors'], 'Should include form selectors');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test should_load_frontend_scripts logic
|
||||
*/
|
||||
public function test_should_load_frontend_scripts_logic()
|
||||
{
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Use reflection to access private method
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('should_load_frontend_scripts');
|
||||
$method->setAccessible(true);
|
||||
|
||||
global $post;
|
||||
|
||||
// Test with KiviCare shortcode
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => 'Some content [kivicare] more content'
|
||||
];
|
||||
|
||||
$should_load = $method->invoke($integration);
|
||||
$this->assertTrue($should_load, 'Should load scripts on pages with kivicare shortcode');
|
||||
|
||||
// Test with KiviCare block
|
||||
$post = (object) [
|
||||
'ID' => 124,
|
||||
'post_content' => '<!-- wp:kivicare/booking --><div>Booking form</div><!-- /wp:kivicare/booking -->'
|
||||
];
|
||||
|
||||
$should_load = $method->invoke($integration);
|
||||
$this->assertTrue($should_load, 'Should load scripts on pages with kivicare block');
|
||||
|
||||
// Test with regular content
|
||||
$post = (object) [
|
||||
'ID' => 125,
|
||||
'post_content' => 'Regular page content without KiviCare'
|
||||
];
|
||||
|
||||
$should_load = $method->invoke($integration);
|
||||
$this->assertFalse($should_load, 'Should not load scripts on regular pages');
|
||||
|
||||
// Test with URL parameters
|
||||
$_GET['kivicare'] = '1';
|
||||
$should_load = $method->invoke($integration);
|
||||
$this->assertTrue($should_load, 'Should load scripts when URL contains kivicare parameter');
|
||||
unset($_GET['kivicare']);
|
||||
|
||||
$_GET['booking'] = '1';
|
||||
$should_load = $method->invoke($integration);
|
||||
$this->assertTrue($should_load, 'Should load scripts when URL contains booking parameter');
|
||||
unset($_GET['booking']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test frontend script file exists and has correct content structure
|
||||
*/
|
||||
public function test_frontend_script_file_structure()
|
||||
{
|
||||
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
|
||||
|
||||
$this->assertFileExists($script_path, 'Frontend JavaScript file should exist');
|
||||
|
||||
$script_content = file_get_contents($script_path);
|
||||
|
||||
// Check for main structure
|
||||
$this->assertStringContainsString('CareBooking', $script_content,
|
||||
'Should contain CareBooking object');
|
||||
$this->assertStringContainsString('init:', $script_content,
|
||||
'Should contain init method');
|
||||
$this->assertStringContainsString('setupObservers:', $script_content,
|
||||
'Should contain setupObservers method');
|
||||
$this->assertStringContainsString('enhanceExistingElements:', $script_content,
|
||||
'Should contain enhanceExistingElements method');
|
||||
|
||||
// Check for graceful degradation features
|
||||
$this->assertStringContainsString('setupFallbacks:', $script_content,
|
||||
'Should contain setupFallbacks method');
|
||||
$this->assertStringContainsString('MutationObserver', $script_content,
|
||||
'Should use MutationObserver for dynamic content');
|
||||
$this->assertStringContainsString('ajaxError', $script_content,
|
||||
'Should handle AJAX errors');
|
||||
|
||||
// Check for validation features
|
||||
$this->assertStringContainsString('validateBookingForm:', $script_content,
|
||||
'Should contain form validation');
|
||||
$this->assertStringContainsString('showLoadingState:', $script_content,
|
||||
'Should contain loading state management');
|
||||
|
||||
// Check for offline detection
|
||||
$this->assertStringContainsString('online offline', $script_content,
|
||||
'Should handle online/offline events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test script dependencies are correct
|
||||
*/
|
||||
public function test_script_dependencies()
|
||||
{
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// Check if jQuery is a dependency
|
||||
global $wp_scripts;
|
||||
|
||||
if (isset($wp_scripts->registered['care-booking-frontend'])) {
|
||||
$script = $wp_scripts->registered['care-booking-frontend'];
|
||||
$this->assertContains('jquery', $script->deps, 'Should depend on jQuery');
|
||||
$this->assertEquals(CARE_BOOKING_BLOCK_VERSION, $script->ver, 'Should use plugin version');
|
||||
$this->assertTrue($script->extra['in_footer'], 'Should load in footer');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test script configuration values
|
||||
*/
|
||||
public function test_script_configuration_values()
|
||||
{
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Capture localized data
|
||||
$captured_data = null;
|
||||
|
||||
// Mock wp_localize_script
|
||||
add_filter('wp_scripts_print_extra_script', function($output, $handle) use (&$captured_data) {
|
||||
if ($handle === 'care-booking-frontend') {
|
||||
// Extract config from JavaScript
|
||||
if (preg_match('/careBookingConfig\s*=\s*({.+?});/', $output, $matches)) {
|
||||
$captured_data = json_decode($matches[1], true);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}, 10, 2);
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// If we captured data, validate it
|
||||
if ($captured_data) {
|
||||
// Check AJAX URL
|
||||
$this->assertStringContainsString('admin-ajax.php', $captured_data['ajaxurl'],
|
||||
'AJAX URL should point to admin-ajax.php');
|
||||
|
||||
// Check nonce is valid
|
||||
$this->assertTrue(wp_verify_nonce($captured_data['nonce'], 'care_booking_frontend'),
|
||||
'Nonce should be valid');
|
||||
|
||||
// Check boolean values
|
||||
$this->assertIsBool($captured_data['debug'], 'Debug should be boolean');
|
||||
$this->assertIsBool($captured_data['fallbackEnabled'], 'Fallback should be boolean');
|
||||
|
||||
// Check numeric values
|
||||
$this->assertIsNumeric($captured_data['retryAttempts'], 'Retry attempts should be numeric');
|
||||
$this->assertIsNumeric($captured_data['retryDelay'], 'Retry delay should be numeric');
|
||||
$this->assertGreaterThan(0, $captured_data['retryAttempts'], 'Should have positive retry attempts');
|
||||
$this->assertGreaterThan(0, $captured_data['retryDelay'], 'Should have positive retry delay');
|
||||
|
||||
// Check selectors
|
||||
$this->assertIsArray($captured_data['selectors'], 'Selectors should be array');
|
||||
$this->assertArrayHasKey('doctors', $captured_data['selectors']);
|
||||
$this->assertArrayHasKey('services', $captured_data['selectors']);
|
||||
$this->assertArrayHasKey('forms', $captured_data['selectors']);
|
||||
|
||||
// Check selector format
|
||||
$this->assertStringContainsString('.kivicare-doctor', $captured_data['selectors']['doctors'],
|
||||
'Doctor selectors should include .kivicare-doctor');
|
||||
$this->assertStringContainsString('.kivicare-service', $captured_data['selectors']['services'],
|
||||
'Service selectors should include .kivicare-service');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test script only loads when KiviCare is active
|
||||
*/
|
||||
public function test_script_kivicare_dependency()
|
||||
{
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
$integration = $this->plugin->kivicare_integration;
|
||||
|
||||
// Mock KiviCare as inactive
|
||||
$reflection = new ReflectionClass($integration);
|
||||
$method = $reflection->getMethod('is_kivicare_active');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// The method checks for actual plugin files, so in test environment
|
||||
// it will likely return false, which is correct behavior
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// Script should not be enqueued if KiviCare is not active
|
||||
// This is mainly to test the logic path doesn't cause errors
|
||||
$this->assertTrue(true, 'Script enqueuing should handle inactive KiviCare gracefully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin area script exclusion
|
||||
*/
|
||||
public function test_admin_area_script_exclusion()
|
||||
{
|
||||
// Mock admin area
|
||||
set_current_screen('edit-post');
|
||||
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
// Trigger script enqueuing
|
||||
do_action('wp_enqueue_scripts');
|
||||
|
||||
// Script should not be enqueued in admin area
|
||||
$this->assertFalse(wp_script_is('care-booking-frontend', 'enqueued'),
|
||||
'Frontend script should not be enqueued in admin area');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test script file is minified in production
|
||||
*/
|
||||
public function test_script_optimization()
|
||||
{
|
||||
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
|
||||
$script_content = file_get_contents($script_path);
|
||||
|
||||
// In development, script should be readable
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
$this->assertGreaterThan(100, substr_count($script_content, "\n"),
|
||||
'Development script should have line breaks');
|
||||
$this->assertStringContainsString(' ', $script_content,
|
||||
'Development script should have indentation');
|
||||
}
|
||||
|
||||
// Script should have proper structure regardless of minification
|
||||
$this->assertStringContainsString('CareBooking', $script_content,
|
||||
'Script should contain CareBooking object');
|
||||
$this->assertStringContainsString('jQuery', $script_content,
|
||||
'Script should reference jQuery');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error handling in script enqueuing
|
||||
*/
|
||||
public function test_script_enqueuing_error_handling()
|
||||
{
|
||||
global $post;
|
||||
$post = (object) [
|
||||
'ID' => 123,
|
||||
'post_content' => '[kivicare]'
|
||||
];
|
||||
|
||||
// Mock file not found scenario by temporarily renaming the file
|
||||
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
|
||||
$temp_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js.temp';
|
||||
|
||||
if (file_exists($script_path)) {
|
||||
rename($script_path, $temp_path);
|
||||
}
|
||||
|
||||
// Trigger script enqueuing - should not cause fatal errors
|
||||
ob_start();
|
||||
do_action('wp_enqueue_scripts');
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Restore file
|
||||
if (file_exists($temp_path)) {
|
||||
rename($temp_path, $script_path);
|
||||
}
|
||||
|
||||
// Should not produce PHP errors
|
||||
$this->assertStringNotContainsString('Fatal error', $output,
|
||||
'Should handle missing script file gracefully');
|
||||
$this->assertStringNotContainsString('Warning:', $output,
|
||||
'Should handle missing script file without warnings');
|
||||
}
|
||||
}
|
||||
425
care-booking-block/tests/integration/test-service-filtering.php
Normal file
425
care-booking-block/tests/integration/test-service-filtering.php
Normal file
@@ -0,0 +1,425 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Integration test for KiviCare service filtering
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test KiviCare service filtering functionality
|
||||
*/
|
||||
class Test_Service_Filtering extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test KiviCare service filter hook is registered
|
||||
*/
|
||||
public function test_service_filter_hook_registered()
|
||||
{
|
||||
$this->assertTrue(has_filter('kc_get_services_by_doctor'), 'Service filter hook should be registered');
|
||||
|
||||
// Verify correct priority
|
||||
$priority = has_filter('kc_get_services_by_doctor');
|
||||
$this->assertEquals(10, $priority, 'Filter should have priority 10');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering removes blocked services for specific doctor
|
||||
*/
|
||||
public function test_service_filtering_removes_blocked_services()
|
||||
{
|
||||
// Create service restrictions for doctor 999
|
||||
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
|
||||
$this->create_test_service_restriction(887, 999, false); // Don't block service 887 for doctor 999
|
||||
$this->create_test_service_restriction(886, 998, true); // Block service 886 for different doctor 998
|
||||
|
||||
// Mock KiviCare service list for doctor 999
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999],
|
||||
['id' => 885, 'name' => 'Exame Rotina', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
$doctor_id = 999;
|
||||
|
||||
// Apply the filter
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, $doctor_id);
|
||||
|
||||
// Verify blocked service was removed for this doctor
|
||||
$service_ids = array_column($filtered_services, 'id');
|
||||
$this->assertNotContains(888, $service_ids, 'Blocked service should be removed for this doctor');
|
||||
$this->assertContains(887, $service_ids, 'Non-blocked service should remain');
|
||||
$this->assertContains(885, $service_ids, 'Service without restriction should remain');
|
||||
|
||||
// Verify structure is preserved
|
||||
$this->assertCount(2, $filtered_services, 'Should return 2 services (excluding blocked one)');
|
||||
|
||||
foreach ($filtered_services as $service) {
|
||||
$this->assertArrayHasKey('id', $service);
|
||||
$this->assertArrayHasKey('name', $service);
|
||||
$this->assertArrayHasKey('doctor_id', $service);
|
||||
$this->assertEquals(999, $service['doctor_id'], 'All services should belong to doctor 999');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering is doctor-specific
|
||||
*/
|
||||
public function test_service_filtering_is_doctor_specific()
|
||||
{
|
||||
// Create service restrictions for different doctors
|
||||
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
|
||||
$this->create_test_service_restriction(888, 998, false); // Don't block service 888 for doctor 998
|
||||
|
||||
$services_doctor_999 = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
$services_doctor_998 = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
|
||||
];
|
||||
|
||||
// Filter for doctor 999 (service 888 should be blocked)
|
||||
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services_doctor_999, 999);
|
||||
$service_ids_999 = array_column($filtered_999, 'id');
|
||||
$this->assertNotContains(888, $service_ids_999, 'Service 888 should be blocked for doctor 999');
|
||||
$this->assertContains(887, $service_ids_999, 'Service 887 should remain for doctor 999');
|
||||
|
||||
// Filter for doctor 998 (service 888 should NOT be blocked)
|
||||
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_doctor_998, 998);
|
||||
$service_ids_998 = array_column($filtered_998, 'id');
|
||||
$this->assertContains(888, $service_ids_998, 'Service 888 should NOT be blocked for doctor 998');
|
||||
$this->assertContains(887, $service_ids_998, 'Service 887 should remain for doctor 998');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering preserves original array when no restrictions
|
||||
*/
|
||||
public function test_service_filtering_preserves_original_when_no_restrictions()
|
||||
{
|
||||
// No restrictions created for doctor 999
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
|
||||
$this->assertEquals($services, $filtered_services, 'Original array should be preserved when no restrictions');
|
||||
$this->assertCount(2, $filtered_services, 'All services should remain when no restrictions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with empty input array
|
||||
*/
|
||||
public function test_service_filtering_with_empty_input()
|
||||
{
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$empty_services = [];
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $empty_services, 999);
|
||||
|
||||
$this->assertEmpty($filtered_services, 'Empty input should return empty output');
|
||||
$this->assertIsArray($filtered_services, 'Should return array even with empty input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with missing doctor_id parameter
|
||||
*/
|
||||
public function test_service_filtering_with_missing_doctor_id()
|
||||
{
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
// Call filter without doctor_id parameter
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services);
|
||||
|
||||
// Should return original array when doctor_id is missing
|
||||
$this->assertEquals($services, $filtered_services, 'Should return original array when doctor_id is missing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with malformed input
|
||||
*/
|
||||
public function test_service_filtering_with_malformed_input()
|
||||
{
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Test with non-array input
|
||||
$non_array_input = "invalid_input";
|
||||
$filtered_result = apply_filters('kc_get_services_by_doctor', $non_array_input, 999);
|
||||
|
||||
$this->assertEquals($non_array_input, $filtered_result, 'Non-array input should be returned unchanged');
|
||||
|
||||
// Test with services missing required fields
|
||||
$malformed_services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral'], // Missing doctor_id
|
||||
['name' => 'Revisão', 'doctor_id' => 999], // Missing id
|
||||
['id' => 885, 'name' => 'Exame Rotina', 'doctor_id' => 999] // Complete
|
||||
];
|
||||
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $malformed_services, 999);
|
||||
|
||||
// Should handle malformed entries gracefully
|
||||
$this->assertIsArray($filtered_services, 'Should return array even with malformed input');
|
||||
|
||||
// Complete entry should be processed correctly
|
||||
$service_ids = array_column($filtered_services, 'id');
|
||||
$this->assertNotContains(888, $service_ids, 'Blocked service should be removed even with malformed entries');
|
||||
$this->assertContains(885, $service_ids, 'Valid non-blocked service should remain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering uses cache for performance
|
||||
*/
|
||||
public function test_service_filtering_uses_cache()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
$this->create_test_service_restriction(887, 999, true);
|
||||
|
||||
// Manually set cache to test cache usage
|
||||
$cached_blocked_services = [888, 887];
|
||||
set_transient('care_booking_services_blocked_999', $cached_blocked_services, 3600);
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999],
|
||||
['id' => 886, 'name' => 'Exame Rotina', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
// Apply filter multiple times to test cache efficiency
|
||||
$start_time = microtime(true);
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
}
|
||||
|
||||
$end_time = microtime(true);
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should be very fast with caching (under 10ms for 5 operations)
|
||||
$this->assertLessThan(10, $execution_time, 'Multiple filter operations should be fast with caching');
|
||||
|
||||
// Verify filtering worked correctly
|
||||
$service_ids = array_column($filtered_services, 'id');
|
||||
$this->assertNotContains(888, $service_ids);
|
||||
$this->assertNotContains(887, $service_ids);
|
||||
$this->assertContains(886, $service_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering falls back to database when cache miss
|
||||
*/
|
||||
public function test_service_filtering_database_fallback()
|
||||
{
|
||||
// Create test restrictions
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Ensure cache is clear
|
||||
delete_transient('care_booking_services_blocked_999');
|
||||
$this->assertFalse(get_transient('care_booking_services_blocked_999'), 'Cache should be empty');
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
|
||||
// Should still work correctly without cache
|
||||
$service_ids = array_column($filtered_services, 'id');
|
||||
$this->assertNotContains(888, $service_ids, 'Should filter correctly even without cache');
|
||||
$this->assertContains(887, $service_ids);
|
||||
|
||||
// Cache should be populated after database query
|
||||
$this->assertNotFalse(get_transient('care_booking_services_blocked_999'), 'Cache should be populated after query');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering performance with large dataset
|
||||
*/
|
||||
public function test_service_filtering_performance_large_dataset()
|
||||
{
|
||||
$doctor_id = 999;
|
||||
|
||||
// Create multiple restrictions for this doctor
|
||||
for ($i = 1000; $i <= 1050; $i++) {
|
||||
$this->create_test_service_restriction($i, $doctor_id, $i % 2 === 0); // Block even IDs
|
||||
}
|
||||
|
||||
// Create large service dataset
|
||||
$services = [];
|
||||
for ($i = 1000; $i <= 1100; $i++) {
|
||||
$services[] = [
|
||||
'id' => $i,
|
||||
'name' => "Service Test $i",
|
||||
'doctor_id' => $doctor_id
|
||||
];
|
||||
}
|
||||
|
||||
$start_time = microtime(true);
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, $doctor_id);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
// Should complete filtering in reasonable time (under 100ms)
|
||||
$this->assertLessThan(100, $execution_time, 'Large dataset filtering should complete under 100ms');
|
||||
|
||||
// Verify correct filtering
|
||||
$filtered_count = count($filtered_services);
|
||||
$this->assertGreaterThan(0, $filtered_count, 'Should return some services');
|
||||
$this->assertLessThan(count($services), $filtered_count, 'Some services should be filtered out');
|
||||
|
||||
// Verify no blocked services remain
|
||||
$filtered_ids = array_column($filtered_services, 'id');
|
||||
foreach ($filtered_ids as $id) {
|
||||
if ($id >= 1000 && $id <= 1050) {
|
||||
$this->assertTrue($id % 2 !== 0, "Service $id should not be blocked (odd IDs only)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering with multiple doctors simultaneously
|
||||
*/
|
||||
public function test_service_filtering_multiple_doctors()
|
||||
{
|
||||
// Create different restrictions for different doctors
|
||||
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
|
||||
$this->create_test_service_restriction(888, 998, false); // Don't block service 888 for doctor 998
|
||||
$this->create_test_service_restriction(887, 999, false); // Don't block service 887 for doctor 999
|
||||
$this->create_test_service_restriction(887, 998, true); // Block service 887 for doctor 998
|
||||
|
||||
$services_both = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
// Test filtering for doctor 999
|
||||
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services_both, 999);
|
||||
$ids_999 = array_column($filtered_999, 'id');
|
||||
$this->assertNotContains(888, $ids_999, 'Service 888 blocked for doctor 999');
|
||||
$this->assertContains(887, $ids_999, 'Service 887 allowed for doctor 999');
|
||||
|
||||
// Change services to belong to doctor 998
|
||||
$services_998 = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
|
||||
];
|
||||
|
||||
// Test filtering for doctor 998
|
||||
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_998, 998);
|
||||
$ids_998 = array_column($filtered_998, 'id');
|
||||
$this->assertContains(888, $ids_998, 'Service 888 allowed for doctor 998');
|
||||
$this->assertNotContains(887, $ids_998, 'Service 887 blocked for doctor 998');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering preserves array keys and structure
|
||||
*/
|
||||
public function test_service_filtering_preserves_structure()
|
||||
{
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$services = [
|
||||
'first_service' => ['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999, 'duration' => 30],
|
||||
'second_service' => ['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999, 'duration' => 15],
|
||||
'third_service' => ['id' => 886, 'name' => 'Exame Rotina', 'doctor_id' => 999, 'duration' => 45]
|
||||
];
|
||||
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
|
||||
// Should preserve associative keys
|
||||
$this->assertArrayHasKey('second_service', $filtered_services);
|
||||
$this->assertArrayHasKey('third_service', $filtered_services);
|
||||
$this->assertArrayNotHasKey('first_service', $filtered_services, 'Blocked service key should be removed');
|
||||
|
||||
// Should preserve all fields in remaining services
|
||||
$this->assertArrayHasKey('duration', $filtered_services['second_service']);
|
||||
$this->assertEquals(15, $filtered_services['second_service']['duration']);
|
||||
$this->assertArrayHasKey('duration', $filtered_services['third_service']);
|
||||
$this->assertEquals(45, $filtered_services['third_service']['duration']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering handles database errors gracefully
|
||||
*/
|
||||
public function test_service_filtering_handles_database_errors()
|
||||
{
|
||||
// Mock database error
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
// Filter should handle database error gracefully
|
||||
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
// Should return original array when database error occurs
|
||||
$this->assertEquals($services, $filtered_services, 'Should return original array when database error occurs');
|
||||
|
||||
// No PHP errors should be thrown
|
||||
$this->assertTrue(true, 'Filter should handle database errors without throwing exceptions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service filtering cache isolation between doctors
|
||||
*/
|
||||
public function test_service_filtering_cache_isolation()
|
||||
{
|
||||
// Create restrictions for different doctors
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
$this->create_test_service_restriction(888, 998, false);
|
||||
|
||||
$services = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
|
||||
];
|
||||
|
||||
// Filter for doctor 999 (should populate cache for doctor 999)
|
||||
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services, 999);
|
||||
$cache_999 = get_transient('care_booking_services_blocked_999');
|
||||
$this->assertNotFalse($cache_999, 'Cache should be set for doctor 999');
|
||||
|
||||
// Filter for doctor 998 (should populate separate cache)
|
||||
$services_998 = [
|
||||
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
|
||||
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
|
||||
];
|
||||
|
||||
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_998, 998);
|
||||
$cache_998 = get_transient('care_booking_services_blocked_998');
|
||||
$this->assertNotFalse($cache_998, 'Cache should be set for doctor 998');
|
||||
|
||||
// Caches should be different
|
||||
$this->assertNotEquals($cache_999, $cache_998, 'Cache should be isolated between doctors');
|
||||
|
||||
// Verify filtering results are correct and different
|
||||
$ids_999 = array_column($filtered_999, 'id');
|
||||
$ids_998 = array_column($filtered_998, 'id');
|
||||
|
||||
$this->assertNotContains(888, $ids_999, 'Service 888 should be blocked for doctor 999');
|
||||
$this->assertContains(888, $ids_998, 'Service 888 should NOT be blocked for doctor 998');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user