chore: add spec-kit and standardize signatures

- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Emanuel Almeida
2025-09-12 01:27:34 +01:00
parent 4a60be990e
commit 38bb926742
118 changed files with 40694 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* PHPUnit bootstrap file for Care Booking Block plugin
*
* @package CareBookingBlock
*/
// Define test environment
define('CARE_BOOKING_BLOCK_TESTS', true);
// Plugin directory
$plugin_dir = dirname(dirname(__FILE__));
// WordPress test suite directory
$wp_tests_dir = getenv('WP_TESTS_DIR') ?: '/tmp/wordpress-tests-lib';
// WordPress core directory for tests
$wp_core_dir = getenv('WP_CORE_DIR') ?: '/tmp/wordpress/';
// Check if WordPress test suite exists
if (!file_exists($wp_tests_dir . '/includes/bootstrap.php')) {
echo "WordPress test suite not found at: $wp_tests_dir\n";
echo "Please install WordPress test suite or set WP_TESTS_DIR environment variable.\n";
exit(1);
}
// Load WordPress test suite functions
require_once $wp_tests_dir . '/includes/functions.php';
/**
* Manually load the plugin for testing
*/
function _manually_load_plugin() {
global $plugin_dir;
require $plugin_dir . '/care-booking-block.php';
// Ensure KiviCare plugin functions are available for testing
if (!function_exists('is_kivicare_active')) {
function is_kivicare_active() {
return true; // Mock KiviCare as active for tests
}
}
}
// Load plugin before WordPress starts
tests_add_filter('muplugins_loaded', '_manually_load_plugin');
// Start up the WordPress testing environment
require $wp_tests_dir . '/includes/bootstrap.php';
// Load plugin test utilities
require_once $plugin_dir . '/tests/test-utilities.php';

View File

@@ -0,0 +1,388 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for CSS injection on wp_head
*
* @package CareBookingBlock
*/
/**
* Test CSS injection functionality on wp_head hook
*/
class Test_CSS_Injection extends Care_Booking_Test_Case
{
/**
* Test wp_head hook is registered for CSS injection
*/
public function test_wp_head_hook_registered()
{
$this->assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions');
// Check if our specific CSS injection hook is registered
$wp_head_callbacks = $GLOBALS['wp_filter']['wp_head']->callbacks;
$found_css_injection = false;
foreach ($wp_head_callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
if (is_array($callback['function']) &&
isset($callback['function'][0]) &&
is_object($callback['function'][0]) &&
method_exists($callback['function'][0], 'inject_restriction_css')) {
$found_css_injection = true;
$this->assertEquals(20, $priority, 'CSS injection should have priority 20 (after theme styles)');
break 2;
}
}
}
$this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head');
}
/**
* Test CSS injection generates correct styles for blocked doctors
*/
public function test_css_injection_blocked_doctors()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_doctor_restriction(998, true);
$this->create_test_doctor_restriction(997, false); // Not blocked
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Should contain CSS for blocked doctors
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output,
'Should contain CSS selector for blocked doctor 999');
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="998"]', $head_output,
'Should contain CSS selector for blocked doctor 998');
$this->assertStringNotContainsString('.kivicare-doctor[data-doctor-id="997"]', $head_output,
'Should NOT contain CSS selector for non-blocked doctor 997');
// Should contain display: none directive
$this->assertStringContainsString('display: none !important;', $head_output,
'Should contain display: none !important directive');
// Should be wrapped in style tags with proper data attribute
$this->assertStringContainsString('<style data-care-booking>', $head_output,
'Should contain opening style tag with data attribute');
$this->assertStringContainsString('</style>', $head_output,
'Should contain closing style tag');
}
/**
* Test CSS injection generates correct styles for blocked services
*/
public function test_css_injection_blocked_services()
{
// Create test service restrictions
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
$this->create_test_service_restriction(887, 998, true); // Block service 887 for doctor 998
$this->create_test_service_restriction(886, 999, false); // Don't block service 886 for doctor 999
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Should contain CSS for blocked services with doctor context
$this->assertStringContainsString('.kivicare-service[data-service-id="888"][data-doctor-id="999"]', $head_output,
'Should contain CSS selector for service 888 blocked for doctor 999');
$this->assertStringContainsString('.kivicare-service[data-service-id="887"][data-doctor-id="998"]', $head_output,
'Should contain CSS selector for service 887 blocked for doctor 998');
// Should NOT contain CSS for non-blocked service
$this->assertStringNotContainsString('[data-service-id="886"]', $head_output,
'Should NOT contain CSS selector for non-blocked service 886');
// Should contain display: none directive
$this->assertStringContainsString('display: none !important;', $head_output);
}
/**
* Test CSS injection includes fallback selectors
*/
public function test_css_injection_fallback_selectors()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_service_restriction(888, 999, true);
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Should include fallback ID selectors
$this->assertStringContainsString('#doctor-999', $head_output,
'Should include fallback ID selector for doctor');
$this->assertStringContainsString('#service-888-doctor-999', $head_output,
'Should include fallback ID selector for service');
// Should include fallback option selectors
$this->assertStringContainsString('.doctor-selection option[value="999"]', $head_output,
'Should include fallback option selector for doctor');
$this->assertStringContainsString('.service-selection option[value="888"]', $head_output,
'Should include fallback option selector for service');
}
/**
* Test CSS injection handles empty restrictions
*/
public function test_css_injection_empty_restrictions()
{
// No restrictions created
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Should still output style tags but with minimal content
if (strpos($head_output, '<style data-care-booking>') !== false) {
$this->assertStringContainsString('<style data-care-booking>', $head_output);
$this->assertStringContainsString('</style>', $head_output);
// Content should be minimal (just comments or empty)
$style_content = $this->extract_style_content($head_output);
$this->assertLessThan(100, strlen(trim($style_content)),
'Style content should be minimal when no restrictions exist');
} else {
// Or no style output at all is also acceptable
$this->assertStringNotContainsString('data-care-booking', $head_output,
'No CSS should be output when no restrictions exist');
}
}
/**
* Test CSS injection uses cache for performance
*/
public function test_css_injection_uses_cache()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
// Pre-populate cache
$blocked_doctors = [999];
$blocked_services = [];
set_transient('care_booking_doctors_blocked', $blocked_doctors, 3600);
// Measure performance with cache
$start_time = microtime(true);
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should be very fast with cache (under 50ms)
$this->assertLessThan(50, $execution_time, 'CSS injection should be fast with cache');
// Should contain correct CSS
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output);
}
/**
* Test CSS injection handles database errors gracefully
*/
public function test_css_injection_handles_database_errors()
{
// Create test restrictions first
$this->create_test_doctor_restriction(999, true);
// Mock database error
global $wpdb;
$original_prefix = $wpdb->prefix;
$wpdb->prefix = 'invalid_prefix_';
// Clear cache to force database query
delete_transient('care_booking_doctors_blocked');
// CSS injection should handle error gracefully
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Restore prefix
$wpdb->prefix = $original_prefix;
// Should not throw fatal errors
$this->assertTrue(true, 'CSS injection should handle database errors without fatal errors');
// May contain minimal or no CSS output due to error
if (strpos($head_output, '<style') !== false) {
$this->assertStringContainsString('<style', $head_output, 'Should contain style tags even on error');
}
}
/**
* Test CSS injection output is properly escaped and secure
*/
public function test_css_injection_security()
{
// Create test restrictions with edge case IDs
$this->create_test_doctor_restriction(999, true);
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Should not contain any unescaped content
$this->assertStringNotContainsString('<script', $head_output, 'Should not contain script tags');
$this->assertStringNotContainsString('javascript:', $head_output, 'Should not contain javascript: protocol');
$this->assertStringNotContainsString('expression(', $head_output, 'Should not contain CSS expressions');
// Should contain proper CSS syntax
$this->assertRegExp('/\{[^}]*display:\s*none\s*!important[^}]*\}/', $head_output,
'Should contain proper CSS syntax for display:none');
}
/**
* Test CSS injection only occurs on frontend pages
*/
public function test_css_injection_frontend_only()
{
$this->create_test_doctor_restriction(999, true);
// Test admin context
set_current_screen('edit-post');
ob_start();
do_action('wp_head');
$admin_output = ob_get_clean();
// Test frontend context
set_current_screen('front');
ob_start();
do_action('wp_head');
$frontend_output = ob_get_clean();
// CSS should be injected on frontend but policy may vary for admin
// At minimum, it should work on frontend
if (strpos($frontend_output, '<style data-care-booking>') !== false) {
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $frontend_output,
'CSS should be injected on frontend');
}
// Admin behavior may vary based on implementation
$this->assertTrue(true, 'CSS injection should handle admin vs frontend context appropriately');
}
/**
* Test CSS injection performance with large restriction sets
*/
public function test_css_injection_performance_large_dataset()
{
// Create many restrictions
for ($i = 1000; $i <= 1100; $i++) {
$this->create_test_doctor_restriction($i, true);
}
for ($i = 2000; $i <= 2050; $i++) {
$this->create_test_service_restriction($i, 1000, true);
}
$start_time = microtime(true);
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete in reasonable time even with large datasets (under 200ms)
$this->assertLessThan(200, $execution_time, 'CSS injection should handle large datasets efficiently');
// Should contain CSS for many restrictions
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="1000"]', $head_output);
$this->assertStringContainsString('.kivicare-service[data-service-id="2000"]', $head_output);
// CSS should be reasonably sized (under 100KB)
$this->assertLessThan(100000, strlen($head_output), 'Generated CSS should be reasonably sized');
}
/**
* Test CSS injection minification and optimization
*/
public function test_css_injection_optimization()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_doctor_restriction(998, true);
$this->create_test_service_restriction(888, 999, true);
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
$style_content = $this->extract_style_content($head_output);
// Should combine selectors efficiently
$doctor_selectors = substr_count($style_content, '.kivicare-doctor');
$this->assertGreaterThan(0, $doctor_selectors, 'Should contain doctor selectors');
// Should minimize redundant CSS
$display_none_count = substr_count($style_content, 'display: none !important');
$this->assertGreaterThan(0, $display_none_count, 'Should contain display:none declarations');
// Should not contain excessive whitespace if minified
if (strpos($style_content, ' ') === false) {
$this->assertTrue(true, 'CSS appears to be minified');
}
}
/**
* Test CSS injection cache invalidation
*/
public function test_css_injection_cache_invalidation()
{
// Create initial restriction
$this->create_test_doctor_restriction(999, true);
// Generate initial CSS
ob_start();
do_action('wp_head');
$initial_output = ob_get_clean();
$this->assertStringContainsString('data-doctor-id="999"', $initial_output);
// Add new restriction (should invalidate cache)
$this->create_test_doctor_restriction(998, true);
// Simulate cache invalidation
delete_transient('care_booking_doctors_blocked');
delete_transient('care_booking_restrictions_hash');
// Generate CSS again
ob_start();
do_action('wp_head');
$updated_output = ob_get_clean();
// Should now include both doctors
$this->assertStringContainsString('data-doctor-id="999"', $updated_output);
$this->assertStringContainsString('data-doctor-id="998"', $updated_output);
}
/**
* Helper method to extract style content from HTML
*/
private function extract_style_content($html)
{
if (preg_match('/<style[^>]*data-care-booking[^>]*>(.*?)<\/style>/s', $html, $matches)) {
return $matches[1];
}
return '';
}
}

