Files
portal/app/tests/Feature/ClientTg/WalletChannelTest.php
T

73 lines
3.5 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Exceptions\Billing\InsufficientBalanceException;
use App\Models\AdWallet;
use App\Models\AdWalletTransaction;
use App\Models\Tenant;
use App\Services\Advertising\AdWalletService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
const TG_CHANNEL = 'telegram';
it('замораживает резерв под Telegram-кампанию и учитывает его по каналу', function () {
$tenant = Tenant::factory()->create();
$svc = app(AdWalletService::class);
$svc->topup($tenant->id, '1000.00', TG_CHANNEL, 'Пополнение');
$svc->freeze($tenant->id, TG_CHANNEL, 'client_tg_campaign', 5, '420.00');
$wallet = AdWallet::where('tenant_id', $tenant->id)->first();
expect((string) $wallet->balance_rub)->toBe('1000.00')
->and((string) $wallet->frozen_rub)->toBe('420.00')
->and(AdWalletTransaction::where('tenant_id', $tenant->id)->where('channel', TG_CHANNEL)->count())->toBe(2); // topup + freeze
});
it('freeze стережёт нехватку средств и бросает ДО списания', function () {
$tenant = Tenant::factory()->create();
$svc = app(AdWalletService::class);
$svc->topup($tenant->id, '300.00', TG_CHANNEL, 'Пополнение');
// free = balance frozen = 300 < 400 → бросок ДО каких-либо списаний
expect(fn () => $svc->freeze($tenant->id, TG_CHANNEL, 'client_tg_campaign', 1, '400.00'))
->toThrow(InsufficientBalanceException::class);
$wallet = AdWallet::where('tenant_id', $tenant->id)->first();
expect((string) $wallet->frozen_rub)->toBe('0.00'); // ничего не заморозилось
});
it('charge НЕ бросает и обнуляет баланс, не уходя в минус', function () {
$tenant = Tenant::factory()->create();
$svc = app(AdWalletService::class);
$svc->topup($tenant->id, '300.00', TG_CHANNEL, 'Пополнение');
// charge не бросает — потому проверка средств обязана быть ДО (через freeze).
$svc->charge($tenant->id, TG_CHANNEL, 'client_tg_campaign', 1, '400.00', 'tg:1:test');
$wallet = AdWallet::where('tenant_id', $tenant->id)->first();
expect((string) $wallet->balance_rub)->toBe('0.00') // пол по нулю, не минус
->and(AdWalletTransaction::where('tenant_id', $tenant->id)
->where('channel', TG_CHANNEL)->where('type', AdWalletTransaction::TYPE_CHARGE)->exists())->toBeTrue();
});
it('полный поток telegram: topup → freeze → charge, идемпотентно по external_key', function () {
$tenant = Tenant::factory()->create();
$svc = app(AdWalletService::class);
$svc->topup($tenant->id, '1000.00', TG_CHANNEL, 'Пополнение');
$svc->freeze($tenant->id, TG_CHANNEL, 'client_tg_campaign', 7, '420.00');
$svc->charge($tenant->id, TG_CHANNEL, 'client_tg_campaign', 7, '420.00', 'tg:7:2026-07-27');
// повторное списание тем же external_key — идемпотентно, ничего не меняет
$svc->charge($tenant->id, TG_CHANNEL, 'client_tg_campaign', 7, '420.00', 'tg:7:2026-07-27');
$wallet = AdWallet::where('tenant_id', $tenant->id)->first();
expect((string) $wallet->balance_rub)->toBe('580.00')
->and(AdWalletTransaction::where('tenant_id', $tenant->id)
->where('channel', TG_CHANNEL)->where('type', AdWalletTransaction::TYPE_CHARGE)->count())->toBe(1);
});