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:
@@ -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 '';
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Test utilities for Care Booking Block plugin
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base test case for Care Booking Block plugin
|
||||
*/
|
||||
class Care_Booking_Test_Case extends WP_UnitTestCase
|
||||
{
|
||||
/**
|
||||
* Plugin instance
|
||||
*
|
||||
* @var CareBookingBlock
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* Test user ID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $admin_user_id;
|
||||
|
||||
/**
|
||||
* Set up test case
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Get plugin instance
|
||||
$this->plugin = CareBookingBlock::get_instance();
|
||||
|
||||
// Create admin user for capability tests
|
||||
$this->admin_user_id = $this->factory->user->create([
|
||||
'role' => 'administrator'
|
||||
]);
|
||||
|
||||
// Clean test database
|
||||
$this->clean_test_data();
|
||||
|
||||
// Create test data
|
||||
$this->create_test_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down test case
|
||||
*/
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->clean_test_data();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean test data from database
|
||||
*/
|
||||
protected function clean_test_data()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
// Clean restrictions table
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
$wpdb->query("DELETE FROM $table_name WHERE target_id >= 999");
|
||||
|
||||
// Clear test caches
|
||||
delete_transient('care_booking_doctors_blocked');
|
||||
delete_transient('care_booking_restrictions_hash');
|
||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_care_booking_services_blocked_99%'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create test data
|
||||
*/
|
||||
protected function create_test_data()
|
||||
{
|
||||
// This method can be overridden by child classes
|
||||
// to create specific test data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create test doctor restriction
|
||||
*
|
||||
* @param int $doctor_id Doctor ID
|
||||
* @param bool $is_blocked Whether doctor is blocked
|
||||
* @return int|false Restriction ID or false on failure
|
||||
*/
|
||||
protected function create_test_doctor_restriction($doctor_id, $is_blocked = true)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => $doctor_id,
|
||||
'doctor_id' => null,
|
||||
'is_blocked' => $is_blocked
|
||||
],
|
||||
['%s', '%d', '%d', '%d']
|
||||
);
|
||||
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create test service restriction
|
||||
*
|
||||
* @param int $service_id Service ID
|
||||
* @param int $doctor_id Doctor ID
|
||||
* @param bool $is_blocked Whether service is blocked
|
||||
* @return int|false Restriction ID or false on failure
|
||||
*/
|
||||
protected function create_test_service_restriction($service_id, $doctor_id, $is_blocked = true)
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => $service_id,
|
||||
'doctor_id' => $doctor_id,
|
||||
'is_blocked' => $is_blocked
|
||||
],
|
||||
['%s', '%d', '%d', '%d']
|
||||
);
|
||||
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert AJAX response structure
|
||||
*
|
||||
* @param array $response AJAX response
|
||||
* @param bool $should_succeed Whether response should indicate success
|
||||
*/
|
||||
protected function assert_ajax_response($response, $should_succeed = true)
|
||||
{
|
||||
$this->assertIsArray($response);
|
||||
$this->assertArrayHasKey('success', $response);
|
||||
$this->assertArrayHasKey('data', $response);
|
||||
|
||||
if ($should_succeed) {
|
||||
$this->assertTrue($response['success']);
|
||||
} else {
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertArrayHasKey('message', $response['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock WordPress nonce for testing
|
||||
*
|
||||
* @param string $action Nonce action
|
||||
* @return string
|
||||
*/
|
||||
protected function mock_wp_nonce($action = 'care_booking_nonce')
|
||||
{
|
||||
return wp_create_nonce($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current user and mock capabilities
|
||||
*
|
||||
* @param int $user_id User ID
|
||||
*/
|
||||
protected function set_current_user($user_id)
|
||||
{
|
||||
wp_set_current_user($user_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Contract test for wp_ajax_care_booking_bulk_update endpoint
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test AJAX endpoint: wp_ajax_care_booking_bulk_update
|
||||
*/
|
||||
class Test_Ajax_Bulk_Update extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test AJAX handler is registered
|
||||
*/
|
||||
public function test_ajax_handler_registered()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_ajax_care_booking_bulk_update'), 'AJAX handler should be registered');
|
||||
$this->assertFalse(has_action('wp_ajax_nopriv_care_booking_bulk_update'), 'Non-privileged AJAX should not be registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful bulk update with mixed restrictions
|
||||
*/
|
||||
public function test_successful_bulk_update()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 998,
|
||||
'is_blocked' => false
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => true
|
||||
]
|
||||
];
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Test response structure according to contract
|
||||
$this->assertArrayHasKey('message', $data['data']);
|
||||
$this->assertArrayHasKey('updated', $data['data']);
|
||||
$this->assertArrayHasKey('errors', $data['data']);
|
||||
|
||||
$this->assertEquals('Bulk update completed', $data['data']['message']);
|
||||
$this->assertEquals(3, $data['data']['updated']);
|
||||
$this->assertEmpty($data['data']['errors']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bulk update with some failures
|
||||
*/
|
||||
public function test_bulk_update_with_partial_failures()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'invalid_type', // This should fail
|
||||
'target_id' => 998,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => true
|
||||
]
|
||||
];
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false); // Partial failure should return false
|
||||
|
||||
$this->assertEquals('Partial failure in bulk update', $data['data']['message']);
|
||||
$this->assertEquals(2, $data['data']['updated']); // Only 2 successful
|
||||
$this->assertCount(1, $data['data']['errors']); // 1 error
|
||||
|
||||
// Check error structure
|
||||
$error = $data['data']['errors'][0];
|
||||
$this->assertArrayHasKey('restriction', $error);
|
||||
$this->assertArrayHasKey('error', $error);
|
||||
$this->assertEquals('invalid_type', $error['restriction']['restriction_type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bulk update with KiviCare target validation
|
||||
*/
|
||||
public function test_bulk_update_with_target_validation()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 99999, // Non-existent doctor
|
||||
'is_blocked' => true
|
||||
]
|
||||
];
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
|
||||
$this->assertEquals(0, $data['data']['updated']);
|
||||
$this->assertCount(1, $data['data']['errors']);
|
||||
$this->assertContains('Target not found in KiviCare', $data['data']['errors'][0]['error']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid nonce returns error
|
||||
*/
|
||||
public function test_invalid_nonce_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => 'invalid_nonce',
|
||||
'restrictions' => []
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid nonce', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test insufficient permissions returns error
|
||||
*/
|
||||
public function test_insufficient_permissions_error()
|
||||
{
|
||||
$subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
$this->set_current_user($subscriber_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => []
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Insufficient permissions', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty restrictions array
|
||||
*/
|
||||
public function test_empty_restrictions_array()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => []
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$this->assertEquals('Bulk update completed', $data['data']['message']);
|
||||
$this->assertEquals(0, $data['data']['updated']);
|
||||
$this->assertEmpty($data['data']['errors']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test missing restrictions parameter
|
||||
*/
|
||||
public function test_missing_restrictions_parameter()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update')
|
||||
// Missing 'restrictions' parameter
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Missing restrictions parameter', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid restrictions format
|
||||
*/
|
||||
public function test_invalid_restrictions_format()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => 'invalid_format' // Should be array
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid restrictions format', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test response time performance requirement for bulk operation
|
||||
*/
|
||||
public function test_response_time_performance()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create bulk data (50 items as per contract)
|
||||
$restrictions = [];
|
||||
for ($i = 1; $i <= 50; $i++) {
|
||||
$restrictions[] = [
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 900 + $i,
|
||||
'is_blocked' => $i % 2 === 0
|
||||
];
|
||||
}
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$response_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
$this->assertLessThan(500, $response_time, 'Bulk update should complete in under 500ms for 50 items');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
$this->assertEquals(50, $data['data']['updated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test transaction rollback on critical errors
|
||||
*/
|
||||
public function test_transaction_rollback_on_critical_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 998,
|
||||
'is_blocked' => true
|
||||
]
|
||||
];
|
||||
|
||||
// Mock database error during processing
|
||||
global $wpdb;
|
||||
|
||||
// Hook into database operations to simulate error
|
||||
add_filter('query', function($query) {
|
||||
if (strpos($query, 'care_booking_restrictions') !== false && strpos($query, '998') !== false) {
|
||||
return 'SELECT * FROM non_existent_table'; // Force error
|
||||
}
|
||||
return $query;
|
||||
});
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Remove filter
|
||||
remove_all_filters('query');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
|
||||
// Verify partial processing occurred with error handling
|
||||
$this->assertIsInt($data['data']['updated']);
|
||||
$this->assertIsArray($data['data']['errors']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache invalidation after bulk update
|
||||
*/
|
||||
public function test_cache_invalidation_after_bulk_update()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Set initial cache
|
||||
set_transient('care_booking_doctors_blocked', [997], 3600);
|
||||
set_transient('care_booking_services_blocked_999', [886], 3600);
|
||||
|
||||
$this->assertNotFalse(get_transient('care_booking_doctors_blocked'));
|
||||
$this->assertNotFalse(get_transient('care_booking_services_blocked_999'));
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
]
|
||||
];
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Cache should be invalidated
|
||||
$this->assertFalse(get_transient('care_booking_doctors_blocked'));
|
||||
$this->assertFalse(get_transient('care_booking_services_blocked_999'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WordPress action triggered after bulk update
|
||||
*/
|
||||
public function test_action_triggered_after_bulk_update()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$actions_fired = [];
|
||||
|
||||
add_action('care_booking_restriction_updated', function($type, $target_id, $doctor_id = null) use (&$actions_fired) {
|
||||
$actions_fired[] = [$type, $target_id, $doctor_id];
|
||||
}, 10, 3);
|
||||
|
||||
$restrictions = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => false
|
||||
]
|
||||
];
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Actions should have fired for each restriction
|
||||
$this->assertCount(2, $actions_fired);
|
||||
$this->assertContains(['doctor', 999, null], $actions_fired);
|
||||
$this->assertContains(['service', 888, 999], $actions_fired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test maximum bulk size limit
|
||||
*/
|
||||
public function test_maximum_bulk_size_limit()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create oversized bulk request (more than allowed)
|
||||
$restrictions = [];
|
||||
for ($i = 1; $i <= 101; $i++) { // Over 100 items limit
|
||||
$restrictions[] = [
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 800 + $i,
|
||||
'is_blocked' => true
|
||||
];
|
||||
}
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_bulk_update',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_bulk_update'),
|
||||
'restrictions' => $restrictions
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_bulk_update');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Bulk size limit exceeded', $data['data']['message']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Contract test for wp_ajax_care_booking_get_entities endpoint
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test AJAX endpoint: wp_ajax_care_booking_get_entities
|
||||
*/
|
||||
class Test_Ajax_Get_Entities extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test AJAX handler is registered
|
||||
*/
|
||||
public function test_ajax_handler_registered()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_ajax_care_booking_get_entities'), 'AJAX handler should be registered');
|
||||
$this->assertFalse(has_action('wp_ajax_nopriv_care_booking_get_entities'), 'Non-privileged AJAX should not be registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful doctors retrieval
|
||||
*/
|
||||
public function test_successful_doctors_retrieval()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create some test restrictions to show restriction status
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, false);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Test response structure according to contract
|
||||
$this->assertArrayHasKey('entities', $data['data']);
|
||||
$this->assertArrayHasKey('total', $data['data']);
|
||||
$this->assertIsArray($data['data']['entities']);
|
||||
$this->assertIsInt($data['data']['total']);
|
||||
|
||||
// Test entity structure if entities exist
|
||||
if (!empty($data['data']['entities'])) {
|
||||
$entity = $data['data']['entities'][0];
|
||||
$this->assertArrayHasKey('id', $entity);
|
||||
$this->assertArrayHasKey('name', $entity);
|
||||
$this->assertArrayHasKey('email', $entity);
|
||||
$this->assertArrayHasKey('is_blocked', $entity);
|
||||
$this->assertIsBool($entity['is_blocked']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful services retrieval
|
||||
*/
|
||||
public function test_successful_services_retrieval()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create service restriction for testing status
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'services'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$this->assertArrayHasKey('entities', $data['data']);
|
||||
$this->assertArrayHasKey('total', $data['data']);
|
||||
|
||||
// Test service entity structure if services exist
|
||||
if (!empty($data['data']['entities'])) {
|
||||
$service = $data['data']['entities'][0];
|
||||
$this->assertArrayHasKey('id', $service);
|
||||
$this->assertArrayHasKey('name', $service);
|
||||
$this->assertArrayHasKey('doctor_id', $service);
|
||||
$this->assertArrayHasKey('is_blocked', $service);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test services retrieval filtered by doctor
|
||||
*/
|
||||
public function test_services_filtered_by_doctor()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create services for different doctors
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
$this->create_test_service_restriction(887, 998, false);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'services',
|
||||
'doctor_id' => 999
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Should only return services for doctor 999
|
||||
if (!empty($data['data']['entities'])) {
|
||||
foreach ($data['data']['entities'] as $service) {
|
||||
$this->assertEquals(999, $service['doctor_id'], 'All services should belong to doctor 999');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid nonce returns error
|
||||
*/
|
||||
public function test_invalid_nonce_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => 'invalid_nonce',
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid nonce', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test insufficient permissions returns error
|
||||
*/
|
||||
public function test_insufficient_permissions_error()
|
||||
{
|
||||
$subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
$this->set_current_user($subscriber_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Insufficient permissions', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid entity type returns error
|
||||
*/
|
||||
public function test_invalid_entity_type_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'invalid_type'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid entity type', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test missing entity_type parameter
|
||||
*/
|
||||
public function test_missing_entity_type_parameter()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities')
|
||||
// Missing entity_type parameter
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Missing entity_type parameter', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test KiviCare plugin not available
|
||||
*/
|
||||
public function test_kivicare_not_available()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Mock KiviCare as not available
|
||||
add_filter('pre_option_active_plugins', function($plugins) {
|
||||
return []; // No active plugins
|
||||
});
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
remove_all_filters('pre_option_active_plugins');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('KiviCare plugin not available', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty results return correct structure
|
||||
*/
|
||||
public function test_empty_results()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Mock empty KiviCare tables
|
||||
global $wpdb;
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$this->assertIsArray($data['data']['entities']);
|
||||
$this->assertEquals(0, $data['data']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test response time performance requirement
|
||||
*/
|
||||
public function test_response_time_performance()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$response_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
$this->assertLessThan(400, $response_time, 'Response time should be under 400ms according to contract');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test restriction status accuracy
|
||||
*/
|
||||
public function test_restriction_status_accuracy()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create known restrictions
|
||||
$this->create_test_doctor_restriction(999, true); // Blocked
|
||||
$this->create_test_doctor_restriction(998, false); // Not blocked
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Find our test doctors and verify restriction status
|
||||
foreach ($data['data']['entities'] as $doctor) {
|
||||
if ($doctor['id'] == 999) {
|
||||
$this->assertTrue($doctor['is_blocked'], 'Doctor 999 should be marked as blocked');
|
||||
} elseif ($doctor['id'] == 998) {
|
||||
$this->assertFalse($doctor['is_blocked'], 'Doctor 998 should not be marked as blocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service restriction status with doctor context
|
||||
*/
|
||||
public function test_service_restriction_status_with_doctor()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create service restrictions for specific doctors
|
||||
$this->create_test_service_restriction(888, 999, true); // Service 888 blocked for doctor 999
|
||||
$this->create_test_service_restriction(888, 998, false); // Service 888 not blocked for doctor 998
|
||||
|
||||
// Get services for doctor 999
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'services',
|
||||
'doctor_id' => 999
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Check service 888 status for doctor 999
|
||||
foreach ($data['data']['entities'] as $service) {
|
||||
if ($service['id'] == 888) {
|
||||
$this->assertTrue($service['is_blocked'], 'Service 888 should be blocked for doctor 999');
|
||||
}
|
||||
}
|
||||
|
||||
// Get services for doctor 998
|
||||
$_POST['doctor_id'] = 998;
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Check service 888 status for doctor 998
|
||||
foreach ($data['data']['entities'] as $service) {
|
||||
if ($service['id'] == 888) {
|
||||
$this->assertFalse($service['is_blocked'], 'Service 888 should not be blocked for doctor 998');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database error handling
|
||||
*/
|
||||
public function test_database_error_handling()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Mock database error
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertContains('Database error', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test large dataset handling
|
||||
*/
|
||||
public function test_large_dataset_handling()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
// This test verifies the system can handle large result sets
|
||||
// without timing out or running into memory issues
|
||||
$start_memory = memory_get_usage();
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$end_memory = memory_get_usage();
|
||||
$memory_used = $end_memory - $start_memory;
|
||||
|
||||
// Memory usage should be reasonable (less than 10MB for the operation)
|
||||
$this->assertLessThan(10 * 1024 * 1024, $memory_used, 'Memory usage should be reasonable');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test concurrent request handling
|
||||
*/
|
||||
public function test_concurrent_request_handling()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_entities',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_entities'),
|
||||
'entity_type' => 'doctors'
|
||||
];
|
||||
|
||||
// Simulate multiple concurrent requests
|
||||
$responses = [];
|
||||
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_entities');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$responses[] = ob_get_clean();
|
||||
}
|
||||
|
||||
// All responses should be valid and consistent
|
||||
foreach ($responses as $response) {
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Structure should be consistent across all responses
|
||||
$this->assertArrayHasKey('entities', $data['data']);
|
||||
$this->assertArrayHasKey('total', $data['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Contract test for wp_ajax_care_booking_get_restrictions endpoint
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test AJAX endpoint: wp_ajax_care_booking_get_restrictions
|
||||
*/
|
||||
class Test_Ajax_Get_Restrictions extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test AJAX handler is registered
|
||||
*/
|
||||
public function test_ajax_handler_registered()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_ajax_care_booking_get_restrictions'), 'AJAX handler should be registered');
|
||||
$this->assertFalse(has_action('wp_ajax_nopriv_care_booking_get_restrictions'), 'Non-privileged AJAX should not be registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful request returns correct structure
|
||||
*/
|
||||
public function test_successful_request_structure()
|
||||
{
|
||||
// Set up admin user
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_service_restriction(888, 999, false);
|
||||
|
||||
// Mock AJAX request
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
// Capture AJAX response
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
// Expected for wp_die() in AJAX handlers
|
||||
}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
// Test response structure according to contract
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$this->assertArrayHasKey('restrictions', $data['data']);
|
||||
$this->assertArrayHasKey('total', $data['data']);
|
||||
$this->assertIsArray($data['data']['restrictions']);
|
||||
$this->assertIsInt($data['data']['total']);
|
||||
|
||||
// Test restriction object structure
|
||||
$this->assertGreaterThan(0, count($data['data']['restrictions']));
|
||||
$restriction = $data['data']['restrictions'][0];
|
||||
|
||||
$this->assertArrayHasKey('id', $restriction);
|
||||
$this->assertArrayHasKey('restriction_type', $restriction);
|
||||
$this->assertArrayHasKey('target_id', $restriction);
|
||||
$this->assertArrayHasKey('doctor_id', $restriction);
|
||||
$this->assertArrayHasKey('is_blocked', $restriction);
|
||||
$this->assertArrayHasKey('created_at', $restriction);
|
||||
$this->assertArrayHasKey('updated_at', $restriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filter by restriction type
|
||||
*/
|
||||
public function test_filter_by_restriction_type()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create different types
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Test filter by doctor
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'doctor'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Should only return doctor restrictions
|
||||
foreach ($data['data']['restrictions'] as $restriction) {
|
||||
$this->assertEquals('doctor', $restriction['restriction_type']);
|
||||
}
|
||||
|
||||
// Test filter by service
|
||||
$_POST['restriction_type'] = 'service';
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Should only return service restrictions
|
||||
foreach ($data['data']['restrictions'] as $restriction) {
|
||||
$this->assertEquals('service', $restriction['restriction_type']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filter by doctor for service restrictions
|
||||
*/
|
||||
public function test_filter_services_by_doctor()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create service restrictions for different doctors
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
$this->create_test_service_restriction(887, 998, true);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'service',
|
||||
'doctor_id' => 999
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Should only return services for doctor 999
|
||||
foreach ($data['data']['restrictions'] as $restriction) {
|
||||
$this->assertEquals('service', $restriction['restriction_type']);
|
||||
$this->assertEquals(999, $restriction['doctor_id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid nonce returns error
|
||||
*/
|
||||
public function test_invalid_nonce_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => 'invalid_nonce',
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid nonce', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test insufficient permissions returns error
|
||||
*/
|
||||
public function test_insufficient_permissions_error()
|
||||
{
|
||||
// Create subscriber user (no manage_options capability)
|
||||
$subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
$this->set_current_user($subscriber_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Insufficient permissions', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid parameters return error
|
||||
*/
|
||||
public function test_invalid_parameters_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'invalid_type'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid parameters', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty results return correct structure
|
||||
*/
|
||||
public function test_empty_results()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$this->assertIsArray($data['data']['restrictions']);
|
||||
$this->assertEmpty($data['data']['restrictions']);
|
||||
$this->assertEquals(0, $data['data']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test response time performance requirement
|
||||
*/
|
||||
public function test_response_time_performance()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create test data
|
||||
for ($i = 1; $i <= 50; $i++) {
|
||||
$this->create_test_doctor_restriction(900 + $i, $i % 2 === 0);
|
||||
}
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$response_time = ($end_time - $start_time) * 1000; // Convert to milliseconds
|
||||
|
||||
$this->assertLessThan(200, $response_time, 'Response time should be under 200ms according to contract');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
$this->assertEquals(50, $data['data']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JSON response format compliance
|
||||
*/
|
||||
public function test_json_response_format()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Test valid JSON
|
||||
$data = json_decode($response, true);
|
||||
$this->assertNotNull($data, 'Response should be valid JSON');
|
||||
$this->assertEquals(JSON_ERROR_NONE, json_last_error(), 'JSON should be valid');
|
||||
|
||||
// Test WordPress AJAX standard format
|
||||
$this->assertArrayHasKey('success', $data);
|
||||
$this->assertArrayHasKey('data', $data);
|
||||
$this->assertIsBool($data['success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database error handling
|
||||
*/
|
||||
public function test_database_error_handling()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Mock database error by temporarily corrupting table name
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_get_restrictions',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_get_restrictions'),
|
||||
'restriction_type' => 'all'
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_get_restrictions');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Restore database prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertContains('Database error', $data['data']['message']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Contract test for wp_ajax_care_booking_toggle_restriction endpoint
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test AJAX endpoint: wp_ajax_care_booking_toggle_restriction
|
||||
*/
|
||||
class Test_Ajax_Toggle_Restriction extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test AJAX handler is registered
|
||||
*/
|
||||
public function test_ajax_handler_registered()
|
||||
{
|
||||
$this->assertTrue(has_action('wp_ajax_care_booking_toggle_restriction'), 'AJAX handler should be registered');
|
||||
$this->assertFalse(has_action('wp_ajax_nopriv_care_booking_toggle_restriction'), 'Non-privileged AJAX should not be registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful doctor restriction toggle
|
||||
*/
|
||||
public function test_successful_doctor_restriction_toggle()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Test response structure according to contract
|
||||
$this->assertArrayHasKey('message', $data['data']);
|
||||
$this->assertArrayHasKey('restriction', $data['data']);
|
||||
$this->assertEquals('Restriction updated successfully', $data['data']['message']);
|
||||
|
||||
// Test restriction object structure
|
||||
$restriction = $data['data']['restriction'];
|
||||
$this->assertArrayHasKey('id', $restriction);
|
||||
$this->assertArrayHasKey('restriction_type', $restriction);
|
||||
$this->assertArrayHasKey('target_id', $restriction);
|
||||
$this->assertArrayHasKey('doctor_id', $restriction);
|
||||
$this->assertArrayHasKey('is_blocked', $restriction);
|
||||
$this->assertArrayHasKey('updated_at', $restriction);
|
||||
|
||||
// Test values match request
|
||||
$this->assertEquals('doctor', $restriction['restriction_type']);
|
||||
$this->assertEquals(999, $restriction['target_id']);
|
||||
$this->assertNull($restriction['doctor_id']);
|
||||
$this->assertTrue($restriction['is_blocked']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful service restriction toggle
|
||||
*/
|
||||
public function test_successful_service_restriction_toggle()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => false
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
$restriction = $data['data']['restriction'];
|
||||
$this->assertEquals('service', $restriction['restriction_type']);
|
||||
$this->assertEquals(888, $restriction['target_id']);
|
||||
$this->assertEquals(999, $restriction['doctor_id']);
|
||||
$this->assertFalse($restriction['is_blocked']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test toggle existing restriction (update operation)
|
||||
*/
|
||||
public function test_toggle_existing_restriction()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Create initial restriction
|
||||
$restriction_id = $this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Toggle to unblocked
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => false
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Should return same restriction ID but updated
|
||||
$this->assertEquals($restriction_id, $data['data']['restriction']['id']);
|
||||
$this->assertFalse($data['data']['restriction']['is_blocked']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid nonce returns error
|
||||
*/
|
||||
public function test_invalid_nonce_error()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => 'invalid_nonce',
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid nonce', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test insufficient permissions returns error
|
||||
*/
|
||||
public function test_insufficient_permissions_error()
|
||||
{
|
||||
$subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
$this->set_current_user($subscriber_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Insufficient permissions', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test missing required parameters
|
||||
*/
|
||||
public function test_missing_required_parameters()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Missing target_id
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertContains('Missing required parameter', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invalid restriction type
|
||||
*/
|
||||
public function test_invalid_restriction_type()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'invalid_type',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Invalid restriction type', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test service restriction without doctor_id
|
||||
*/
|
||||
public function test_service_restriction_without_doctor_id()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'is_blocked' => true
|
||||
// Missing doctor_id for service restriction
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('doctor_id required for service restrictions', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test target not found in KiviCare
|
||||
*/
|
||||
public function test_target_not_found()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 99999, // Non-existent doctor
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Target not found', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database error handling
|
||||
*/
|
||||
public function test_database_error_handling()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Mock database error
|
||||
global $wpdb;
|
||||
$original_prefix = $wpdb->prefix;
|
||||
$wpdb->prefix = 'invalid_prefix_';
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Restore prefix
|
||||
$wpdb->prefix = $original_prefix;
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, false);
|
||||
$this->assertEquals('Database error', $data['data']['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test response time performance requirement
|
||||
*/
|
||||
public function test_response_time_performance()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$start_time = microtime(true);
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$end_time = microtime(true);
|
||||
$response_time = ($end_time - $start_time) * 1000;
|
||||
|
||||
$this->assertLessThan(300, $response_time, 'Response time should be under 300ms according to contract');
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache invalidation after toggle
|
||||
*/
|
||||
public function test_cache_invalidation_after_toggle()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
// Set initial cache
|
||||
set_transient('care_booking_doctors_blocked', [998], 3600);
|
||||
$this->assertNotFalse(get_transient('care_booking_doctors_blocked'));
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Cache should be invalidated
|
||||
$this->assertFalse(get_transient('care_booking_doctors_blocked'), 'Cache should be invalidated after toggle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WordPress action triggered after successful toggle
|
||||
*/
|
||||
public function test_action_triggered_after_toggle()
|
||||
{
|
||||
$this->set_current_user($this->admin_user_id);
|
||||
|
||||
$action_fired = false;
|
||||
$action_args = [];
|
||||
|
||||
// Hook to test action
|
||||
add_action('care_booking_restriction_updated', function($type, $target_id, $doctor_id = null) use (&$action_fired, &$action_args) {
|
||||
$action_fired = true;
|
||||
$action_args = [$type, $target_id, $doctor_id];
|
||||
}, 10, 3);
|
||||
|
||||
$_POST = [
|
||||
'action' => 'care_booking_toggle_restriction',
|
||||
'nonce' => $this->mock_wp_nonce('care_booking_toggle_restriction'),
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
do_action('wp_ajax_care_booking_toggle_restriction');
|
||||
} catch (WPAjaxDieStopException $e) {}
|
||||
$response = ob_get_clean();
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$this->assert_ajax_response($data, true);
|
||||
|
||||
// Action should have fired
|
||||
$this->assertTrue($action_fired, 'care_booking_restriction_updated action should fire');
|
||||
$this->assertEquals(['doctor', 999, null], $action_args, 'Action should receive correct arguments');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* WordPress cache integration tests for Care Booking Block plugin
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test WordPress cache integration functionality
|
||||
*/
|
||||
class Test_Cache_Integration extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test cache manager class exists and can be instantiated
|
||||
*/
|
||||
public function test_cache_manager_class_exists()
|
||||
{
|
||||
$this->assertTrue(class_exists('Care_Booking_Cache_Manager'), 'Care_Booking_Cache_Manager class should exist');
|
||||
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
$this->assertInstanceOf('Care_Booking_Cache_Manager', $cache_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache blocked doctors list
|
||||
*/
|
||||
public function test_cache_blocked_doctors()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
$blocked_doctors = [999, 998, 997];
|
||||
|
||||
// Set cache
|
||||
$result = $cache_manager->set_blocked_doctors($blocked_doctors);
|
||||
$this->assertTrue($result, 'Should successfully cache blocked doctors');
|
||||
|
||||
// Get from cache
|
||||
$cached_doctors = $cache_manager->get_blocked_doctors();
|
||||
$this->assertIsArray($cached_doctors, 'Should return array from cache');
|
||||
$this->assertEquals($blocked_doctors, $cached_doctors, 'Cached data should match original data');
|
||||
|
||||
// Verify WordPress transient was set
|
||||
$transient_data = get_transient('care_booking_doctors_blocked');
|
||||
$this->assertEquals($blocked_doctors, $transient_data, 'WordPress transient should contain correct data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache blocked services for doctor
|
||||
*/
|
||||
public function test_cache_blocked_services_by_doctor()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
$doctor_id = 999;
|
||||
$blocked_services = [888, 887, 886];
|
||||
|
||||
// Set cache
|
||||
$result = $cache_manager->set_blocked_services($doctor_id, $blocked_services);
|
||||
$this->assertTrue($result, 'Should successfully cache blocked services');
|
||||
|
||||
// Get from cache
|
||||
$cached_services = $cache_manager->get_blocked_services($doctor_id);
|
||||
$this->assertIsArray($cached_services);
|
||||
$this->assertEquals($blocked_services, $cached_services);
|
||||
|
||||
// Verify WordPress transient was set with correct key
|
||||
$transient_key = "care_booking_services_blocked_$doctor_id";
|
||||
$transient_data = get_transient($transient_key);
|
||||
$this->assertEquals($blocked_services, $transient_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache expiration
|
||||
*/
|
||||
public function test_cache_expiration()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Set short expiration for testing
|
||||
$blocked_doctors = [999];
|
||||
$result = $cache_manager->set_blocked_doctors($blocked_doctors, 1); // 1 second expiration
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Verify cache exists
|
||||
$cached_data = $cache_manager->get_blocked_doctors();
|
||||
$this->assertEquals($blocked_doctors, $cached_data);
|
||||
|
||||
// Wait for expiration
|
||||
sleep(2);
|
||||
|
||||
// Verify cache expired
|
||||
$expired_data = $cache_manager->get_blocked_doctors();
|
||||
$this->assertFalse($expired_data, 'Cache should expire after timeout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache invalidation
|
||||
*/
|
||||
public function test_cache_invalidation()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Set initial cache data
|
||||
$cache_manager->set_blocked_doctors([999, 998]);
|
||||
$cache_manager->set_blocked_services(999, [888, 887]);
|
||||
$cache_manager->set_blocked_services(998, [886]);
|
||||
|
||||
// Verify cache exists
|
||||
$this->assertNotFalse($cache_manager->get_blocked_doctors());
|
||||
$this->assertNotFalse($cache_manager->get_blocked_services(999));
|
||||
$this->assertNotFalse($cache_manager->get_blocked_services(998));
|
||||
|
||||
// Invalidate all cache
|
||||
$result = $cache_manager->invalidate_all();
|
||||
$this->assertTrue($result, 'Should successfully invalidate all cache');
|
||||
|
||||
// Verify cache was cleared
|
||||
$this->assertFalse($cache_manager->get_blocked_doctors(), 'Blocked doctors cache should be cleared');
|
||||
$this->assertFalse($cache_manager->get_blocked_services(999), 'Blocked services cache should be cleared');
|
||||
$this->assertFalse($cache_manager->get_blocked_services(998), 'Blocked services cache should be cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache hash for change detection
|
||||
*/
|
||||
public function test_cache_hash_management()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Set initial hash
|
||||
$initial_hash = 'test_hash_123';
|
||||
$result = $cache_manager->set_restrictions_hash($initial_hash);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Get hash
|
||||
$cached_hash = $cache_manager->get_restrictions_hash();
|
||||
$this->assertEquals($initial_hash, $cached_hash);
|
||||
|
||||
// Update hash
|
||||
$new_hash = 'test_hash_456';
|
||||
$cache_manager->set_restrictions_hash($new_hash);
|
||||
|
||||
$updated_hash = $cache_manager->get_restrictions_hash();
|
||||
$this->assertEquals($new_hash, $updated_hash);
|
||||
$this->assertNotEquals($initial_hash, $updated_hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache miss behavior
|
||||
*/
|
||||
public function test_cache_miss_behavior()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Test getting non-existent cache
|
||||
$non_existent_doctors = $cache_manager->get_blocked_doctors();
|
||||
$this->assertFalse($non_existent_doctors, 'Should return false for cache miss');
|
||||
|
||||
$non_existent_services = $cache_manager->get_blocked_services(123);
|
||||
$this->assertFalse($non_existent_services, 'Should return false for cache miss');
|
||||
|
||||
$non_existent_hash = $cache_manager->get_restrictions_hash();
|
||||
$this->assertFalse($non_existent_hash, 'Should return false for cache miss');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache key generation
|
||||
*/
|
||||
public function test_cache_key_generation()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Test different doctor IDs generate different cache keys
|
||||
$doctor1_services = [888];
|
||||
$doctor2_services = [887];
|
||||
|
||||
$cache_manager->set_blocked_services(999, $doctor1_services);
|
||||
$cache_manager->set_blocked_services(998, $doctor2_services);
|
||||
|
||||
// Verify separate caches
|
||||
$cached_services_1 = $cache_manager->get_blocked_services(999);
|
||||
$cached_services_2 = $cache_manager->get_blocked_services(998);
|
||||
|
||||
$this->assertEquals($doctor1_services, $cached_services_1);
|
||||
$this->assertEquals($doctor2_services, $cached_services_2);
|
||||
$this->assertNotEquals($cached_services_1, $cached_services_2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache size limits and memory usage
|
||||
*/
|
||||
public function test_cache_size_and_memory()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Test caching large dataset
|
||||
$large_doctor_list = range(1, 1000); // 1000 doctor IDs
|
||||
|
||||
$result = $cache_manager->set_blocked_doctors($large_doctor_list);
|
||||
$this->assertTrue($result, 'Should handle large datasets');
|
||||
|
||||
$cached_large_list = $cache_manager->get_blocked_doctors();
|
||||
$this->assertEquals($large_doctor_list, $cached_large_list, 'Large dataset should be cached correctly');
|
||||
$this->assertCount(1000, $cached_large_list, 'All items should be cached');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache with WordPress object cache compatibility
|
||||
*/
|
||||
public function test_wordpress_object_cache_compatibility()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Test with WordPress wp_cache functions if available
|
||||
if (function_exists('wp_cache_set') && function_exists('wp_cache_get')) {
|
||||
$test_data = [999, 998];
|
||||
|
||||
// Use WordPress object cache directly
|
||||
wp_cache_set('care_booking_test', $test_data, 'care_booking', 3600);
|
||||
$wp_cached_data = wp_cache_get('care_booking_test', 'care_booking');
|
||||
|
||||
$this->assertEquals($test_data, $wp_cached_data, 'WordPress object cache should work with plugin data');
|
||||
} else {
|
||||
$this->markTestSkipped('WordPress object cache not available in test environment');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache performance benchmarks
|
||||
*/
|
||||
public function test_cache_performance()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
$test_data = range(1, 100);
|
||||
|
||||
// Measure cache write performance
|
||||
$start_time = microtime(true);
|
||||
$cache_manager->set_blocked_doctors($test_data);
|
||||
$write_time = microtime(true) - $start_time;
|
||||
|
||||
$this->assertLessThan(0.1, $write_time, 'Cache write should complete in under 100ms');
|
||||
|
||||
// Measure cache read performance
|
||||
$start_time = microtime(true);
|
||||
$cached_data = $cache_manager->get_blocked_doctors();
|
||||
$read_time = microtime(true) - $start_time;
|
||||
|
||||
$this->assertLessThan(0.05, $read_time, 'Cache read should complete in under 50ms');
|
||||
$this->assertEquals($test_data, $cached_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache invalidation on restriction changes
|
||||
*/
|
||||
public function test_cache_invalidation_on_changes()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Set initial cache
|
||||
$cache_manager->set_blocked_doctors([999]);
|
||||
$cache_manager->set_blocked_services(999, [888]);
|
||||
|
||||
// Verify cache exists
|
||||
$this->assertNotFalse($cache_manager->get_blocked_doctors());
|
||||
$this->assertNotFalse($cache_manager->get_blocked_services(999));
|
||||
|
||||
// Simulate restriction change event
|
||||
do_action('care_booking_restriction_updated', 'doctor', 999);
|
||||
|
||||
// Cache should be automatically invalidated
|
||||
$this->assertFalse($cache_manager->get_blocked_doctors(), 'Cache should be invalidated on restriction change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache with concurrent access
|
||||
*/
|
||||
public function test_concurrent_cache_access()
|
||||
{
|
||||
$cache_manager = new Care_Booking_Cache_Manager();
|
||||
|
||||
// Simulate concurrent writes (in real scenario this would be multiple requests)
|
||||
$cache_manager->set_blocked_doctors([999]);
|
||||
$cache_manager->set_blocked_doctors([998]);
|
||||
$cache_manager->set_blocked_doctors([997]);
|
||||
|
||||
// Last write should win
|
||||
$final_data = $cache_manager->get_blocked_doctors();
|
||||
$this->assertEquals([997], $final_data, 'Last cache write should be preserved');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Database schema tests for Care Booking Block plugin
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test database schema creation and structure
|
||||
*/
|
||||
class Test_Database_Schema extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test that wp_care_booking_restrictions table exists
|
||||
*/
|
||||
public function test_restrictions_table_exists()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
|
||||
|
||||
$this->assertTrue($table_exists, 'wp_care_booking_restrictions table should exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test table has required columns with correct types
|
||||
*/
|
||||
public function test_table_has_correct_columns()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
$columns = $wpdb->get_results("DESCRIBE $table_name");
|
||||
|
||||
// Convert to associative array for easier testing
|
||||
$column_data = [];
|
||||
foreach ($columns as $column) {
|
||||
$column_data[$column->Field] = [
|
||||
'Type' => $column->Type,
|
||||
'Null' => $column->Null,
|
||||
'Key' => $column->Key,
|
||||
'Default' => $column->Default,
|
||||
'Extra' => $column->Extra
|
||||
];
|
||||
}
|
||||
|
||||
// Test required columns exist
|
||||
$expected_columns = ['id', 'restriction_type', 'target_id', 'doctor_id', 'is_blocked', 'created_at', 'updated_at'];
|
||||
foreach ($expected_columns as $column) {
|
||||
$this->assertArrayHasKey($column, $column_data, "Column '$column' should exist");
|
||||
}
|
||||
|
||||
// Test column types
|
||||
$this->assertEquals('bigint(20) unsigned', $column_data['id']['Type']);
|
||||
$this->assertEquals("enum('doctor','service')", $column_data['restriction_type']['Type']);
|
||||
$this->assertEquals('bigint(20) unsigned', $column_data['target_id']['Type']);
|
||||
$this->assertEquals('bigint(20) unsigned', $column_data['doctor_id']['Type']);
|
||||
$this->assertEquals('tinyint(1)', $column_data['is_blocked']['Type']);
|
||||
$this->assertEquals('timestamp', $column_data['created_at']['Type']);
|
||||
$this->assertEquals('timestamp', $column_data['updated_at']['Type']);
|
||||
|
||||
// Test primary key
|
||||
$this->assertEquals('PRI', $column_data['id']['Key']);
|
||||
$this->assertEquals('auto_increment', $column_data['id']['Extra']);
|
||||
|
||||
// Test null constraints
|
||||
$this->assertEquals('NO', $column_data['id']['Null']);
|
||||
$this->assertEquals('NO', $column_data['restriction_type']['Null']);
|
||||
$this->assertEquals('NO', $column_data['target_id']['Null']);
|
||||
$this->assertEquals('YES', $column_data['doctor_id']['Null']);
|
||||
$this->assertEquals('NO', $column_data['is_blocked']['Null']);
|
||||
|
||||
// Test default values
|
||||
$this->assertEquals('0', $column_data['is_blocked']['Default']);
|
||||
$this->assertEquals('current_timestamp()', $column_data['created_at']['Default']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test table has required indexes
|
||||
*/
|
||||
public function test_table_has_required_indexes()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
$indexes = $wpdb->get_results("SHOW INDEX FROM $table_name");
|
||||
|
||||
// Convert to associative array for easier testing
|
||||
$index_data = [];
|
||||
foreach ($indexes as $index) {
|
||||
$index_data[$index->Key_name][] = $index->Column_name;
|
||||
}
|
||||
|
||||
// Test required indexes exist
|
||||
$this->assertArrayHasKey('PRIMARY', $index_data, 'PRIMARY index should exist');
|
||||
$this->assertArrayHasKey('idx_type_target', $index_data, 'idx_type_target index should exist');
|
||||
$this->assertArrayHasKey('idx_doctor_service', $index_data, 'idx_doctor_service index should exist');
|
||||
$this->assertArrayHasKey('idx_blocked', $index_data, 'idx_blocked index should exist');
|
||||
|
||||
// Test index columns
|
||||
$this->assertEquals(['id'], $index_data['PRIMARY']);
|
||||
$this->assertEquals(['restriction_type', 'target_id'], $index_data['idx_type_target']);
|
||||
$this->assertEquals(['doctor_id', 'target_id'], $index_data['idx_doctor_service']);
|
||||
$this->assertEquals(['is_blocked'], $index_data['idx_blocked']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enum constraint on restriction_type
|
||||
*/
|
||||
public function test_restriction_type_enum_constraint()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
// Test valid enum values
|
||||
$valid_insert = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
$this->assertNotFalse($valid_insert, 'Should insert valid restriction_type');
|
||||
|
||||
// Clean up
|
||||
$wpdb->delete($table_name, ['target_id' => 999]);
|
||||
|
||||
// Test invalid enum values (this should fail)
|
||||
$invalid_insert = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'invalid_type',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
$this->assertFalse($invalid_insert, 'Should not insert invalid restriction_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test auto-increment behavior on id field
|
||||
*/
|
||||
public function test_auto_increment_id_field()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
// Insert first record
|
||||
$result1 = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
$this->assertNotFalse($result1);
|
||||
$id1 = $wpdb->insert_id;
|
||||
|
||||
// Insert second record
|
||||
$result2 = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 998,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
$this->assertNotFalse($result2);
|
||||
$id2 = $wpdb->insert_id;
|
||||
|
||||
// Test auto-increment
|
||||
$this->assertGreaterThan($id1, $id2, 'Second ID should be greater than first');
|
||||
|
||||
// Clean up
|
||||
$wpdb->delete($table_name, ['target_id' => 999]);
|
||||
$wpdb->delete($table_name, ['target_id' => 998]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test timestamp fields behavior
|
||||
*/
|
||||
public function test_timestamp_fields()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
// Insert record and check timestamps
|
||||
$before_insert = current_time('mysql');
|
||||
|
||||
$result = $wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
$this->assertNotFalse($result);
|
||||
|
||||
$after_insert = current_time('mysql');
|
||||
|
||||
// Get the inserted record
|
||||
$record = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE target_id = %d", 999));
|
||||
$this->assertNotNull($record);
|
||||
|
||||
// Test created_at is set automatically
|
||||
$this->assertNotNull($record->created_at);
|
||||
$this->assertGreaterThanOrEqual($before_insert, $record->created_at);
|
||||
$this->assertLessThanOrEqual($after_insert, $record->created_at);
|
||||
|
||||
// Test updated_at matches created_at on insert
|
||||
$this->assertEquals($record->created_at, $record->updated_at);
|
||||
|
||||
// Wait a moment and update record
|
||||
sleep(1);
|
||||
$before_update = current_time('mysql');
|
||||
|
||||
$wpdb->update(
|
||||
$table_name,
|
||||
['is_blocked' => 0],
|
||||
['target_id' => 999]
|
||||
);
|
||||
|
||||
$after_update = current_time('mysql');
|
||||
|
||||
// Get updated record
|
||||
$updated_record = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE target_id = %d", 999));
|
||||
|
||||
// Test updated_at changed automatically
|
||||
$this->assertNotEquals($record->updated_at, $updated_record->updated_at);
|
||||
$this->assertGreaterThanOrEqual($before_update, $updated_record->updated_at);
|
||||
$this->assertLessThanOrEqual($after_update, $updated_record->updated_at);
|
||||
|
||||
// Test created_at remained unchanged
|
||||
$this->assertEquals($record->created_at, $updated_record->created_at);
|
||||
|
||||
// Clean up
|
||||
$wpdb->delete($table_name, ['target_id' => 999]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database table cleanup on plugin deactivation
|
||||
*/
|
||||
public function test_plugin_deactivation_preserves_data()
|
||||
{
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'care_booking_restrictions';
|
||||
|
||||
// Insert test data
|
||||
$wpdb->insert(
|
||||
$table_name,
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => 1
|
||||
]
|
||||
);
|
||||
|
||||
// Simulate plugin deactivation
|
||||
$plugin = CareBookingBlock::get_instance();
|
||||
$plugin->deactivate();
|
||||
|
||||
// Check that table and data still exist
|
||||
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
|
||||
$this->assertTrue($table_exists, 'Table should still exist after deactivation');
|
||||
|
||||
$record_exists = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE target_id = %d", 999));
|
||||
$this->assertEquals('1', $record_exists, 'Data should be preserved after deactivation');
|
||||
|
||||
// Clean up
|
||||
$wpdb->delete($table_name, ['target_id' => 999]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* Descomplicar® Crescimento Digital
|
||||
* https://descomplicar.pt
|
||||
*/
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Restriction model CRUD tests for Care Booking Block plugin
|
||||
*
|
||||
* @package CareBookingBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test restriction model CRUD operations
|
||||
*/
|
||||
class Test_Restriction_Model extends Care_Booking_Test_Case
|
||||
{
|
||||
|
||||
/**
|
||||
* Test restriction model class exists and can be instantiated
|
||||
*/
|
||||
public function test_restriction_model_class_exists()
|
||||
{
|
||||
$this->assertTrue(class_exists('Care_Booking_Restriction_Model'), 'Care_Booking_Restriction_Model class should exist');
|
||||
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
$this->assertInstanceOf('Care_Booking_Restriction_Model', $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create doctor restriction
|
||||
*/
|
||||
public function test_create_doctor_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
$restriction_data = [
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$restriction_id = $model->create($restriction_data);
|
||||
$this->assertIsInt($restriction_id, 'Should return integer restriction ID');
|
||||
$this->assertGreaterThan(0, $restriction_id, 'Restriction ID should be greater than 0');
|
||||
|
||||
// Verify restriction was created in database
|
||||
$created_restriction = $model->get($restriction_id);
|
||||
$this->assertNotFalse($created_restriction, 'Should retrieve created restriction');
|
||||
$this->assertEquals('doctor', $created_restriction->restriction_type);
|
||||
$this->assertEquals(999, $created_restriction->target_id);
|
||||
$this->assertNull($created_restriction->doctor_id);
|
||||
$this->assertEquals(1, $created_restriction->is_blocked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create service restriction
|
||||
*/
|
||||
public function test_create_service_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
$restriction_data = [
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$restriction_id = $model->create($restriction_data);
|
||||
$this->assertIsInt($restriction_id);
|
||||
$this->assertGreaterThan(0, $restriction_id);
|
||||
|
||||
// Verify restriction was created
|
||||
$created_restriction = $model->get($restriction_id);
|
||||
$this->assertEquals('service', $created_restriction->restriction_type);
|
||||
$this->assertEquals(888, $created_restriction->target_id);
|
||||
$this->assertEquals(999, $created_restriction->doctor_id);
|
||||
$this->assertEquals(1, $created_restriction->is_blocked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test read restriction by ID
|
||||
*/
|
||||
public function test_read_restriction_by_id()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test restriction
|
||||
$restriction_id = $this->create_test_doctor_restriction(999, true);
|
||||
$this->assertNotFalse($restriction_id);
|
||||
|
||||
// Read restriction
|
||||
$restriction = $model->get($restriction_id);
|
||||
$this->assertNotFalse($restriction, 'Should retrieve restriction by ID');
|
||||
$this->assertEquals($restriction_id, $restriction->id);
|
||||
$this->assertEquals('doctor', $restriction->restriction_type);
|
||||
$this->assertEquals(999, $restriction->target_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get restrictions by type
|
||||
*/
|
||||
public function test_get_restrictions_by_type()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true);
|
||||
$this->create_test_doctor_restriction(998, false);
|
||||
$this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Get doctor restrictions
|
||||
$doctor_restrictions = $model->get_by_type('doctor');
|
||||
$this->assertIsArray($doctor_restrictions, 'Should return array of doctor restrictions');
|
||||
$this->assertCount(2, $doctor_restrictions, 'Should return 2 doctor restrictions');
|
||||
|
||||
// Verify all are doctor type
|
||||
foreach ($doctor_restrictions as $restriction) {
|
||||
$this->assertEquals('doctor', $restriction->restriction_type);
|
||||
}
|
||||
|
||||
// Get service restrictions
|
||||
$service_restrictions = $model->get_by_type('service');
|
||||
$this->assertIsArray($service_restrictions);
|
||||
$this->assertCount(1, $service_restrictions, 'Should return 1 service restriction');
|
||||
$this->assertEquals('service', $service_restrictions[0]->restriction_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get blocked doctors
|
||||
*/
|
||||
public function test_get_blocked_doctors()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test restrictions
|
||||
$this->create_test_doctor_restriction(999, true); // Blocked
|
||||
$this->create_test_doctor_restriction(998, false); // Not blocked
|
||||
$this->create_test_doctor_restriction(997, true); // Blocked
|
||||
|
||||
$blocked_doctors = $model->get_blocked_doctors();
|
||||
$this->assertIsArray($blocked_doctors, 'Should return array of blocked doctor IDs');
|
||||
$this->assertCount(2, $blocked_doctors, 'Should return 2 blocked doctors');
|
||||
|
||||
// Check that correct doctors are blocked
|
||||
$this->assertContains(999, $blocked_doctors);
|
||||
$this->assertContains(997, $blocked_doctors);
|
||||
$this->assertNotContains(998, $blocked_doctors, 'Non-blocked doctor should not be in list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get blocked services for specific doctor
|
||||
*/
|
||||
public function test_get_blocked_services_by_doctor()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test service restrictions for doctor 999
|
||||
$this->create_test_service_restriction(888, 999, true); // Blocked
|
||||
$this->create_test_service_restriction(887, 999, false); // Not blocked
|
||||
$this->create_test_service_restriction(886, 998, true); // Different doctor
|
||||
|
||||
$blocked_services = $model->get_blocked_services(999);
|
||||
$this->assertIsArray($blocked_services);
|
||||
$this->assertCount(1, $blocked_services, 'Should return 1 blocked service for doctor 999');
|
||||
$this->assertContains(888, $blocked_services);
|
||||
$this->assertNotContains(887, $blocked_services, 'Non-blocked service should not be in list');
|
||||
$this->assertNotContains(886, $blocked_services, 'Service for different doctor should not be in list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update restriction
|
||||
*/
|
||||
public function test_update_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test restriction
|
||||
$restriction_id = $this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Update restriction
|
||||
$update_data = ['is_blocked' => false];
|
||||
$result = $model->update($restriction_id, $update_data);
|
||||
$this->assertTrue($result, 'Update should return true on success');
|
||||
|
||||
// Verify update
|
||||
$updated_restriction = $model->get($restriction_id);
|
||||
$this->assertEquals(0, $updated_restriction->is_blocked, 'is_blocked should be updated to false');
|
||||
|
||||
// Verify updated_at timestamp changed
|
||||
$this->assertNotNull($updated_restriction->updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete restriction
|
||||
*/
|
||||
public function test_delete_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create test restriction
|
||||
$restriction_id = $this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Verify restriction exists
|
||||
$restriction = $model->get($restriction_id);
|
||||
$this->assertNotFalse($restriction);
|
||||
|
||||
// Delete restriction
|
||||
$result = $model->delete($restriction_id);
|
||||
$this->assertTrue($result, 'Delete should return true on success');
|
||||
|
||||
// Verify restriction no longer exists
|
||||
$deleted_restriction = $model->get($restriction_id);
|
||||
$this->assertFalse($deleted_restriction, 'Restriction should not exist after deletion');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test find existing restriction
|
||||
*/
|
||||
public function test_find_existing_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Create doctor restriction
|
||||
$doctor_restriction_id = $this->create_test_doctor_restriction(999, true);
|
||||
|
||||
// Find existing doctor restriction
|
||||
$found_doctor = $model->find_existing('doctor', 999);
|
||||
$this->assertNotFalse($found_doctor, 'Should find existing doctor restriction');
|
||||
$this->assertEquals($doctor_restriction_id, $found_doctor->id);
|
||||
|
||||
// Create service restriction
|
||||
$service_restriction_id = $this->create_test_service_restriction(888, 999, true);
|
||||
|
||||
// Find existing service restriction
|
||||
$found_service = $model->find_existing('service', 888, 999);
|
||||
$this->assertNotFalse($found_service, 'Should find existing service restriction');
|
||||
$this->assertEquals($service_restriction_id, $found_service->id);
|
||||
|
||||
// Try to find non-existing restriction
|
||||
$not_found = $model->find_existing('doctor', 123);
|
||||
$this->assertFalse($not_found, 'Should return false for non-existing restriction');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test toggle restriction (create or update)
|
||||
*/
|
||||
public function test_toggle_restriction()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Toggle non-existing restriction (should create)
|
||||
$result = $model->toggle('doctor', 999, null, true);
|
||||
$this->assertIsInt($result, 'Should return restriction ID when creating');
|
||||
|
||||
// Verify restriction was created
|
||||
$restriction = $model->find_existing('doctor', 999);
|
||||
$this->assertNotFalse($restriction);
|
||||
$this->assertEquals(1, $restriction->is_blocked);
|
||||
|
||||
// Toggle existing restriction (should update)
|
||||
$result2 = $model->toggle('doctor', 999, null, false);
|
||||
$this->assertTrue($result2, 'Should return true when updating existing');
|
||||
|
||||
// Verify restriction was updated
|
||||
$updated_restriction = $model->find_existing('doctor', 999);
|
||||
$this->assertEquals(0, $updated_restriction->is_blocked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validation errors
|
||||
*/
|
||||
public function test_validation_errors()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
// Test invalid restriction type
|
||||
$invalid_data = [
|
||||
'restriction_type' => 'invalid',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$result = $model->create($invalid_data);
|
||||
$this->assertFalse($result, 'Should return false for invalid restriction type');
|
||||
|
||||
// Test missing target_id
|
||||
$invalid_data2 = [
|
||||
'restriction_type' => 'doctor',
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$result2 = $model->create($invalid_data2);
|
||||
$this->assertFalse($result2, 'Should return false for missing target_id');
|
||||
|
||||
// Test service restriction without doctor_id
|
||||
$invalid_data3 = [
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'is_blocked' => true
|
||||
];
|
||||
|
||||
$result3 = $model->create($invalid_data3);
|
||||
$this->assertFalse($result3, 'Should return false for service restriction without doctor_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bulk operations
|
||||
*/
|
||||
public function test_bulk_operations()
|
||||
{
|
||||
$model = new Care_Booking_Restriction_Model();
|
||||
|
||||
$bulk_data = [
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 999,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'doctor',
|
||||
'target_id' => 998,
|
||||
'is_blocked' => true
|
||||
],
|
||||
[
|
||||
'restriction_type' => 'service',
|
||||
'target_id' => 888,
|
||||
'doctor_id' => 999,
|
||||
'is_blocked' => false
|
||||
]
|
||||
];
|
||||
|
||||
$results = $model->bulk_create($bulk_data);
|
||||
$this->assertIsArray($results, 'Should return array of results');
|
||||
$this->assertCount(3, $results, 'Should return 3 results');
|
||||
|
||||
// Verify all were created successfully
|
||||
foreach ($results as $result) {
|
||||
$this->assertIsInt($result, 'Each result should be a restriction ID');
|
||||
$this->assertGreaterThan(0, $result);
|
||||
}
|
||||
|
||||
// Verify restrictions exist
|
||||
$doctor_restrictions = $model->get_by_type('doctor');
|
||||
$this->assertCount(2, $doctor_restrictions);
|
||||
|
||||
$service_restrictions = $model->get_by_type('service');
|
||||
$this->assertCount(1, $service_restrictions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user