View File

@@ -0,0 +1,354 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for KiviCare doctor filtering
*
* @package CareBookingBlock
*/
/**
* Test KiviCare doctor filtering functionality
*/
class Test_Doctor_Filtering extends Care_Booking_Test_Case
{
/**
* Test KiviCare doctor filter hook is registered
*/
public function test_doctor_filter_hook_registered()
{
$this->assertTrue(has_filter('kc_get_doctors_for_booking'), 'Doctor filter hook should be registered');
// Verify correct priority
$priority = has_filter('kc_get_doctors_for_booking');
$this->assertEquals(10, $priority, 'Filter should have priority 10');
}
/**
* Test doctor filtering removes blocked doctors
*/
public function test_doctor_filtering_removes_blocked_doctors()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true); // Block doctor 999
$this->create_test_doctor_restriction(998, false); // Don't block doctor 998
// Mock KiviCare doctor list
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'],
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com']
];
// Apply the filter
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
// Verify blocked doctor was removed
$doctor_ids = array_column($filtered_doctors, 'id');
$this->assertNotContains(999, $doctor_ids, 'Blocked doctor should be removed from list');
$this->assertContains(998, $doctor_ids, 'Non-blocked doctor should remain in list');
$this->assertContains(997, $doctor_ids, 'Doctor without restriction should remain in list');
// Verify structure is preserved
$this->assertCount(2, $filtered_doctors, 'Should return 2 doctors (excluding blocked one)');
foreach ($filtered_doctors as $doctor) {
$this->assertArrayHasKey('id', $doctor);
$this->assertArrayHasKey('name', $doctor);
$this->assertArrayHasKey('email', $doctor);
}
}
/**
* Test doctor filtering preserves original array when no restrictions
*/
public function test_doctor_filtering_preserves_original_when_no_restrictions()
{
// No restrictions created
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
];
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
$this->assertEquals($doctors, $filtered_doctors, 'Original array should be preserved when no restrictions');
$this->assertCount(2, $filtered_doctors, 'All doctors should remain when no restrictions');
}
/**
* Test doctor filtering with empty input array
*/
public function test_doctor_filtering_with_empty_input()
{
$this->create_test_doctor_restriction(999, true);
$empty_doctors = [];
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $empty_doctors);
$this->assertEmpty($filtered_doctors, 'Empty input should return empty output');
$this->assertIsArray($filtered_doctors, 'Should return array even with empty input');
}
/**
* Test doctor filtering with malformed input
*/
public function test_doctor_filtering_with_malformed_input()
{
$this->create_test_doctor_restriction(999, true);
// Test with non-array input
$non_array_input = "invalid_input";
$filtered_result = apply_filters('kc_get_doctors_for_booking', $non_array_input);
$this->assertEquals($non_array_input, $filtered_result, 'Non-array input should be returned unchanged');
// Test with doctors missing required fields
$malformed_doctors = [
['id' => 999, 'name' => 'Dr. João Silva'], // Missing email
['name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'], // Missing id
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com'] // Complete
];
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $malformed_doctors);
// Should handle malformed entries gracefully
$this->assertIsArray($filtered_doctors, 'Should return array even with malformed input');
// Complete entry should be processed correctly
$doctor_ids = array_column($filtered_doctors, 'id');
$this->assertNotContains(999, $doctor_ids, 'Blocked doctor should be removed even with malformed entries');
$this->assertContains(997, $doctor_ids, 'Valid non-blocked doctor should remain');
}
/**
* Test doctor filtering uses cache for performance
*/
public function test_doctor_filtering_uses_cache()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_doctor_restriction(998, true);
// Manually set cache to test cache usage
$cached_blocked_doctors = [999, 998];
set_transient('care_booking_doctors_blocked', $cached_blocked_doctors, 3600);
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com'],
['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com']
];
// Apply filter multiple times to test cache efficiency
$start_time = microtime(true);
for ($i = 0; $i < 5; $i++) {
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
}
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should be very fast with caching (under 10ms for 5 operations)
$this->assertLessThan(10, $execution_time, 'Multiple filter operations should be fast with caching');
// Verify filtering worked correctly
$doctor_ids = array_column($filtered_doctors, 'id');
$this->assertNotContains(999, $doctor_ids);
$this->assertNotContains(998, $doctor_ids);
$this->assertContains(997, $doctor_ids);
}
/**
* Test doctor filtering falls back to database when cache miss
*/
public function test_doctor_filtering_database_fallback()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
// Ensure cache is clear
delete_transient('care_booking_doctors_blocked');
$this->assertFalse(get_transient('care_booking_doctors_blocked'), 'Cache should be empty');
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
];
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
// Should still work correctly without cache
$doctor_ids = array_column($filtered_doctors, 'id');
$this->assertNotContains(999, $doctor_ids, 'Should filter correctly even without cache');
$this->assertContains(998, $doctor_ids);
// Cache should be populated after database query
$this->assertNotFalse(get_transient('care_booking_doctors_blocked'), 'Cache should be populated after query');
}
/**
* Test doctor filtering performance with large dataset
*/
public function test_doctor_filtering_performance_large_dataset()
{
// Create multiple restrictions
for ($i = 1000; $i <= 1050; $i++) {
$this->create_test_doctor_restriction($i, $i % 2 === 0); // Block even IDs
}
// Create large doctor dataset
$doctors = [];
for ($i = 1000; $i <= 1100; $i++) {
$doctors[] = [
'id' => $i,
'name' => "Dr. Test $i",
'email' => "test$i@clinic.com"
];
}
$start_time = microtime(true);
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete filtering in reasonable time (under 100ms)
$this->assertLessThan(100, $execution_time, 'Large dataset filtering should complete under 100ms');
// Verify correct filtering
$filtered_count = count($filtered_doctors);
$this->assertGreaterThan(0, $filtered_count, 'Should return some doctors');
$this->assertLessThan(count($doctors), $filtered_count, 'Some doctors should be filtered out');
// Verify no blocked doctors remain
$filtered_ids = array_column($filtered_doctors, 'id');
foreach ($filtered_ids as $id) {
if ($id >= 1000 && $id <= 1050) {
$this->assertTrue($id % 2 !== 0, "Doctor $id should not be blocked (odd IDs only)");
}
}
}
/**
* Test doctor filtering with concurrent filter applications
*/
public function test_doctor_filtering_concurrent_applications()
{
$this->create_test_doctor_restriction(999, true);
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
];
// Simulate concurrent filter applications
$results = [];
for ($i = 0; $i < 10; $i++) {
$results[] = apply_filters('kc_get_doctors_for_booking', $doctors);
}
// All results should be identical
$first_result = $results[0];
foreach ($results as $result) {
$this->assertEquals($first_result, $result, 'All concurrent applications should return identical results');
}
// Verify correct filtering in all results
foreach ($results as $result) {
$doctor_ids = array_column($result, 'id');
$this->assertNotContains(999, $doctor_ids);
$this->assertContains(998, $doctor_ids);
}
}
/**
* Test doctor filtering preserves array keys and structure
*/
public function test_doctor_filtering_preserves_structure()
{
$this->create_test_doctor_restriction(999, true);
$doctors = [
'first_doctor' => ['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com', 'specialty' => 'Cardiology'],
'second_doctor' => ['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com', 'specialty' => 'Neurology'],
'third_doctor' => ['id' => 997, 'name' => 'Dr. Pedro Oliveira', 'email' => 'pedro@clinic.com', 'specialty' => 'Pediatrics']
];
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
// Should preserve associative keys
$this->assertArrayHasKey('second_doctor', $filtered_doctors);
$this->assertArrayHasKey('third_doctor', $filtered_doctors);
$this->assertArrayNotHasKey('first_doctor', $filtered_doctors, 'Blocked doctor key should be removed');
// Should preserve all fields in remaining doctors
$this->assertArrayHasKey('specialty', $filtered_doctors['second_doctor']);
$this->assertEquals('Neurology', $filtered_doctors['second_doctor']['specialty']);
$this->assertArrayHasKey('specialty', $filtered_doctors['third_doctor']);
$this->assertEquals('Pediatrics', $filtered_doctors['third_doctor']['specialty']);
}
/**
* Test doctor filtering handles database errors gracefully
*/
public function test_doctor_filtering_handles_database_errors()
{
// Mock database error
global $wpdb;
$original_prefix = $wpdb->prefix;
$wpdb->prefix = 'invalid_prefix_';
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
];
// Filter should handle database error gracefully
$filtered_doctors = apply_filters('kc_get_doctors_for_booking', $doctors);
// Restore prefix
$wpdb->prefix = $original_prefix;
// Should return original array when database error occurs
$this->assertEquals($doctors, $filtered_doctors, 'Should return original array when database error occurs');
// No PHP errors should be thrown
$this->assertTrue(true, 'Filter should handle database errors without throwing exceptions');
}
/**
* Test doctor filtering integration with WordPress admin vs frontend
*/
public function test_doctor_filtering_admin_vs_frontend_context()
{
$this->create_test_doctor_restriction(999, true);
$doctors = [
['id' => 999, 'name' => 'Dr. João Silva', 'email' => 'joao@clinic.com'],
['id' => 998, 'name' => 'Dr. Maria Santos', 'email' => 'maria@clinic.com']
];
// Test in admin context (should filter)
set_current_screen('edit-post');
$admin_filtered = apply_filters('kc_get_doctors_for_booking', $doctors);
$admin_ids = array_column($admin_filtered, 'id');
// Test in frontend context (should filter)
set_current_screen('front');
$frontend_filtered = apply_filters('kc_get_doctors_for_booking', $doctors);
$frontend_ids = array_column($frontend_filtered, 'id');
// Both contexts should apply filtering
$this->assertNotContains(999, $admin_ids, 'Admin context should filter blocked doctors');
$this->assertNotContains(999, $frontend_ids, 'Frontend context should filter blocked doctors');
$this->assertContains(998, $admin_ids, 'Admin context should keep non-blocked doctors');
$this->assertContains(998, $frontend_ids, 'Frontend context should keep non-blocked doctors');
}
}

