Files
portal/app/tests/Feature/Advertising/AdvertisingMinimumSpendTest.php
T

86 lines
4.3 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\AdCampaign;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Advertising\YandexMinimumSpend;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
uses(DatabaseTransactions::class);
/**
* Минимум площадки — в мастере, а не на последнем шаге.
*
* 🔴 Живой отказ 30.07.2026: Директ не берёт кампанию, у которой расход за период ниже
* его минимума, и отвечает по-английски — «Budget for this period cannot be less than
* 600 rub.». Клиент видел этот текст ПОСЛЕ того, как собрал аудиторию (15 часов),
* залил пятнадцать картинок и нажал «Запустить». Понять из него, чего от него хотят,
* невозможно.
*
* Минимум считается от длины периода — 300 ₽ за каждый календарный день. Перевести его
* в понятные клиенту величины (сколько показов и сколько рублей) можно только на сервере:
* в расчёте участвует наша наценка, а её клиенту показывать нельзя.
*
* 🔑 Формула ОДНА на портал: и запуск, и мастер спрашивают один и тот же счётчик.
* Две копии одного расчёта однажды разойдутся, и верить будет нечему.
*/
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
$this->actingAs($this->user);
config(['services.yandex_direct.min_spend_limit_rub_per_day' => 300]);
config(['services.yandex_direct.spend_limit_guard_multiplier' => '1.2']);
DB::table('ad_settings')->update(['ad_margin_percent' => '40.00']);
});
it('мастер получает минимум площадки в показах и рублях, наценку не раскрывая', function () {
$campaign = AdCampaign::create([
'tenant_id' => $this->tenant->id,
'name' => 'Черновик',
'status' => AdCampaign::STATUS_DRAFT,
'audience_days' => 10,
'mode' => AdCampaign::MODE_AUTO,
'run_days' => 1,
'client_cpm_rub' => '500.00',
]);
$otvet = $this->getJson("/api/advertising/campaigns/{$campaign->id}/audience-size?frequency=5&days=10")
->assertOk();
// Срок 1 день → период сегодня…завтра = 2 календарных дня → минимум Яндексу 600 ₽.
// Яндексу уходит 60 % от 500 ₽ = 300 ₽ за тысячу; с запасом 1,2 это 0,36 ₽ за показ.
// 600 / 0,36 = 1 666,67 → 1 667 показов. Клиенту это 1667 × 500 / 1000 = 833,50 ₽.
$otvet->assertJsonPath('min_impressions', 1667)
->assertJsonPath('min_cost_rub', '833.50');
// Наценка, доля Яндекса и его ставка клиенту не видны ни в каком виде.
$telo = $otvet->getContent();
expect($telo)->not->toContain('margin')
->and($telo)->not->toContain('yandex_cpm')
->and($telo)->not->toContain('300.00');
});
it('запуск и мастер считают минимум ОДНИМ счётчиком', function () {
$schetchik = app(YandexMinimumSpend::class);
$itog = $schetchik->minimum(1, '500.00');
expect($itog['impressions'])->toBe(1667)
->and($itog['cost_rub'])->toBe('833.50');
});
it('длиннее срок — выше минимум: неделя стоит семь дней по 300 ₽', function () {
$schetchik = app(YandexMinimumSpend::class);
// Срок 7 дней → период сегодня…+6 = 7 календарных дней → 2100 ₽ Яндексу.
// 2100 / 0,36 = 5 833,33 → 5 834 показа → 2 917,00 ₽ клиенту.
$itog = $schetchik->minimum(7, '500.00');
expect($itog['impressions'])->toBe(5834)
->and($itog['cost_rub'])->toBe('2917.00');
});