Skip to content

Commit 447acc7

Browse files
test(wordpress): assert restored controls meet retention prerequisites
1 parent 017b6f5 commit 447acc7

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

‎integrations/wordpress/tests/retention.php‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ function ct_test_exists( $job ) {
118118
ct_test_assert( $recent && null !== $recent->result_json && get_post( $recent_report['request_id'] ), 'Old legacy evidence was deleted before recently delivered findings reached their separate deadline.' );
119119
ct_test_assert( ct_scan_findings_retain_until( $recent ) === ( new DateTimeImmutable( $delivery_at, new DateTimeZone( 'UTC' ) ) )->modify( '+366 days' )->format( 'Y-m-d H:i:s' ), 'Findings used the execution-stop timestamp instead of confirmed delivery.' );
120120

121+
/** Positive restored controls must have valid stored dates before testing identity, holds or paging. */
122+
function ct_test_due_tombstone_deadline( $row, $label ) {
123+
ct_test_assert( is_object( $row ), $label . ': restored row is missing.' );
124+
foreach ( array( 'created_at', 'closed_at', 'retain_until' ) as $field ) {
125+
ct_test_assert( property_exists( $row, $field ) && ct_scan_valid_datetime( $row->$field ), $label . ': valid ' . $field . ' is required for a positive tombstone control.' );
126+
}
127+
ct_test_assert( $row->closed_at >= $row->created_at, $label . ': closure precedes creation.' );
128+
ct_test_assert( ct_scan_tombstone_deadline_passed( $row, ct_scan_utc_now() ), $label . ': restored row is not past its current policy deadline.' );
129+
}
130+
121131
// ---- Evidence deadlines --------------------------------------------------------------------------------------
122132
$edge_keep = ct_test_aged_job( 'ret-keep.example.net', 'failed', '-2 years -11 months', '-1 day' );
123133
$edge_del = ct_test_aged_job( 'ret-del.example.net', 'failed', '-3 years -1 day', '-1 day' );
@@ -152,10 +162,12 @@ function ct_test_exists( $job ) {
152162
update_post_meta( $edge_del['request_id'], '_ct_scan_identity', $stone->identity );
153163
ct_test_assert( 32 === strlen( (string) $stone->identity ), 'The tombstone did not keep the opaque identity.' );
154164
ct_test_assert( ct_test_exists( $edge_del ) && get_post( $edge_del['request_id'] ), 'Fixture failed to restore the deleted records.' );
165+
ct_test_due_tombstone_deadline( ct_scan_row( $edge_del['job_id'] ), 'Complete restored backup' );
155166
ct_test_maintain();
156167
ct_test_assert( ! ct_test_exists( $edge_del ) && ! get_post( $edge_del['request_id'] ), 'A restored backup resurrected deleted records.' );
157168
// A different job that merely reuses the identifier is not touched.
158169
$wpdb->insert( $jobs, array_merge( $ghost, array( 'created_at' => ct_test_old( '-3 years -2 days' ), 'status' => 'failed', 'terms_accepted_at' => ct_test_old( '-3 years -2 days' ), 'authorization_expires_at' => ct_test_old( '+30 days' ), 'contact_email' => 'other@example.com' ) ) );
170+
ct_test_due_tombstone_deadline( ct_scan_row( $edge_del['job_id'] ), 'Different-identity control' );
159171
ct_test_maintain();
160172
ct_test_assert( ct_test_exists( $edge_del ), 'A job that is not the deleted one matched the tombstone.' );
161173
$wpdb->delete( $jobs, array( 'job_id' => $edge_del['job_id'] ) );
@@ -220,6 +232,8 @@ function ct_test_exists( $job ) {
220232
ct_test_assert( ! ct_test_exists( $restore_held ), 'Fixture expected the job to be deleted first.' );
221233
$rstone = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . ct_scan_tombstones_table() . ' WHERE job_id = %d', $restore_held['job_id'] ) );
222234
$wpdb->insert( $jobs, array_merge( $restore_held_backup, array( 'retention_checked_at' => ct_scan_utc_now(), 'hold_reason' => 'Preserving this restored record for a dispute.', 'hold_until' => ct_test_old( '+30 days' ) ) ) );
235+
ct_test_due_tombstone_deadline( ct_scan_row( $restore_held['job_id'] ), 'Held restored backup' );
236+
ct_test_assert( ct_scan_job_on_hold( ct_scan_row( $restore_held['job_id'] ), ct_scan_utc_now() ), 'Held restored control has no effective hold.' );
223237
ct_test_maintain();
224238
ct_test_assert( ct_test_exists( $restore_held ), 'A restored job under a hold was deleted by its tombstone.' );
225239
$wpdb->delete( $jobs, array( 'job_id' => $restore_held['job_id'] ) );
@@ -533,6 +547,8 @@ function ct_test_exists( $job ) {
533547
}
534548
ct_test_assert( 1 === $wpdb->insert( $jobs, array_merge( $restored_base, array( 'job_id' => 887000200, 'request_id' => 886000200 ) ) ), 'Fixture insert of a restored row failed: ' . $wpdb->last_error );
535549
$wpdb->insert( ct_scan_tombstones_table(), array( 'job_id' => 887000200, 'request_id' => 886000200, 'scope_mac' => ct_scan_tombstone_mac( $restored_base['scope_hash'] ), 'created_at' => $restored_base['created_at'], 'deleted_at' => ct_test_old( '-1 day' ), 'reason' => 'retention', 'identity' => bin2hex( random_bytes( 16 ) ) ) );
550+
ct_test_due_tombstone_deadline( ct_scan_row( 887000200 ), 'Paging positive control' );
551+
ct_test_assert( ! ct_scan_job_on_hold( ct_scan_row( 887000200 ), ct_scan_utc_now() ) && ! ct_scan_job_holds_lease( ct_scan_row( 887000200 ) ), 'Paging positive control is held or leased.' );
536552
ct_scan_retention_run( true );
537553
$starve_left = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$jobs} WHERE job_id BETWEEN 887000100 AND 887000155" );
538554
ct_test_assert( ! ct_scan_row( 887000200 ) && 56 === $starve_left, "Held or leased restored rows starved a later restored row, or were deleted ($starve_left of 56 kept)." );
@@ -749,6 +765,7 @@ function ct_test_make_due( $job ) {
749765
wp_insert_post( array( 'import_id' => $recycled['request_id'], 'post_type' => 'ct_promo_request', 'post_status' => 'private', 'post_title' => 'Integration fixture request', 'post_content' => 'another record' ) );
750766
update_post_meta( $recycled['request_id'], '_ct_scan_identity', str_repeat( 'e', 32 ) );
751767
ct_test_assert( ct_test_exists( $recycled ) && get_post( $recycled['request_id'] ), 'Fixture failed to restore the row and recycle the post id.' );
768+
ct_test_due_tombstone_deadline( ct_scan_row( $recycled['job_id'] ), 'Recycled-post positive row control' );
752769
ct_test_maintain();
753770
ct_test_assert( ! ct_test_exists( $recycled ) && get_post( $recycled['request_id'] ), 'A restored row survived, or its tombstone deleted a post that belongs to another record.' );
754771
wp_delete_post( $recycled['request_id'], true );
@@ -768,6 +785,7 @@ function ct_test_make_due( $job ) {
768785
foreach ( $ghost_rows as $g ) {
769786
$g['retention_checked_at'] = ct_scan_utc_now(); // examined just now: only the tombstone loop reaches these
770787
$wpdb->insert( $jobs, $g );
788+
ct_test_due_tombstone_deadline( ct_scan_row( $g['job_id'] ), 'Shared-budget restored control' );
771789
}
772790
$budget_jobs = array();
773791
for ( $i = 0; $i < 45; $i++ ) {
@@ -813,14 +831,22 @@ function ct_test_make_due( $job ) {
813831
// running scan): that would free the one-at-a-time gate while its traffic may be in flight. Once the lease ends, it is.
814832
foreach ( array( 'control' => array( 'status' => 'awaiting_control', 'control_required' => 1, 'control_state' => 'checking', 'control_attempts' => 1, 'control_lease_hash' => str_repeat( 'c', 64 ), 'control_lease_until' => ct_test_old( '+2 minutes' ) ), 'scan' => array( 'status' => 'running', 'attempts' => 1, 'lease_hash' => str_repeat( 'b', 64 ), 'lease_until' => ct_test_old( '+2 minutes' ) ) ) as $kind => $leased ) {
815833
$gone = ct_test_aged_job( "ret-restore-{$kind}.example.net", 'failed', '-3 years -1 day', '-1 day' );
834+
ct_test_make_due( $gone );
835+
$gone_backup = (array) ct_scan_row( $gone['job_id'] );
816836
ct_test_maintain();
817837
ct_test_assert( ! ct_test_exists( $gone ), 'Fixture expected the job to be deleted first.' );
818838
$gstone = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . ct_scan_tombstones_table() . ' WHERE job_id = %d', $gone['job_id'] ) );
819-
$wpdb->insert( $jobs, array_merge( array( 'job_id' => $gone['job_id'], 'request_id' => $gone['request_id'], 'target_origin' => "https://ret-restore-{$kind}.example.net/", 'scope_hash' => hash( 'sha256', "https://ret-restore-{$kind}.example.net/" ), 'terms_version' => '2026-10-21-r2', 'terms_hash' => str_repeat( 'a', 64 ), 'terms_accepted_at' => ct_test_old( '-3 years -1 day' ), 'authorization_expires_at' => ct_test_old( '+30 days' ), 'contact_email' => 'restored@example.com', 'contact_name' => 'Restored', 'referrer' => 'direct', 'created_at' => $gstone->created_at ), $leased ) );
820-
ct_test_maintain();
839+
$wpdb->insert( $jobs, array_merge( $gone_backup, $leased ) );
840+
ct_test_due_tombstone_deadline( ct_scan_row( $gone['job_id'] ), 'Leased restored control' );
841+
$lease_budget = ct_scan_retention_batch_limit();
842+
ct_scan_apply_tombstones( ct_scan_utc_now(), microtime( true ) + 3, $lease_budget );
821843
ct_test_assert( ct_test_exists( $gone ), "A restored row holding a live {$kind} lease was deleted by its tombstone mid-lease." );
822844
$wpdb->update( $jobs, 'control' === $kind ? array( 'control_lease_until' => ct_test_old( '-1 minute' ) ) : array( 'lease_until' => ct_test_old( '-1 minute' ) ), array( 'job_id' => $gone['job_id'] ) );
823-
ct_test_maintain();
845+
ct_test_assert( ct_scan_lock_acquire(), 'Lease-expiry fixture could not acquire the queue lock.' );
846+
try { ct_scan_maintenance_locked(); } finally { ct_scan_lock_release(); }
847+
ct_test_due_tombstone_deadline( ct_scan_row( $gone['job_id'] ), 'Expired-lease restored control' );
848+
$lease_budget = ct_scan_retention_batch_limit();
849+
ct_scan_apply_tombstones( ct_scan_utc_now(), microtime( true ) + 3, $lease_budget );
824850
ct_test_assert( ! ct_test_exists( $gone ), "A restored row was not deleted again after its {$kind} lease ended." );
825851
}
826852

0 commit comments

Comments
 (0)