View File

@@ -0,0 +1,353 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for enhanced CSS injection (T031, T033)
*
* @package CareBookingBlock
*/
/**
* Test enhanced CSS injection with optimization and caching
*/
class Test_Enhanced_CSS_Injection extends Care_Booking_Test_Case
{
/**
* Test wp_head hook has correct priority
*/
public function test_wp_head_hook_priority()
{
$this->assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions');
// Check priority is 15 (after theme styles)
$wp_head_callbacks = $GLOBALS['wp_filter']['wp_head']->callbacks;
$found_css_injection = false;
foreach ($wp_head_callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
if (is_array($callback['function']) &&
isset($callback['function'][0]) &&
is_object($callback['function'][0]) &&
method_exists($callback['function'][0], 'inject_restriction_css')) {
$found_css_injection = true;
$this->assertEquals(15, $priority, 'CSS injection should have priority 15');
break 2;
}
}
}
$this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head');
}
/**
* Test enhanced CSS generation with caching
*/
public function test_enhanced_css_generation_with_caching()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('generate_restriction_css');
$method->setAccessible(true);
// Clear cache first
delete_transient('care_booking_css_' . md5(serialize([[999], [['service_id' => 888, 'doctor_id' => 999]]])));
// First call should generate and cache CSS
$start_time = microtime(true);
$css1 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
$end_time = microtime(true);
$time1 = ($end_time - $start_time) * 1000;
// Second call should use cache
$start_time = microtime(true);
$css2 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
$end_time = microtime(true);
$time2 = ($end_time - $start_time) * 1000;
$this->assertEquals($css1, $css2, 'Cached CSS should be identical');
$this->assertLessThan($time1, $time2, 'Cached call should be faster');
$this->assertLessThan(10, $time2, 'Cached call should be very fast');
}
/**
* Test enhanced CSS selectors for KiviCare 3.0+
*/
public function test_enhanced_css_selectors()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('generate_restriction_css');
$method->setAccessible(true);
$css = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]);
// Check for KiviCare 3.0+ selectors
$this->assertStringContainsString('.kc-doctor-item[data-id="999"]', $css, 'Should contain KiviCare 3.0 doctor selector');
$this->assertStringContainsString('.doctor-card[data-doctor="999"]', $css, 'Should contain modern doctor card selector');
$this->assertStringContainsString('.kc-service-item[data-service="888"][data-doctor="999"]', $css, 'Should contain KiviCare 3.0 service selector');
// Check for form selectors
$this->assertStringContainsString('select[name=\'doctor_id\'] option[value="999"]', $css, 'Should contain form option selector');
$this->assertStringContainsString('.service-selection[data-doctor="999"] option[value="888"]', $css, 'Should contain contextual service selector');
// Check for booking form selectors
$this->assertStringContainsString('.booking-doctor-999', $css, 'Should contain booking doctor selector');
$this->assertStringContainsString('.appointment-service-888.doctor-999', $css, 'Should contain appointment service selector');
}
/**
* Test CSS chunking for performance
*/
public function test_css_chunking_performance()
{
// Create many doctor restrictions
$doctor_ids = [];
for ($i = 1000; $i <= 1150; $i++) {
$this->create_test_doctor_restriction($i, true);
$doctor_ids[] = $i;
}
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('generate_restriction_css');
$method->setAccessible(true);
$css = $method->invoke($integration, $doctor_ids, []);
// Should contain multiple CSS rules (chunked)
$rule_count = substr_count($css, 'display: none !important;');
$this->assertGreaterThan(1, $rule_count, 'Should chunk selectors into multiple CSS rules');
$this->assertLessThan(10, $rule_count, 'Should not create too many rules');
// CSS should be reasonably sized
$this->assertLessThan(100000, strlen($css), 'Generated CSS should be reasonably sized');
}
/**
* Test CSS minification in production
*/
public function test_css_minification()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('minify_css');
$method->setAccessible(true);
$verbose_css = "/* Comment */\n.test {\n display: none !important;\n visibility: hidden !important;\n}";
$minified = $method->invoke($integration, $verbose_css);
$this->assertStringNotContainsString('/*', $minified, 'Comments should be removed');
$this->assertStringNotContainsString("\n", $minified, 'Line breaks should be removed');
$this->assertStringNotContainsString(" ", $minified, 'Multiple spaces should be removed');
$this->assertStringContainsString('display:none!important', $minified, 'Properties should be compressed');
}
/**
* Test conditional CSS injection based on page content
*/
public function test_conditional_css_injection()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('should_inject_css');
$method->setAccessible(true);
// Mock KiviCare as active
$reflection_active = new ReflectionClass($integration);
$active_method = $reflection_active->getMethod('is_kivicare_active');
$active_method->setAccessible(true);
// Test with page that should load scripts
global $post;
$post = (object) ['post_content' => '[kivicare_booking]'];
// Should inject CSS
$should_inject = $method->invoke($integration);
// Note: This might be false if KiviCare is not actually active in test environment
// Test with page that shouldn't load scripts
$post = (object) ['post_content' => 'Regular page content'];
$should_not_inject = $method->invoke($integration);
// This test depends on KiviCare being active, so we'll just ensure method doesn't crash
$this->assertTrue(is_bool($should_not_inject), 'should_inject_css should return boolean');
}
/**
* Test graceful degradation CSS classes
*/
public function test_graceful_degradation_css()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('generate_restriction_css');
$method->setAccessible(true);
$css = $method->invoke($integration, [999], []);
// Should contain fallback classes
$this->assertStringContainsString('.care-booking-fallback', $css, 'Should contain fallback class');
$this->assertStringContainsString('.care-booking-loading::after', $css, 'Should contain loading class');
$this->assertStringContainsString('opacity: 0.7', $css, 'Should contain fallback styling');
$this->assertStringContainsString('pointer-events: none', $css, 'Should disable pointer events for fallback');
}
/**
* Test CSS injection with version attribute
*/
public function test_css_injection_versioning()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
// Capture CSS output
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
if (strpos($head_output, 'care-booking-restrictions') !== false) {
// Should contain version attribute
$this->assertStringContainsString('data-version="' . CARE_BOOKING_BLOCK_VERSION . '"', $head_output,
'Should contain version attribute');
// Should contain proper ID
$this->assertStringContainsString('id="care-booking-restrictions"', $head_output,
'Should contain proper style ID');
// Should contain HTML comments for debugging
$this->assertStringContainsString('<!-- Care Booking Block Styles -->', $head_output,
'Should contain start comment');
$this->assertStringContainsString('<!-- End Care Booking Block Styles -->', $head_output,
'Should contain end comment');
}
}
/**
* Test error handling in CSS injection
*/
public function test_css_injection_error_handling()
{
// Create test restrictions first
$this->create_test_doctor_restriction(999, true);
// Force an error by mocking a database issue
global $wpdb;
$original_prefix = $wpdb->prefix;
$wpdb->prefix = 'invalid_prefix_';
// Clear cache to force database query
delete_transient('care_booking_doctors_blocked');
// CSS injection should handle error gracefully
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
// Restore prefix
$wpdb->prefix = $original_prefix;
// Should not contain PHP errors
$this->assertStringNotContainsString('Fatal error', $head_output, 'Should not contain fatal errors');
$this->assertStringNotContainsString('Warning:', $head_output, 'Should not contain warnings');
// In debug mode, should contain error comment
if (defined('WP_DEBUG') && WP_DEBUG) {
$this->assertStringContainsString('CSS injection failed', $head_output,
'Should contain error comment in debug mode');
}
}
/**
* Test CSS injection performance
*/
public function test_css_injection_performance()
{
// Create moderate number of restrictions
for ($i = 1000; $i <= 1050; $i++) {
$this->create_test_doctor_restriction($i, true);
}
for ($i = 2000; $i <= 2025; $i++) {
$this->create_test_service_restriction($i, 1000, true);
}
$start_time = microtime(true);
ob_start();
do_action('wp_head');
$head_output = ob_get_clean();
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete quickly (under 200ms)
$this->assertLessThan(200, $execution_time, 'CSS injection should be performant');
// Generated CSS should be reasonable size
if (strpos($head_output, 'care-booking-restrictions') !== false) {
$this->assertLessThan(50000, strlen($head_output), 'Generated CSS should be reasonably sized');
}
}
/**
* Test CSS cache invalidation
*/
public function test_css_cache_invalidation()
{
// Create initial restriction
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('generate_restriction_css');
$method->setAccessible(true);
// Generate initial CSS
$css1 = $method->invoke($integration, [999], []);
$this->assertStringContainsString('999', $css1, 'Initial CSS should contain doctor 999');
// Add new restriction
$this->create_test_doctor_restriction(998, true);
// Cache should be invalidated and new CSS should include both doctors
$css2 = $method->invoke($integration, [999, 998], []);
$this->assertStringContainsString('999', $css2, 'Updated CSS should contain doctor 999');
$this->assertStringContainsString('998', $css2, 'Updated CSS should contain doctor 998');
$this->assertNotEquals($css1, $css2, 'CSS should be different after adding restriction');
}
}

