From efbc4e549a067a3d4e33e827cc3af8a329e1a9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 16 May 2026 07:00:19 +0300 Subject: [PATCH] fix(billing): topup save() rationale comment + cross-tenant test (Task 1 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code-quality review fixups: документирующий комментарий про безопасность Eloquent save() для bcmath-строки (расхождение с LedgerService raw-update); cross-tenant isolation тест на /api/billing/topup; balance_rub_after в assertDatabaseHas. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/app/Services/Billing/BillingTopupService.php | 4 ++++ app/phpstan-baseline.neon | 2 +- app/tests/Feature/Billing/TopupControllerTest.php | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/app/Services/Billing/BillingTopupService.php b/app/app/Services/Billing/BillingTopupService.php index 5683961e..382c618f 100644 --- a/app/app/Services/Billing/BillingTopupService.php +++ b/app/app/Services/Billing/BillingTopupService.php @@ -41,6 +41,10 @@ final class BillingTopupService // bcadd — DECIMAL-точность, НЕ PHP float (паттерн LedgerService). $newBalanceRub = bcadd((string) $tenant->balance_rub, $amountRub, 2); + // Eloquent decimal:2 cast сохраняет bcmath-строку без потери точности + // при save() (в отличие от decrement(), который требует float|int — + // именно поэтому LedgerService использует raw DB::table()->update(); + // здесь же присваивание уже посчитанной строки через модель безопасно). $tenant->balance_rub = $newBalanceRub; $tenant->save(); diff --git a/app/phpstan-baseline.neon b/app/phpstan-baseline.neon index e71edca3..b18d49f0 100644 --- a/app/phpstan-baseline.neon +++ b/app/phpstan-baseline.neon @@ -591,7 +591,7 @@ parameters: - message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:postJson\(\)\.$#' identifier: method.notFound - count: 7 + count: 8 path: tests/Feature/Billing/TopupControllerTest.php - diff --git a/app/tests/Feature/Billing/TopupControllerTest.php b/app/tests/Feature/Billing/TopupControllerTest.php index 832734aa..4cde5b22 100644 --- a/app/tests/Feature/Billing/TopupControllerTest.php +++ b/app/tests/Feature/Billing/TopupControllerTest.php @@ -33,6 +33,7 @@ test('POST /api/billing/topup пишет строку balance_transactions с us 'user_id' => $this->user->id, 'type' => 'topup', 'amount_rub' => '100.00', + 'balance_rub_after' => '600.00', ]); }); @@ -44,6 +45,16 @@ test('POST /api/billing/topup использует bcmath-точность', fun expect((string) $this->tenant->fresh()->balance_rub)->toBe('100.30'); }); +test('POST /api/billing/topup не затрагивает баланс чужого тенанта', function () { + $otherTenant = Tenant::factory()->create(['balance_rub' => '777.00']); + + $this->postJson('/api/billing/topup', ['amount_rub' => 100])->assertStatus(201); + + // Топап эндпоинт не принимает tenant_id — резолвит из auth-пользователя; + // баланс чужого тенанта неприкосновенен (defense-in-depth, паттерн проекта). + expect((string) $otherTenant->fresh()->balance_rub)->toBe('777.00'); +}); + test('POST /api/billing/topup отклоняет сумму ниже минимума 100 ₽', function () { $this->postJson('/api/billing/topup', ['amount_rub' => 50]) ->assertStatus(422)