tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'balance_leads' => 12]); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); }); test('POST /api/billing/topup кредитует баланс и возвращает 201', function () { $response = $this->postJson('/api/billing/topup', ['amount_rub' => 250]); $response->assertStatus(201) ->assertJsonPath('balance_rub', '750.00') ->assertJsonPath('transaction.type', 'topup') ->assertJsonPath('transaction.amount_rub', '250.00'); expect((string) $this->tenant->fresh()->balance_rub)->toBe('750.00'); }); test('POST /api/billing/topup пишет строку balance_transactions с user_id', function () { $this->postJson('/api/billing/topup', ['amount_rub' => 100])->assertStatus(201); $this->assertDatabaseHas('balance_transactions', [ 'tenant_id' => $this->tenant->id, 'user_id' => $this->user->id, 'type' => 'topup', 'amount_rub' => '100.00', 'balance_rub_after' => '600.00', ]); }); test('POST /api/billing/topup использует bcmath-точность', function () { $this->tenant->update(['balance_rub' => '0.10']); $this->postJson('/api/billing/topup', ['amount_rub' => 100.20])->assertStatus(201); 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) ->assertJsonValidationErrors('amount_rub'); }); test('POST /api/billing/topup отклоняет отсутствующую сумму', function () { $this->postJson('/api/billing/topup', []) ->assertStatus(422) ->assertJsonValidationErrors('amount_rub'); }); test('POST /api/billing/topup отклоняет более 2 знаков после запятой', function () { $this->postJson('/api/billing/topup', ['amount_rub' => 100.123]) ->assertStatus(422) ->assertJsonValidationErrors('amount_rub'); }); test('POST /api/billing/topup без auth: 401', function () { auth()->logout(); $this->postJson('/api/billing/topup', ['amount_rub' => 100])->assertStatus(401); });