View File

@@ -0,0 +1,272 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for enhanced doctor filtering hooks (T029)
*
* @package CareBookingBlock
*/
/**
* Test enhanced doctor filtering with multiple KiviCare hooks
*/
class Test_Enhanced_Doctor_Filtering extends Care_Booking_Test_Case
{
/**
* Test multiple doctor filter hooks are registered
*/
public function test_multiple_doctor_hooks_registered()
{
$hooks_to_test = [
'kc_get_doctors_for_booking',
'kivicare_doctors_list',
'kivicare_get_doctors'
];
foreach ($hooks_to_test as $hook) {
$this->assertTrue(has_filter($hook), "Hook {$hook} should be registered");
// Check that our callback is registered
$callbacks = $GLOBALS['wp_filter'][$hook]->callbacks[10] ?? [];
$found_callback = false;
foreach ($callbacks as $callback) {
if (is_array($callback['function']) &&
isset($callback['function'][0]) &&
is_object($callback['function'][0]) &&
method_exists($callback['function'][0], 'filter_doctors')) {
$found_callback = true;
break;
}
}
$this->assertTrue($found_callback, "filter_doctors callback should be registered for {$hook}");
}
}
/**
* Test doctor filtering works with different data formats
*/
public function test_doctor_filtering_data_formats()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$this->create_test_doctor_restriction(998, false);
$integration = $this->plugin->kivicare_integration;
// Test array format
$doctors_array = [
['id' => 999, 'name' => 'Dr. Blocked'],
['id' => 998, 'name' => 'Dr. Available'],
['id' => 997, 'name' => 'Dr. Other']
];
$filtered_array = $integration->filter_doctors($doctors_array);
$this->assertCount(2, $filtered_array, 'Should filter out blocked doctor from array format');
$this->assertArrayNotHasKey(0, $filtered_array, 'Blocked doctor should be removed');
// Test object format
$doctor_objects = [
(object) ['id' => 999, 'name' => 'Dr. Blocked'],
(object) ['id' => 998, 'name' => 'Dr. Available'],
(object) ['id' => 997, 'name' => 'Dr. Other']
];
$filtered_objects = $integration->filter_doctors($doctor_objects);
$this->assertCount(2, $filtered_objects, 'Should filter out blocked doctor from object format');
}
/**
* Test REST API doctor filtering
*/
public function test_rest_api_doctor_filtering()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Mock REST request
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
// Mock REST response
$response_data = [
'success' => true,
'data' => [
['id' => 999, 'name' => 'Dr. Blocked'],
['id' => 998, 'name' => 'Dr. Available']
]
];
$response = new WP_REST_Response($response_data);
// Test filtering
$result = $integration->filter_rest_api_response(false, $response, $request, null);
// Should return original served value (false)
$this->assertFalse($result);
// Check if response data was filtered
$filtered_data = $response->get_data();
$this->assertCount(1, $filtered_data['data'], 'REST API should filter blocked doctors');
$this->assertEquals(998, $filtered_data['data'][1]['id'], 'Available doctor should remain');
}
/**
* Test KiviCare endpoint detection
*/
public function test_kivicare_endpoint_detection()
{
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('is_kivicare_rest_endpoint');
$method->setAccessible(true);
// Test KiviCare endpoints
$kivicare_request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
$this->assertTrue($method->invoke($integration, $kivicare_request));
$kc_request = new WP_REST_Request('GET', '/kc/v1/services');
$this->assertTrue($method->invoke($integration, $kc_request));
// Test non-KiviCare endpoint
$other_request = new WP_REST_Request('GET', '/wp/v2/posts');
$this->assertFalse($method->invoke($integration, $other_request));
}
/**
* Test admin bypass in REST API filtering
*/
public function test_rest_api_admin_bypass()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
// Set admin user
$this->set_current_user($this->admin_user_id);
$integration = $this->plugin->kivicare_integration;
// Mock admin context
set_current_screen('edit-post');
// Mock REST request
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
$response_data = [
'success' => true,
'data' => [
['id' => 999, 'name' => 'Dr. Blocked'],
['id' => 998, 'name' => 'Dr. Available']
]
];
$response = new WP_REST_Response($response_data);
// Test filtering - should bypass for admin
$integration->filter_rest_api_response(false, $response, $request, null);
$filtered_data = $response->get_data();
$this->assertCount(2, $filtered_data['data'], 'Admin should see all doctors in REST API');
}
/**
* Test error handling in REST API filtering
*/
public function test_rest_api_error_handling()
{
$integration = $this->plugin->kivicare_integration;
// Mock malformed request
$request = new WP_REST_Request('GET', '/kivicare/v1/doctors');
// Mock malformed response
$response = new WP_REST_Response(null);
// Should not throw errors
$result = $integration->filter_rest_api_response(false, $response, $request, null);
$this->assertFalse($result, 'Should handle malformed responses gracefully');
}
/**
* Test caching works with multiple hooks
*/
public function test_caching_with_multiple_hooks()
{
// Create test restrictions
$this->create_test_doctor_restriction(999, true);
$integration = $this->plugin->kivicare_integration;
// Pre-populate cache
set_transient('care_booking_doctors_blocked', [999], 3600);
$doctors = [
['id' => 999, 'name' => 'Dr. Blocked'],
['id' => 998, 'name' => 'Dr. Available']
];
// Test multiple hook calls use same cache
$start_time = microtime(true);
$filtered1 = apply_filters('kc_get_doctors_for_booking', $doctors);
$filtered2 = apply_filters('kivicare_doctors_list', $doctors);
$filtered3 = apply_filters('kivicare_get_doctors', $doctors);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should be very fast with cache (under 50ms for all three calls)
$this->assertLessThan(50, $execution_time, 'Multiple hook calls should use cached data');
// All should return same filtered results
$this->assertEquals($filtered1, $filtered2, 'All hooks should return same results');
$this->assertEquals($filtered2, $filtered3, 'All hooks should return same results');
// Should filter blocked doctor
$this->assertCount(1, $filtered1, 'Should filter blocked doctor');
}
/**
* Test performance with large doctor datasets
*/
public function test_performance_large_doctor_dataset()
{
// Create many restrictions
for ($i = 1000; $i <= 1100; $i++) {
$this->create_test_doctor_restriction($i, true);
}
// Create large doctor dataset
$doctors = [];
for ($i = 900; $i <= 1200; $i++) {
$doctors[] = ['id' => $i, 'name' => "Dr. Test {$i}"];
}
$integration = $this->plugin->kivicare_integration;
$start_time = microtime(true);
$filtered = $integration->filter_doctors($doctors);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete in reasonable time (under 100ms)
$this->assertLessThan(100, $execution_time, 'Should handle large datasets efficiently');
// Should filter out blocked doctors (1000-1100)
$this->assertLessThanOrEqual(200, count($filtered), 'Should filter out blocked doctors');
// Available doctors (900-999, 1101-1200) should remain
$this->assertGreaterThanOrEqual(200, count($filtered), 'Available doctors should remain');
}
}

