create([ 'site_code' => 'main', 'site_player_id' => 'reconcile-1', 'username' => null, 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); TransferOrder::query()->create([ 'transfer_no' => 'TO_missing_txn', 'player_id' => $player->id, 'direction' => 'out', 'currency_code' => 'NPR', 'amount' => 300, 'idempotent_key' => 'reconcile-missing', 'status' => 'success', 'external_request_payload' => null, 'external_response_payload' => null, 'external_ref_no' => 'MAIN-001', 'fail_reason' => null, 'finished_at' => now(), ]); TransferOrder::query()->create([ 'transfer_no' => 'TO_pending', 'player_id' => $player->id, 'direction' => 'out', 'currency_code' => 'NPR', 'amount' => 180, 'idempotent_key' => 'reconcile-pending', 'status' => 'pending_reconcile', 'external_request_payload' => null, 'external_response_payload' => null, 'external_ref_no' => null, 'fail_reason' => 'main_site_timeout', 'finished_at' => null, ]); $this->artisan('lottery:wallet-transfer-reconcile --lookback-hours=24 --stale-minutes=15 --limit=50') ->assertExitCode(0); $job = ReconcileJob::query()->where('reconcile_type', 'wallet_transfer')->latest('id')->first(); expect($job)->not->toBeNull() ->and($job?->summary_json['item_count'] ?? null)->toBe(2) ->and($job?->summary_json['mismatch_count'] ?? null)->toBe(2) ->and($job?->items()->count())->toBe(2); $statuses = $job?->items()->pluck('status')->all() ?? []; expect($statuses)->toContain('missing_wallet_txn', 'pending_reconcile'); });