2026-05-16 06:46:52 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
|
|
|
|
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
|
|
$this->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',
|
2026-05-16 07:00:19 +03:00
|
|
|
|
'balance_rub_after' => '600.00',
|
2026-05-16 06:46:52 +03: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');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-16 07:00:19 +03:00
|
|
|
|
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');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-16 06:46:52 +03: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);
|
|
|
|
|
|
});
|