View File

@@ -0,0 +1,346 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for enhanced service filtering hooks (T030)
*
* @package CareBookingBlock
*/
/**
* Test enhanced service filtering with multiple KiviCare hooks
*/
class Test_Enhanced_Service_Filtering extends Care_Booking_Test_Case
{
/**
* Test multiple service filter hooks are registered
*/
public function test_multiple_service_hooks_registered()
{
$hooks_to_test = [
'kc_get_services_by_doctor',
'kivicare_services_list',
'kivicare_get_services'
];
foreach ($hooks_to_test as $hook) {
$this->assertTrue(has_filter($hook), "Hook {$hook} should be registered");
// Check that our callback is registered
$callbacks = $GLOBALS['wp_filter'][$hook]->callbacks[10] ?? [];
$found_callback = false;
foreach ($callbacks as $callback) {
if (is_array($callback['function']) &&
isset($callback['function'][0]) &&
is_object($callback['function'][0]) &&
method_exists($callback['function'][0], 'filter_services')) {
$found_callback = true;
break;
}
}
$this->assertTrue($found_callback, "filter_services callback should be registered for {$hook}");
}
}
/**
* Test service filtering without doctor ID extraction
*/
public function test_service_filtering_with_context_extraction()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$this->create_test_service_restriction(887, 999, false);
$integration = $this->plugin->kivicare_integration;
// Test services with doctor_id in array
$services = [
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999],
['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998]
];
$filtered = $integration->filter_services($services);
$this->assertCount(2, $filtered, 'Should filter out blocked service');
// Check remaining services
$remaining_ids = array_column($filtered, 'id');
$this->assertNotContains(888, $remaining_ids, 'Blocked service should be filtered');
$this->assertContains(887, $remaining_ids, 'Available service should remain');
$this->assertContains(886, $remaining_ids, 'Service for different doctor should remain');
}
/**
* Test doctor ID extraction from URL parameters
*/
public function test_doctor_id_extraction_from_url()
{
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('extract_doctor_id_from_context');
$method->setAccessible(true);
// Test extraction from GET parameters
$_GET['doctor_id'] = '999';
$result = $method->invoke($integration, []);
$this->assertEquals(999, $result, 'Should extract doctor ID from GET parameters');
unset($_GET['doctor_id']);
// Test extraction from POST parameters
$_POST['doctor_id'] = '998';
$result = $method->invoke($integration, []);
$this->assertEquals(998, $result, 'Should extract doctor ID from POST parameters');
unset($_POST['doctor_id']);
}
/**
* Test service filtering with blocked doctors
*/
public function test_service_filtering_with_blocked_doctors()
{
// Create blocked doctor and service restrictions
$this->create_test_doctor_restriction(999, true); // Block doctor 999
$this->create_test_service_restriction(888, 998, true); // Block service 888 for doctor 998
$integration = $this->plugin->kivicare_integration;
$services = [
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999], // Should be filtered (blocked doctor)
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999], // Should be filtered (blocked doctor)
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 998], // Should be filtered (blocked service)
['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998] // Should remain
];
$filtered = $integration->filter_services($services);
$this->assertCount(1, $filtered, 'Should filter services from blocked doctor and blocked services');
$remaining_service = reset($filtered);
$this->assertEquals(886, $remaining_service['id'], 'Only non-blocked service should remain');
$this->assertEquals(998, $remaining_service['doctor_id'], 'Service should be for non-blocked doctor');
}
/**
* Test REST API service filtering
*/
public function test_rest_api_service_filtering()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Mock REST request with doctor_id parameter
$request = new WP_REST_Request('GET', '/kivicare/v1/services');
$request->set_param('doctor_id', 999);
// Mock REST response
$response_data = [
'success' => true,
'data' => [
['id' => 888, 'name' => 'Blocked Service'],
['id' => 887, 'name' => 'Available Service']
]
];
$response = new WP_REST_Response($response_data);
// Test filtering
$result = $integration->filter_rest_api_response(false, $response, $request, null);
// Should return original served value (false)
$this->assertFalse($result);
// Check if response data was filtered
$filtered_data = $response->get_data();
$this->assertCount(1, $filtered_data['data'], 'REST API should filter blocked services');
$this->assertEquals(887, $filtered_data['data'][1]['id'], 'Available service should remain');
}
/**
* Test service filtering with object format
*/
public function test_service_filtering_object_format()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Test object format
$services = [
(object) ['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
(object) ['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999],
(object) ['id' => 886, 'name' => 'Service 3', 'doctor_id' => 998]
];
$filtered = $integration->filter_services($services, 999);
$this->assertCount(2, $filtered, 'Should filter out blocked service from object format');
// Check that blocked service is not present
$remaining_ids = [];
foreach ($filtered as $service) {
$remaining_ids[] = $service->id;
}
$this->assertNotContains(888, $remaining_ids, 'Blocked service should be filtered from objects');
}
/**
* Test service filtering with empty or invalid input
*/
public function test_service_filtering_invalid_input()
{
$integration = $this->plugin->kivicare_integration;
// Test with non-array input
$result = $integration->filter_services('not an array');
$this->assertEquals('not an array', $result, 'Should return original input if not array');
$result = $integration->filter_services(null);
$this->assertNull($result, 'Should return null input unchanged');
// Test with empty array
$result = $integration->filter_services([]);
$this->assertEquals([], $result, 'Should return empty array unchanged');
}
/**
* Test admin bypass in service filtering
*/
public function test_service_filtering_admin_bypass()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
// Set admin user
$this->set_current_user($this->admin_user_id);
$integration = $this->plugin->kivicare_integration;
// Mock admin context
set_current_screen('edit-post');
$services = [
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999],
['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999]
];
$filtered = $integration->filter_services($services, 999);
$this->assertCount(2, $filtered, 'Admin should see all services including blocked ones');
}
/**
* Test performance with large service datasets
*/
public function test_performance_large_service_dataset()
{
// Create many restrictions
for ($i = 2000; $i <= 2050; $i++) {
$this->create_test_service_restriction($i, 1000, true);
}
// Create large service dataset
$services = [];
for ($i = 1900; $i <= 2100; $i++) {
$services[] = ['id' => $i, 'name' => "Service {$i}", 'doctor_id' => 1000];
}
$integration = $this->plugin->kivicare_integration;
$start_time = microtime(true);
$filtered = $integration->filter_services($services, 1000);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete in reasonable time (under 100ms)
$this->assertLessThan(100, $execution_time, 'Should handle large service datasets efficiently');
// Should filter out blocked services (2000-2050)
$this->assertLessThanOrEqual(150, count($filtered), 'Should filter out blocked services');
// Available services (1900-1999, 2051-2100) should remain
$this->assertGreaterThanOrEqual(150, count($filtered), 'Available services should remain');
}
/**
* Test service filtering with mixed data formats
*/
public function test_service_filtering_mixed_formats()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Mixed array and object format (edge case)
$services = [
['id' => 888, 'name' => 'Service 1', 'doctor_id' => 999], // Array - blocked
(object) ['id' => 887, 'name' => 'Service 2', 'doctor_id' => 999], // Object - available
['id' => 886] // Array missing doctor_id
];
$filtered = $integration->filter_services($services, 999);
// Should handle mixed formats gracefully
$this->assertGreaterThan(0, count($filtered), 'Should handle mixed formats');
// Blocked service should be removed
$has_blocked_service = false;
foreach ($filtered as $service) {
$service_id = is_array($service) ? ($service['id'] ?? 0) : ($service->id ?? 0);
if ($service_id == 888) {
$has_blocked_service = true;
break;
}
}
$this->assertFalse($has_blocked_service, 'Blocked service should not be in filtered results');
}
/**
* Test caching with service filtering
*/
public function test_service_filtering_caching()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$integration = $this->plugin->kivicare_integration;
// Pre-populate cache
set_transient('care_booking_services_blocked_999', [888], 3600);
$services = [
['id' => 888, 'name' => 'Service 1'],
['id' => 887, 'name' => 'Service 2']
];
$start_time = microtime(true);
$filtered = $integration->filter_services($services, 999);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should be very fast with cache
$this->assertLessThan(50, $execution_time, 'Service filtering should be fast with cache');
// Should filter blocked service
$this->assertCount(1, $filtered, 'Should filter blocked service using cache');
}
}

