flushChatRoomRedisState(); } /** * 测试座驾列表返回上架座驾、当前座驾和购买记录。 */ public function test_items_returns_ride_items_and_purchase_state(): void { $user = User::factory()->create(); $ride = $this->createRide(['name' => '歼-35测试座驾', 'slug' => 'ride_j35']); UserRidePurchase::create([ 'user_id' => $user->id, 'ride_id' => $ride->id, 'status' => 'active', 'price_paid' => 18888, 'expires_at' => now()->addDays(3), ]); $response = $this->actingAs($user)->getJson(route('rides.items')); $response->assertOk() ->assertJsonFragment(['slug' => 'ride_j35']) ->assertJsonPath('current_ride.item.slug', 'ride_j35') ->assertJsonPath('purchases.0.item.slug', 'ride_j35'); } /** * 测试金币不足时不能购买座驾。 */ public function test_cannot_buy_ride_when_balance_is_not_enough(): void { $user = User::factory()->create(['jjb' => 100]); $room = Room::create(['room_name' => '座驾余额房']); $this->joinRoom($user, $room); $ride = $this->createRide(['price' => 18888]); $response = $this->actingAs($user)->postJson(route('rides.buy'), [ 'item_id' => $ride->id, 'room_id' => $room->id, ]); $response->assertStatus(400) ->assertJsonPath('status', 'error'); $this->assertSame(100, (int) $user->fresh()->jjb); $this->assertDatabaseMissing('user_ride_purchases', [ 'user_id' => $user->id, 'ride_id' => $ride->id, ]); $this->assertDatabaseMissing('user_currency_logs', [ 'user_id' => $user->id, 'source' => CurrencySource::RIDE_BUY->value, ]); } /** * 测试购买座驾会扣金币并生成当前激活记录。 */ public function test_can_buy_ride_and_activate_it(): void { $user = User::factory()->create(['jjb' => 30000]); $room = Room::create(['room_name' => '座驾购买房']); $this->joinRoom($user, $room); $ride = $this->createRide(['slug' => 'ride_99a', 'price' => 18888, 'duration_days' => 7]); $response = $this->actingAs($user)->postJson(route('rides.buy'), [ 'item_id' => $ride->id, 'room_id' => $room->id, ]); $response->assertOk() ->assertJsonPath('status', 'success') ->assertJsonPath('current_ride.item.slug', 'ride_99a') ->assertJsonPath('jjb', 11112); $this->assertDatabaseHas('user_ride_purchases', [ 'user_id' => $user->id, 'ride_id' => $ride->id, 'status' => 'active', 'price_paid' => 18888, ]); $this->assertDatabaseHas('user_currency_logs', [ 'user_id' => $user->id, 'currency' => 'gold', 'amount' => -18888, 'balance_after' => 11112, 'source' => CurrencySource::RIDE_BUY->value, 'remark' => '购买聊天室座驾:测试座驾', 'room_id' => $room->id, ]); $messages = app(\App\Services\ChatStateService::class)->getNewMessages($room->id, 0); $purchaseNotice = collect($messages)->first(fn (array $message): bool => ($message['action'] ?? '') === 'ride_purchase'); $this->assertNotNull($purchaseNotice); $this->assertSame('系统传音', $purchaseNotice['from_user']); $this->assertStringContainsString('【座驾】', $purchaseNotice['content']); $this->assertStringContainsString($user->username, $purchaseNotice['content']); $this->assertStringContainsString('测试座驾', $purchaseNotice['content']); $this->assertStringContainsString('openRideModal()', $purchaseNotice['content']); $this->assertStringContainsString('购买座驾', $purchaseNotice['content']); } /** * 测试同款座驾续购会叠加有效期且保持一条 active 当前座驾。 */ public function test_buying_same_ride_extends_active_purchase(): void { $user = User::factory()->create(['jjb' => 50000]); $room = Room::create(['room_name' => '座驾续费房']); $this->joinRoom($user, $room); $ride = $this->createRide(['price' => 1000, 'duration_days' => 7]); $purchase = UserRidePurchase::create([ 'user_id' => $user->id, 'ride_id' => $ride->id, 'status' => 'active', 'price_paid' => 1000, 'expires_at' => now()->addDays(2), ]); $response = $this->actingAs($user)->postJson(route('rides.buy'), [ 'item_id' => $ride->id, 'room_id' => $room->id, ]); $response->assertOk(); $this->assertSame('cancelled', $purchase->fresh()->status); $activePurchase = UserRidePurchase::query() ->where('user_id', $user->id) ->where('ride_id', $ride->id) ->where('status', 'active') ->firstOrFail(); $this->assertSame(1000, (int) $activePurchase->price_paid); $this->assertTrue($activePurchase->expires_at->greaterThan(now()->addDays(8))); $this->assertSame(1, UserRidePurchase::query()->where('user_id', $user->id)->where('status', 'active')->count()); } /** * 测试购买不同座驾会取消旧座驾并激活新座驾。 */ public function test_buying_different_ride_cancels_previous_active_ride(): void { $user = User::factory()->create(['jjb' => 50000]); $room = Room::create(['room_name' => '座驾替换房']); $this->joinRoom($user, $room); $oldRide = $this->createRide(['slug' => 'ride_j35', 'price' => 1000]); $newRide = $this->createRide(['slug' => 'ride_df5c', 'price' => 2000]); $oldPurchase = UserRidePurchase::create([ 'user_id' => $user->id, 'ride_id' => $oldRide->id, 'status' => 'active', 'price_paid' => 1000, 'expires_at' => now()->addDays(3), ]); $response = $this->actingAs($user)->postJson(route('rides.buy'), [ 'item_id' => $newRide->id, 'room_id' => $room->id, ]); $response->assertOk() ->assertJsonPath('current_ride.item.slug', 'ride_df5c'); $this->assertSame('cancelled', $oldPurchase->fresh()->status); $this->assertDatabaseHas('user_ride_purchases', [ 'user_id' => $user->id, 'ride_id' => $newRide->id, 'status' => 'active', ]); } /** * 测试后台座驾独立模块可以保存欢迎语字段。 */ public function test_admin_can_store_ride_with_welcome_message(): void { $admin = User::factory()->create(['id' => 1, 'user_level' => 100]); $response = $this->actingAs($admin)->post(route('admin.rides.store'), [ 'name' => '测试座驾', 'slug' => 'ride_test', 'effect_key' => 'test', 'icon' => '🚘', 'description' => '测试座驾说明', 'price' => 12345, 'duration_days' => 7, 'welcome_message' => '【{name}】驾驶【{ride}】入场', 'sort_order' => 99, 'is_active' => 1, ]); $response->assertRedirect(route('admin.rides.index')); $this->assertDatabaseHas('rides', [ 'slug' => 'ride_test', 'effect_key' => 'test', 'welcome_message' => '【{name}】驾驶【{ride}】入场', ]); } /** * 测试后台座驾管理页面可以显示独立座驾数据。 */ public function test_admin_can_view_ride_management_page(): void { $admin = User::factory()->create(['id' => 1, 'user_level' => 100]); $ride = $this->createRide(['name' => '后台可见座驾', 'slug' => 'ride_admin_visible']); $response = $this->actingAs($admin)->get(route('admin.rides.index')); $response->assertOk() ->assertSee('座驾管理') ->assertSee($ride->name) ->assertSee('ride_admin_visible'); } /** * 创建测试用独立座驾。 * * @param array $attributes 覆盖字段 */ private function createRide(array $attributes = []): Ride { $data = array_merge([ 'name' => '测试座驾', 'slug' => 'ride_test_'.str()->random(8), 'effect_key' => 'test_'.str()->random(8), 'description' => '测试座驾说明', 'icon' => '🚘', 'price' => 1000, 'duration_days' => 7, 'sort_order' => 80, 'is_active' => true, 'welcome_message' => '【{name}】驾驶【{ride}】入场', ], $attributes); return Ride::query()->updateOrCreate( ['slug' => $data['slug']], $data, ); } /** * 将测试用户写入指定房间在线表,模拟已经进入聊天室的状态。 */ private function joinRoom(User $user, Room $room): void { Redis::hset("room:{$room->id}:users", $user->username, json_encode([ 'id' => $user->id, 'username' => $user->username, ], JSON_UNESCAPED_UNICODE)); } }