agent(); $token = $this->token($agent); $this->agents->update($agent, ['password' => 'new-password']); $this->assertSame(0, AgentToken::query()->where('agent_id', $agent->id)->count()); $request = Request::create('/agent/profile'); $request->headers->set('Authorization', 'Bearer ' . $token); $response = (new AgentAuthMiddleware())->handle($request, fn () => new JsonResponse(['code' => 0])); $this->assertSame(101011, $response->getData(true)['code']); } public function test_disable_then_enable_does_not_restore_old_login(): void { $agent = $this->agent(); $this->token($agent); $this->agents->update($agent, ['status' => 0]); $this->agents->update($agent, ['status' => 1]); $this->assertSame(0, AgentToken::query()->count()); } public function test_non_sensitive_edit_preserves_login(): void { $agent = $this->agent(); $this->token($agent); $this->agents->update($agent, ['real_name' => 'new name']); $this->assertSame(1, AgentToken::query()->count()); } public function test_finance_counts_include_only_successful_in_range_provider_orders(): void { $agent = $this->agent(); DB::table('users')->insert(['member_id' => 'm1', 'username' => 'member', 'agent_id' => $agent->id, 'created_at' => '2026-01-01 00:00:00']); $base = ['member_id' => 'm1', 'amount' => 10, 'created_at' => now()]; DB::table('recharges')->insert($base + ['status' => 1]); DB::table('withdraws')->insert($base + ['status' => 1]); foreach ([PaymentOrderService::TYPE_PAY, PaymentOrderService::TYPE_PAYOUT, PaymentOrderService::TYPE_SELF_PAYOUT] as $type) { DB::table('payment_orders')->insert($base + ['type' => $type, 'status' => PaymentOrderService::STATUS_SUCCESS]); } DB::table('payment_orders')->insert($base + ['type' => PaymentOrderService::TYPE_PAY, 'status' => -99]); DB::table('payment_orders')->insert(array_merge($base, ['type' => PaymentOrderService::TYPE_PAY, 'status' => PaymentOrderService::STATUS_SUCCESS, 'created_at' => '2026-08-01 00:00:00'])); DB::table('payment_orders')->insert(array_merge($base, ['type' => PaymentOrderService::TYPE_PAY, 'status' => PaymentOrderService::STATUS_SUCCESS, 'member_id' => 'unrelated'])); $result = (new AgentMemberService(new AgentReportService()))->finance($agent, []); $member = $result['list'][0]; $this->assertSame(2, $member->recharge_count); $this->assertSame(3, $member->withdraw_count); $this->assertSame('20.0000', $member->recharge_amount); $this->assertSame('30.0000', $member->withdraw_amount); } public function test_login_logs_filter_login_time_not_record_creation_time(): void { $agent = $this->agent(); AgentLoginLog::query()->create(['agent_id' => $agent->id, 'username' => $agent->username, 'login_at' => '2026-09-06 23:59:59', 'created_at' => '2026-09-07 00:00:01']); $controller = new AdminAgent(); $request = Request::create('/admin/agent/login-logs', 'GET', ['start_date' => '2026-09-06', 'end_date' => '2026-09-06']); $this->assertSame(1, $controller->loginLogs($request)->getData(true)['data']['total']); } private function fundedAccount(Agent $agent): AgentWithdrawAccount { Agent::query()->whereKey($agent->id)->update(['balance' => 100, 'realtime_balance' => 100]); return AgentWithdrawAccount::query()->create(['agent_id' => $agent->id, 'type' => 'usdt', 'network' => 'TRC20', 'account' => 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE', 'status' => 1]); } public function test_withdrawal_uses_current_password_not_stale_request_model(): void { $agent = $this->agent(); $account = $this->fundedAccount($agent); $this->agents->update($agent, ['withdraw_password' => 'withdraw-new']); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('提现密码错误'); (new AgentWithdrawalService())->request($agent, $account->id, '10', 'withdraw-old', 'stale'); } public function test_disabled_agent_cannot_submit_with_stale_request_model(): void { $agent = $this->agent(); $account = $this->fundedAccount($agent); $this->agents->update($agent, ['status' => 0]); $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('代理账号已禁用'); (new AgentWithdrawalService())->request($agent, $account->id, '10', 'withdraw-old', 'disabled'); } public function test_withdrawal_retry_and_rejection_move_funds_once(): void { $agent = $this->agent(); $account = $this->fundedAccount($agent); $service = new AgentWithdrawalService(); $one = $service->request($agent, $account->id, '10', 'withdraw-old', 'retry'); $two = $service->request($agent, $account->id, '10', 'withdraw-old', 'retry'); $this->assertSame($one->id, $two->id); $this->assertSame('90.0000', $agent->fresh()->balance); $this->assertSame('10.0000', $agent->fresh()->frozen_balance); $service->audit($one->id, 'reject', 1); $this->assertSame('100.0000', $agent->fresh()->balance); $this->assertSame('0.0000', $agent->fresh()->frozen_balance); $this->assertSame(1, AgentWithdrawal::query()->count()); $this->assertSame(2, AgentBalanceLog::query()->count()); } public function test_member_finance_does_not_include_other_agent_members(): void { $agent = $this->agent(); $other = $this->agent(); foreach ([$agent, $agent, $other] as $i => $owner) { DB::table('users')->insert(['member_id' => 'm' . $i, 'username' => 'member' . $i, 'agent_id' => $owner->id, 'created_at' => now()]); } $service = new AgentMemberService(new AgentReportService()); $this->assertSame(2, $service->finance($agent, [])['total']); $this->assertSame(0, $service->finance($agent, ['agent_id' => $other->id])['total']); $this->assertSame(1, $service->finance($other, [])['total']); } public function test_business_errors_keep_existing_response_contract(): void { $controller = new class extends AgentApiController { public function trigger() { return $this->execute(fn () => throw new \RuntimeException('代理余额不足')); } }; $data = $controller->trigger()->getData(true); $this->assertSame(['code', 'timestamp', 'msg', 'data'], array_keys($data)); $this->assertSame(-3, $data['code']); $this->assertSame('代理余额不足', $data['msg']); } public function test_settlement_command_rejects_invalid_and_unfinished_dates(): void { $handler = $this->createMock(ExceptionHandler::class); $handler->expects($this->exactly(3))->method('report'); $this->app->instance(ExceptionHandler::class, $handler); $reports = $this->createMock(AgentReportService::class); $reports->expects($this->never())->method('refreshDaily'); $this->app->instance(AgentReportService::class, $reports); $commissions = $this->createMock(AgentCommissionService::class); $commissions->expects($this->never())->method('settle'); $this->app->instance(AgentCommissionService::class, $commissions); foreach (['2026-02-30', '2026-09-07', '2026-09-08'] as $date) { $command = new \App\Console\Commands\AgentDailySettle(); $command->setLaravel($this->app); $tester = new \Symfony\Component\Console\Tester\CommandTester($command); $this->assertSame(1, $tester->execute(['date' => $date])); } } public function test_sql_errors_are_logged_but_not_returned_to_frontend(): void { $handler = $this->createMock(ExceptionHandler::class); $handler->expects($this->once())->method('report'); $this->app->instance(ExceptionHandler::class, $handler); $controller = new class extends AgentApiController { public function trigger() { return $this->execute(fn () => throw new QueryException('select sensitive_table', ['secret'], new \PDOException('internal database error'))); } }; $data = $controller->trigger()->getData(true); $this->assertSame(-3, $data['code']); $this->assertSame('系统处理失败,请稍后重试', $data['msg']); $this->assertSame([], $data['data']); } }