View File

@@ -0,0 +1,374 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for frontend JavaScript graceful degradation (T032)
*
* @package CareBookingBlock
*/
/**
* Test frontend JavaScript enqueuing and configuration
*/
class Test_Frontend_JavaScript extends Care_Booking_Test_Case
{
/**
* Test frontend scripts are enqueued on appropriate pages
*/
public function test_frontend_scripts_enqueued()
{
// Mock KiviCare as active
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare_booking]'
];
$integration = $this->plugin->kivicare_integration;
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// Check if script is registered
$this->assertTrue(wp_script_is('care-booking-frontend', 'registered'),
'Frontend script should be registered');
// Note: wp_script_is('enqueued') might not work in test environment
// as it depends on KiviCare being active and page content detection
}
/**
* Test script localization with correct configuration
*/
public function test_script_localization()
{
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
$integration = $this->plugin->kivicare_integration;
// Mock wp_localize_script to capture data
$localized_data = null;
add_filter('wp_localize_script_care-booking-frontend_careBookingConfig', function($data) use (&$localized_data) {
$localized_data = $data;
return $data;
});
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// If script was enqueued and localized, check the data
if ($localized_data) {
$this->assertArrayHasKey('ajaxurl', $localized_data, 'Should include AJAX URL');
$this->assertArrayHasKey('nonce', $localized_data, 'Should include nonce');
$this->assertArrayHasKey('debug', $localized_data, 'Should include debug flag');
$this->assertArrayHasKey('fallbackEnabled', $localized_data, 'Should include fallback flag');
$this->assertArrayHasKey('retryAttempts', $localized_data, 'Should include retry attempts');
$this->assertArrayHasKey('selectors', $localized_data, 'Should include selectors');
// Check selectors structure
$this->assertArrayHasKey('doctors', $localized_data['selectors'], 'Should include doctor selectors');
$this->assertArrayHasKey('services', $localized_data['selectors'], 'Should include service selectors');
$this->assertArrayHasKey('forms', $localized_data['selectors'], 'Should include form selectors');
}
}
/**
* Test should_load_frontend_scripts logic
*/
public function test_should_load_frontend_scripts_logic()
{
$integration = $this->plugin->kivicare_integration;
// Use reflection to access private method
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('should_load_frontend_scripts');
$method->setAccessible(true);
global $post;
// Test with KiviCare shortcode
$post = (object) [
'ID' => 123,
'post_content' => 'Some content [kivicare] more content'
];
$should_load = $method->invoke($integration);
$this->assertTrue($should_load, 'Should load scripts on pages with kivicare shortcode');
// Test with KiviCare block
$post = (object) [
'ID' => 124,
'post_content' => '<!-- wp:kivicare/booking --><div>Booking form</div><!-- /wp:kivicare/booking -->'
];
$should_load = $method->invoke($integration);
$this->assertTrue($should_load, 'Should load scripts on pages with kivicare block');
// Test with regular content
$post = (object) [
'ID' => 125,
'post_content' => 'Regular page content without KiviCare'
];
$should_load = $method->invoke($integration);
$this->assertFalse($should_load, 'Should not load scripts on regular pages');
// Test with URL parameters
$_GET['kivicare'] = '1';
$should_load = $method->invoke($integration);
$this->assertTrue($should_load, 'Should load scripts when URL contains kivicare parameter');
unset($_GET['kivicare']);
$_GET['booking'] = '1';
$should_load = $method->invoke($integration);
$this->assertTrue($should_load, 'Should load scripts when URL contains booking parameter');
unset($_GET['booking']);
}
/**
* Test frontend script file exists and has correct content structure
*/
public function test_frontend_script_file_structure()
{
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
$this->assertFileExists($script_path, 'Frontend JavaScript file should exist');
$script_content = file_get_contents($script_path);
// Check for main structure
$this->assertStringContainsString('CareBooking', $script_content,
'Should contain CareBooking object');
$this->assertStringContainsString('init:', $script_content,
'Should contain init method');
$this->assertStringContainsString('setupObservers:', $script_content,
'Should contain setupObservers method');
$this->assertStringContainsString('enhanceExistingElements:', $script_content,
'Should contain enhanceExistingElements method');
// Check for graceful degradation features
$this->assertStringContainsString('setupFallbacks:', $script_content,
'Should contain setupFallbacks method');
$this->assertStringContainsString('MutationObserver', $script_content,
'Should use MutationObserver for dynamic content');
$this->assertStringContainsString('ajaxError', $script_content,
'Should handle AJAX errors');
// Check for validation features
$this->assertStringContainsString('validateBookingForm:', $script_content,
'Should contain form validation');
$this->assertStringContainsString('showLoadingState:', $script_content,
'Should contain loading state management');
// Check for offline detection
$this->assertStringContainsString('online offline', $script_content,
'Should handle online/offline events');
}
/**
* Test script dependencies are correct
*/
public function test_script_dependencies()
{
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// Check if jQuery is a dependency
global $wp_scripts;
if (isset($wp_scripts->registered['care-booking-frontend'])) {
$script = $wp_scripts->registered['care-booking-frontend'];
$this->assertContains('jquery', $script->deps, 'Should depend on jQuery');
$this->assertEquals(CARE_BOOKING_BLOCK_VERSION, $script->ver, 'Should use plugin version');
$this->assertTrue($script->extra['in_footer'], 'Should load in footer');
}
}
/**
* Test script configuration values
*/
public function test_script_configuration_values()
{
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
$integration = $this->plugin->kivicare_integration;
// Capture localized data
$captured_data = null;
// Mock wp_localize_script
add_filter('wp_scripts_print_extra_script', function($output, $handle) use (&$captured_data) {
if ($handle === 'care-booking-frontend') {
// Extract config from JavaScript
if (preg_match('/careBookingConfig\s*=\s*({.+?});/', $output, $matches)) {
$captured_data = json_decode($matches[1], true);
}
}
return $output;
}, 10, 2);
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// If we captured data, validate it
if ($captured_data) {
// Check AJAX URL
$this->assertStringContainsString('admin-ajax.php', $captured_data['ajaxurl'],
'AJAX URL should point to admin-ajax.php');
// Check nonce is valid
$this->assertTrue(wp_verify_nonce($captured_data['nonce'], 'care_booking_frontend'),
'Nonce should be valid');
// Check boolean values
$this->assertIsBool($captured_data['debug'], 'Debug should be boolean');
$this->assertIsBool($captured_data['fallbackEnabled'], 'Fallback should be boolean');
// Check numeric values
$this->assertIsNumeric($captured_data['retryAttempts'], 'Retry attempts should be numeric');
$this->assertIsNumeric($captured_data['retryDelay'], 'Retry delay should be numeric');
$this->assertGreaterThan(0, $captured_data['retryAttempts'], 'Should have positive retry attempts');
$this->assertGreaterThan(0, $captured_data['retryDelay'], 'Should have positive retry delay');
// Check selectors
$this->assertIsArray($captured_data['selectors'], 'Selectors should be array');
$this->assertArrayHasKey('doctors', $captured_data['selectors']);
$this->assertArrayHasKey('services', $captured_data['selectors']);
$this->assertArrayHasKey('forms', $captured_data['selectors']);
// Check selector format
$this->assertStringContainsString('.kivicare-doctor', $captured_data['selectors']['doctors'],
'Doctor selectors should include .kivicare-doctor');
$this->assertStringContainsString('.kivicare-service', $captured_data['selectors']['services'],
'Service selectors should include .kivicare-service');
}
}
/**
* Test script only loads when KiviCare is active
*/
public function test_script_kivicare_dependency()
{
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
$integration = $this->plugin->kivicare_integration;
// Mock KiviCare as inactive
$reflection = new ReflectionClass($integration);
$method = $reflection->getMethod('is_kivicare_active');
$method->setAccessible(true);
// The method checks for actual plugin files, so in test environment
// it will likely return false, which is correct behavior
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// Script should not be enqueued if KiviCare is not active
// This is mainly to test the logic path doesn't cause errors
$this->assertTrue(true, 'Script enqueuing should handle inactive KiviCare gracefully');
}
/**
* Test admin area script exclusion
*/
public function test_admin_area_script_exclusion()
{
// Mock admin area
set_current_screen('edit-post');
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
// Trigger script enqueuing
do_action('wp_enqueue_scripts');
// Script should not be enqueued in admin area
$this->assertFalse(wp_script_is('care-booking-frontend', 'enqueued'),
'Frontend script should not be enqueued in admin area');
}
/**
* Test script file is minified in production
*/
public function test_script_optimization()
{
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
$script_content = file_get_contents($script_path);
// In development, script should be readable
if (defined('WP_DEBUG') && WP_DEBUG) {
$this->assertGreaterThan(100, substr_count($script_content, "\n"),
'Development script should have line breaks');
$this->assertStringContainsString(' ', $script_content,
'Development script should have indentation');
}
// Script should have proper structure regardless of minification
$this->assertStringContainsString('CareBooking', $script_content,
'Script should contain CareBooking object');
$this->assertStringContainsString('jQuery', $script_content,
'Script should reference jQuery');
}
/**
* Test error handling in script enqueuing
*/
public function test_script_enqueuing_error_handling()
{
global $post;
$post = (object) [
'ID' => 123,
'post_content' => '[kivicare]'
];
// Mock file not found scenario by temporarily renaming the file
$script_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js';
$temp_path = CARE_BOOKING_BLOCK_PLUGIN_DIR . 'public/js/frontend.js.temp';
if (file_exists($script_path)) {
rename($script_path, $temp_path);
}
// Trigger script enqueuing - should not cause fatal errors
ob_start();
do_action('wp_enqueue_scripts');
$output = ob_get_clean();
// Restore file
if (file_exists($temp_path)) {
rename($temp_path, $script_path);
}
// Should not produce PHP errors
$this->assertStringNotContainsString('Fatal error', $output,
'Should handle missing script file gracefully');
$this->assertStringNotContainsString('Warning:', $output,
'Should handle missing script file without warnings');
}
}

