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:
508
care-booking-block/tests/unit/test-ajax-bulk-update.php
Normal file
508
care-booking-block/tests/unit/test-ajax-bulk-update.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user