Http::response(['affordable' => true]), $base.'/debit' => Http::response(['ok' => true]), ]); } public function test_session_end_records_streaming_usage_and_debits_wallet(): void { $this->fakeAffordableBilling(); $organization = Organization::create([ 'owner_ref' => 'owner-001', 'name' => 'Billing Org', 'slug' => 'billing-org', 'timezone' => 'UTC', ]); $room = Room::create([ 'owner_ref' => 'owner-001', 'organization_id' => $organization->id, 'host_user_ref' => 'owner-001', 'title' => 'Billing Room', 'type' => 'instant', 'status' => 'live', 'settings' => config('meet.default_settings'), ]); $session = Session::create([ 'owner_ref' => 'owner-001', 'room_id' => $room->id, 'media_room_name' => 'room-test', 'status' => 'ended', 'started_at' => now()->subHour(), 'ended_at' => now(), ]); app(UsageMeteringService::class)->recordSessionUsage($session); $this->assertDatabaseHas('meet_usage_records', [ 'organization_id' => $organization->id, 'metric' => 'streaming_hours', ]); Http::assertSent(fn ($request) => str_contains($request->url(), '/debit') && ($request->data()['source'] ?? '') === 'meet_streaming'); } public function test_storage_renewal_enters_grace_when_wallet_is_empty(): void { $base = rtrim((string) config('billing.api_url'), '/'); Http::fake([ $base.'/can-afford*' => Http::response(['affordable' => false]), ]); $organization = Organization::create([ 'owner_ref' => 'owner-002', 'name' => 'Grace Org', 'slug' => 'grace-org', 'timezone' => 'UTC', ]); $room = Room::create([ 'owner_ref' => 'owner-002', 'organization_id' => $organization->id, 'host_user_ref' => 'owner-002', 'title' => 'Grace Room', 'type' => 'instant', 'status' => 'ended', 'settings' => config('meet.default_settings'), ]); $session = Session::create([ 'owner_ref' => 'owner-002', 'room_id' => $room->id, 'media_room_name' => 'room-grace', 'status' => 'ended', 'started_at' => now()->subDay(), 'ended_at' => now()->subDay(), ]); $file = \App\Models\SessionFile::create([ 'owner_ref' => 'owner-002', 'room_id' => $room->id, 'session_id' => $session->id, 'uploaded_by_ref' => 'owner-002', 'uploaded_by_name' => 'Host', 'original_name' => 'notes.pdf', 'storage_path' => 'meet-files/test/notes.pdf', 'file_size' => 1073741824, 'storage_paid_until' => now()->subMinute(), ]); $result = app(MeetBillingService::class)->processDueStorageRenewals(); $file->refresh(); $this->assertSame(1, $result['graced']); $this->assertNotNull($file->storage_grace_ends_at); } }