View File

@@ -0,0 +1,425 @@
/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Integration test for KiviCare service filtering
*
* @package CareBookingBlock
*/
/**
* Test KiviCare service filtering functionality
*/
class Test_Service_Filtering extends Care_Booking_Test_Case
{
/**
* Test KiviCare service filter hook is registered
*/
public function test_service_filter_hook_registered()
{
$this->assertTrue(has_filter('kc_get_services_by_doctor'), 'Service filter hook should be registered');
// Verify correct priority
$priority = has_filter('kc_get_services_by_doctor');
$this->assertEquals(10, $priority, 'Filter should have priority 10');
}
/**
* Test service filtering removes blocked services for specific doctor
*/
public function test_service_filtering_removes_blocked_services()
{
// Create service restrictions for doctor 999
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
$this->create_test_service_restriction(887, 999, false); // Don't block service 887 for doctor 999
$this->create_test_service_restriction(886, 998, true); // Block service 886 for different doctor 998
// Mock KiviCare service list for doctor 999
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999],
['id' => 885, 'name' => 'Exame Rotina', 'doctor_id' => 999]
];
$doctor_id = 999;
// Apply the filter
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, $doctor_id);
// Verify blocked service was removed for this doctor
$service_ids = array_column($filtered_services, 'id');
$this->assertNotContains(888, $service_ids, 'Blocked service should be removed for this doctor');
$this->assertContains(887, $service_ids, 'Non-blocked service should remain');
$this->assertContains(885, $service_ids, 'Service without restriction should remain');
// Verify structure is preserved
$this->assertCount(2, $filtered_services, 'Should return 2 services (excluding blocked one)');
foreach ($filtered_services as $service) {
$this->assertArrayHasKey('id', $service);
$this->assertArrayHasKey('name', $service);
$this->assertArrayHasKey('doctor_id', $service);
$this->assertEquals(999, $service['doctor_id'], 'All services should belong to doctor 999');
}
}
/**
* Test service filtering is doctor-specific
*/
public function test_service_filtering_is_doctor_specific()
{
// Create service restrictions for different doctors
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
$this->create_test_service_restriction(888, 998, false); // Don't block service 888 for doctor 998
$services_doctor_999 = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
$services_doctor_998 = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
];
// Filter for doctor 999 (service 888 should be blocked)
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services_doctor_999, 999);
$service_ids_999 = array_column($filtered_999, 'id');
$this->assertNotContains(888, $service_ids_999, 'Service 888 should be blocked for doctor 999');
$this->assertContains(887, $service_ids_999, 'Service 887 should remain for doctor 999');
// Filter for doctor 998 (service 888 should NOT be blocked)
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_doctor_998, 998);
$service_ids_998 = array_column($filtered_998, 'id');
$this->assertContains(888, $service_ids_998, 'Service 888 should NOT be blocked for doctor 998');
$this->assertContains(887, $service_ids_998, 'Service 887 should remain for doctor 998');
}
/**
* Test service filtering preserves original array when no restrictions
*/
public function test_service_filtering_preserves_original_when_no_restrictions()
{
// No restrictions created for doctor 999
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
$this->assertEquals($services, $filtered_services, 'Original array should be preserved when no restrictions');
$this->assertCount(2, $filtered_services, 'All services should remain when no restrictions');
}
/**
* Test service filtering with empty input array
*/
public function test_service_filtering_with_empty_input()
{
$this->create_test_service_restriction(888, 999, true);
$empty_services = [];
$filtered_services = apply_filters('kc_get_services_by_doctor', $empty_services, 999);
$this->assertEmpty($filtered_services, 'Empty input should return empty output');
$this->assertIsArray($filtered_services, 'Should return array even with empty input');
}
/**
* Test service filtering with missing doctor_id parameter
*/
public function test_service_filtering_with_missing_doctor_id()
{
$this->create_test_service_restriction(888, 999, true);
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
// Call filter without doctor_id parameter
$filtered_services = apply_filters('kc_get_services_by_doctor', $services);
// Should return original array when doctor_id is missing
$this->assertEquals($services, $filtered_services, 'Should return original array when doctor_id is missing');
}
/**
* Test service filtering with malformed input
*/
public function test_service_filtering_with_malformed_input()
{
$this->create_test_service_restriction(888, 999, true);
// Test with non-array input
$non_array_input = "invalid_input";
$filtered_result = apply_filters('kc_get_services_by_doctor', $non_array_input, 999);
$this->assertEquals($non_array_input, $filtered_result, 'Non-array input should be returned unchanged');
// Test with services missing required fields
$malformed_services = [
['id' => 888, 'name' => 'Consulta Geral'], // Missing doctor_id
['name' => 'Revisão', 'doctor_id' => 999], // Missing id
['id' => 885, 'name' => 'Exame Rotina', 'doctor_id' => 999] // Complete
];
$filtered_services = apply_filters('kc_get_services_by_doctor', $malformed_services, 999);
// Should handle malformed entries gracefully
$this->assertIsArray($filtered_services, 'Should return array even with malformed input');
// Complete entry should be processed correctly
$service_ids = array_column($filtered_services, 'id');
$this->assertNotContains(888, $service_ids, 'Blocked service should be removed even with malformed entries');
$this->assertContains(885, $service_ids, 'Valid non-blocked service should remain');
}
/**
* Test service filtering uses cache for performance
*/
public function test_service_filtering_uses_cache()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
$this->create_test_service_restriction(887, 999, true);
// Manually set cache to test cache usage
$cached_blocked_services = [888, 887];
set_transient('care_booking_services_blocked_999', $cached_blocked_services, 3600);
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999],
['id' => 886, 'name' => 'Exame Rotina', 'doctor_id' => 999]
];
// Apply filter multiple times to test cache efficiency
$start_time = microtime(true);
for ($i = 0; $i < 5; $i++) {
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
}
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should be very fast with caching (under 10ms for 5 operations)
$this->assertLessThan(10, $execution_time, 'Multiple filter operations should be fast with caching');
// Verify filtering worked correctly
$service_ids = array_column($filtered_services, 'id');
$this->assertNotContains(888, $service_ids);
$this->assertNotContains(887, $service_ids);
$this->assertContains(886, $service_ids);
}
/**
* Test service filtering falls back to database when cache miss
*/
public function test_service_filtering_database_fallback()
{
// Create test restrictions
$this->create_test_service_restriction(888, 999, true);
// Ensure cache is clear
delete_transient('care_booking_services_blocked_999');
$this->assertFalse(get_transient('care_booking_services_blocked_999'), 'Cache should be empty');
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
// Should still work correctly without cache
$service_ids = array_column($filtered_services, 'id');
$this->assertNotContains(888, $service_ids, 'Should filter correctly even without cache');
$this->assertContains(887, $service_ids);
// Cache should be populated after database query
$this->assertNotFalse(get_transient('care_booking_services_blocked_999'), 'Cache should be populated after query');
}
/**
* Test service filtering performance with large dataset
*/
public function test_service_filtering_performance_large_dataset()
{
$doctor_id = 999;
// Create multiple restrictions for this doctor
for ($i = 1000; $i <= 1050; $i++) {
$this->create_test_service_restriction($i, $doctor_id, $i % 2 === 0); // Block even IDs
}
// Create large service dataset
$services = [];
for ($i = 1000; $i <= 1100; $i++) {
$services[] = [
'id' => $i,
'name' => "Service Test $i",
'doctor_id' => $doctor_id
];
}
$start_time = microtime(true);
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, $doctor_id);
$end_time = microtime(true);
$execution_time = ($end_time - $start_time) * 1000;
// Should complete filtering in reasonable time (under 100ms)
$this->assertLessThan(100, $execution_time, 'Large dataset filtering should complete under 100ms');
// Verify correct filtering
$filtered_count = count($filtered_services);
$this->assertGreaterThan(0, $filtered_count, 'Should return some services');
$this->assertLessThan(count($services), $filtered_count, 'Some services should be filtered out');
// Verify no blocked services remain
$filtered_ids = array_column($filtered_services, 'id');
foreach ($filtered_ids as $id) {
if ($id >= 1000 && $id <= 1050) {
$this->assertTrue($id % 2 !== 0, "Service $id should not be blocked (odd IDs only)");
}
}
}
/**
* Test service filtering with multiple doctors simultaneously
*/
public function test_service_filtering_multiple_doctors()
{
// Create different restrictions for different doctors
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
$this->create_test_service_restriction(888, 998, false); // Don't block service 888 for doctor 998
$this->create_test_service_restriction(887, 999, false); // Don't block service 887 for doctor 999
$this->create_test_service_restriction(887, 998, true); // Block service 887 for doctor 998
$services_both = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
// Test filtering for doctor 999
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services_both, 999);
$ids_999 = array_column($filtered_999, 'id');
$this->assertNotContains(888, $ids_999, 'Service 888 blocked for doctor 999');
$this->assertContains(887, $ids_999, 'Service 887 allowed for doctor 999');
// Change services to belong to doctor 998
$services_998 = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
];
// Test filtering for doctor 998
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_998, 998);
$ids_998 = array_column($filtered_998, 'id');
$this->assertContains(888, $ids_998, 'Service 888 allowed for doctor 998');
$this->assertNotContains(887, $ids_998, 'Service 887 blocked for doctor 998');
}
/**
* Test service filtering preserves array keys and structure
*/
public function test_service_filtering_preserves_structure()
{
$this->create_test_service_restriction(888, 999, true);
$services = [
'first_service' => ['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999, 'duration' => 30],
'second_service' => ['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999, 'duration' => 15],
'third_service' => ['id' => 886, 'name' => 'Exame Rotina', 'doctor_id' => 999, 'duration' => 45]
];
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
// Should preserve associative keys
$this->assertArrayHasKey('second_service', $filtered_services);
$this->assertArrayHasKey('third_service', $filtered_services);
$this->assertArrayNotHasKey('first_service', $filtered_services, 'Blocked service key should be removed');
// Should preserve all fields in remaining services
$this->assertArrayHasKey('duration', $filtered_services['second_service']);
$this->assertEquals(15, $filtered_services['second_service']['duration']);
$this->assertArrayHasKey('duration', $filtered_services['third_service']);
$this->assertEquals(45, $filtered_services['third_service']['duration']);
}
/**
* Test service filtering handles database errors gracefully
*/
public function test_service_filtering_handles_database_errors()
{
// Mock database error
global $wpdb;
$original_prefix = $wpdb->prefix;
$wpdb->prefix = 'invalid_prefix_';
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
// Filter should handle database error gracefully
$filtered_services = apply_filters('kc_get_services_by_doctor', $services, 999);
// Restore prefix
$wpdb->prefix = $original_prefix;
// Should return original array when database error occurs
$this->assertEquals($services, $filtered_services, 'Should return original array when database error occurs');
// No PHP errors should be thrown
$this->assertTrue(true, 'Filter should handle database errors without throwing exceptions');
}
/**
* Test service filtering cache isolation between doctors
*/
public function test_service_filtering_cache_isolation()
{
// Create restrictions for different doctors
$this->create_test_service_restriction(888, 999, true);
$this->create_test_service_restriction(888, 998, false);
$services = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 999],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 999]
];
// Filter for doctor 999 (should populate cache for doctor 999)
$filtered_999 = apply_filters('kc_get_services_by_doctor', $services, 999);
$cache_999 = get_transient('care_booking_services_blocked_999');
$this->assertNotFalse($cache_999, 'Cache should be set for doctor 999');
// Filter for doctor 998 (should populate separate cache)
$services_998 = [
['id' => 888, 'name' => 'Consulta Geral', 'doctor_id' => 998],
['id' => 887, 'name' => 'Revisão', 'doctor_id' => 998]
];
$filtered_998 = apply_filters('kc_get_services_by_doctor', $services_998, 998);
$cache_998 = get_transient('care_booking_services_blocked_998');
$this->assertNotFalse($cache_998, 'Cache should be set for doctor 998');
// Caches should be different
$this->assertNotEquals($cache_999, $cache_998, 'Cache should be isolated between doctors');
// Verify filtering results are correct and different
$ids_999 = array_column($filtered_999, 'id');
$ids_998 = array_column($filtered_998, 'id');
$this->assertNotContains(888, $ids_999, 'Service 888 should be blocked for doctor 999');
$this->assertContains(888, $ids_998, 'Service 888 should NOT be blocked for doctor 998');
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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