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

85 lines
3.6 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Jobs\ClientTg\SweepStuckTelegramCampaignsJob;
use App\Models\AdWallet;
use App\Models\AdWalletHold;
use App\Models\ClientTg\Campaign;
use App\Models\Tenant;
use App\Services\Advertising\AdWalletService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
/**
* Ревью-фикс F4 (Мост #1): кампания не должна вечно висеть в `moderating`, если робот
* не может прочитать вердикт (сломались селекторы кабинета / кампанию удалили / id
* протух) — иначе бронь кошелька заперта бессрочно. Предохранитель по возрасту: старше
* окна `client_tg.moderation_stuck_hours` → `needs_review` для ручного разбора, БЕЗ
* возврата брони (кампания могла реально показываться → деньги вслепую не трогаем).
*/
uses(RefreshDatabase::class, SharesSupplierPdo::class);
/** Кампания на модерации с состаренным updated_at (в часах). */
function modStuckCampaign(int $tenantId, ?string $mtsId, int $ageHours): Campaign
{
$c = Campaign::query()->create([
'tenant_id' => $tenantId,
'status' => Campaign::STATUS_MODERATING,
'mts_campaign_id' => $mtsId,
'ad_text' => 'Мод-стак-тест',
'ord_category' => 'Размещение рекламы',
'budget_cap_rub' => '1000.00',
'audience_kind' => Campaign::AUDIENCE_LIST,
'planned_count' => 0,
'estimated_cost_rub' => '0.00',
'created_by' => 1,
]);
if ($ageHours > 0) {
DB::table('client_tg_campaigns')->where('id', $c->id)
->update(['updated_at' => now()->subHours($ageHours)]);
}
return $c;
}
function modStuckWallet(int $tenantId, int $campaignId, string $balance, string $freeze): void
{
AdWallet::query()->create(['tenant_id' => $tenantId, 'balance_rub' => $balance, 'frozen_rub' => '0.00']);
app(AdWalletService::class)->freeze($tenantId, 'telegram', 'campaign', $campaignId, $freeze);
}
it('moderating допускает переход в needs_review', function () {
$t = Tenant::factory()->create();
$c = modStuckCampaign($t->id, '2231160', 0);
expect($c->canTransitionTo(Campaign::STATUS_NEEDS_REVIEW))->toBeTrue();
});
it('moderating дольше окна → needs_review, бронь НЕ возвращается', function () {
config()->set('client_tg.sandbox', false);
config()->set('client_tg.moderation_stuck_hours', 24);
$t = Tenant::factory()->create();
$c = modStuckCampaign($t->id, '2231161', ageHours: 25);
modStuckWallet($t->id, $c->id, '5000.00', '1000.00');
SweepStuckTelegramCampaignsJob::dispatchSync();
expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_NEEDS_REVIEW)
->and((string) AdWallet::where('tenant_id', $t->id)->value('frozen_rub'))->toBe('1000.00')
->and(AdWalletHold::where('source_id', $c->id)->value('status'))->toBe(AdWalletHold::STATUS_ACTIVE);
});
it('свежая moderating (в пределах окна) не трогается', function () {
config()->set('client_tg.sandbox', false);
config()->set('client_tg.moderation_stuck_hours', 24);
$t = Tenant::factory()->create();
$c = modStuckCampaign($t->id, '2231162', ageHours: 2); // 2ч < 24ч
SweepStuckTelegramCampaignsJob::dispatchSync();
expect(Campaign::find($c->id)->status)->toBe(Campaign::STATUS_MODERATING);
});