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:
Emanuel Almeida
2025-09-12 01:27:34 +01:00
parent 4a60be990e
commit 38bb926742
118 changed files with 40694 additions and 0 deletions

View 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 '';
}
}

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

View File

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

View File

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

View File

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

View File

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

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