set('client_tg.sandbox', false); }); function crCampaign(int $tenantId, string $status, ?string $mtsId = null): Campaign { $campaign = Campaign::query()->create([ 'tenant_id' => $tenantId, 'status' => $status, 'mts_campaign_id' => $mtsId, 'ad_text' => 'Приходите к нам за услугой', 'ad_link' => 'https://example.test/promo', 'ord_category' => 'Размещение рекламы', 'budget_cap_rub' => '500.00', 'audience_kind' => Campaign::AUDIENCE_LIST, 'planned_count' => 2, 'estimated_cost_rub' => '500.00', 'created_by' => 1, ]); foreach (['79990000001', '79990000002'] as $phone) { CampaignPhone::query()->create(['tenant_id' => $tenantId, 'campaign_id' => $campaign->id, 'phone' => $phone]); } return $campaign; } function crMockRobot(RobotResult $result): void { $mock = Mockery::mock(TelegramRobotRunner::class); $mock->shouldReceive('run')->andReturn($result); app()->instance(TelegramRobotRunner::class, $mock); } /** Сколько возвратов сметы прошло по кампании (сальдо-источник правды — проводки). */ function crRefundCount(int $campaignId): int { return BalanceTransaction::query() ->where('type', BalanceTransaction::TYPE_TG_AD_REFUND) ->where('related_type', Campaign::class) ->where('related_id', $campaignId) ->count(); } it('finalize: провал робота с id кабинета → needs_review, списание держится, refund НЕ произошёл', function () { $tenant = Tenant::factory()->create(['balance_rub' => '1000.00']); DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id); $campaign = crCampaign($tenant->id, Campaign::STATUS_QUEUED); app(TelegramCampaignChargeService::class)->charge($campaign, (string) $campaign->estimated_cost_rub); crMockRobot(RobotResult::failed('Оборвалось на подтверждении', 'confirm', '2231200')); RunTelegramCampaignJob::dispatchSync($campaign->id, $tenant->id); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_NEEDS_REVIEW) ->and($fresh->mts_campaign_id)->toBe('2231200') ->and(crRefundCount($campaign->id))->toBe(0) // возврата не было ->and((string) $tenant->fresh()->balance_rub)->toBe('500.00'); // списание держится }); it('finalize: провал робота БЕЗ id кабинета → failed, refund произошёл, баланс восстановлен', function () { $tenant = Tenant::factory()->create(['balance_rub' => '1000.00']); DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id); $campaign = crCampaign($tenant->id, Campaign::STATUS_QUEUED); app(TelegramCampaignChargeService::class)->charge($campaign, (string) $campaign->estimated_cost_rub); crMockRobot(RobotResult::failed('Мало номеров «не МТС»', 'upload')); RunTelegramCampaignJob::dispatchSync($campaign->id, $tenant->id); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_FAILED) ->and(crRefundCount($campaign->id))->toBe(1) // возврат произошёл ->and((string) $tenant->fresh()->balance_rub)->toBe('1000.00'); // баланс восстановлен }); it('failed(): running с id кабинета → needs_review, списание держится, refund НЕ произошёл', function () { $tenant = Tenant::factory()->create(['balance_rub' => '1000.00']); DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id); $campaign = crCampaign($tenant->id, Campaign::STATUS_RUNNING, mtsId: '2231201'); app(TelegramCampaignChargeService::class)->charge($campaign, (string) $campaign->estimated_cost_rub); (new RunTelegramCampaignJob($campaign->id, $tenant->id))->failed(new RuntimeException('boom')); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_NEEDS_REVIEW) ->and(crRefundCount($campaign->id))->toBe(0) ->and((string) $tenant->fresh()->balance_rub)->toBe('500.00'); }); it('failed(): running без id кабинета → failed, refund произошёл, баланс восстановлен', function () { $tenant = Tenant::factory()->create(['balance_rub' => '1000.00']); DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id); $campaign = crCampaign($tenant->id, Campaign::STATUS_RUNNING); app(TelegramCampaignChargeService::class)->charge($campaign, (string) $campaign->estimated_cost_rub); (new RunTelegramCampaignJob($campaign->id, $tenant->id))->failed(new RuntimeException('boom')); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_FAILED) ->and(crRefundCount($campaign->id))->toBe(1) ->and((string) $tenant->fresh()->balance_rub)->toBe('1000.00'); }); it('failed(): уже moderating → статус не трогаем, refund НЕ произошёл', function () { $tenant = Tenant::factory()->create(['balance_rub' => '1000.00']); DB::statement('SET LOCAL app.current_tenant_id = '.$tenant->id); $campaign = crCampaign($tenant->id, Campaign::STATUS_MODERATING, mtsId: '2231202'); app(TelegramCampaignChargeService::class)->charge($campaign, (string) $campaign->estimated_cost_rub); (new RunTelegramCampaignJob($campaign->id, $tenant->id))->failed(new RuntimeException('boom')); $fresh = Campaign::find($campaign->id); expect($fresh->status)->toBe(Campaign::STATUS_MODERATING) ->and(crRefundCount($campaign->id))->toBe(0) ->and((string) $tenant->fresh()->balance_rub)->toBe('500.00'); });