652b708203
Из 29 карточек приёмки Яндекса закрыты 27. Остались две, и обе ждут не работы, а события: живого отказа всех объявлений разом и платных комбинаций Я1-Я9. Нашлась вторая за день тихая беда. Слушатель остановки рекламы был подписан на событие ДВАЖДЫ: Laravel 11 сам находит слушателей в app/Listeners по типу параметра handle(), а в провайдере он вдобавок прописан руками. Остановка не бесплатная: по каждой кампании клиента она идёт в Яндекс за числом показов, платит за показанное и глушит кампанию, - и всё это делалось по два раза. Деньги уцелели по случайности: списание идемпотентно по ключу показов, снятие заморозки тоже, а второй заход не находил кампаний, первый уже увёл их из работы. Ручная подписка убрана. Найдено не глазами, а вырезанием: новый сквозной сторож Я-Б3 не покраснел, когда подписку отключили. Если бы регистрация была одна, он обязан был. Замерено на живой кампании #13, без единого рубля сверху: - Я-Д3: показано 21, списано 10,50 руб = 21 x 500/1000; по смете было бы 850; - Я-М2: кампания running, заморозка 839,50 = 850 - 10,50, показы идут; - Я-Х4: задача из очереди под боевой ролью без контекста клиента видит 1 кампанию служебным путём и 0 обычным - тот самый молчаливый ноль, ради которого служебный путь и заведён. Новые сторожа, каждый принят красным: - Я-Б2: денег ровно на смету - запуск проходит, вторая кампания уже нет; - Я-Б3: деньги кончились - кампания РЕАЛЬНО встала и владельцу ушло письмо; - Я-М4: пока вердикта нет, не двигаются ни заморозка, ни списание; - Я-М5: после «Исправить» и повторного запуска заморозка ОДНА; - Я-Х2: десять подделок отбиты, чужой номер клиента ничего не меняет; - Я-Д1: заморозка равна смете копейка в копейку, включая некруглую; - Я-Д2: сплошной обход всех семи читающих путей - наценка не видна нигде (прежний сторож проверял один путь из семи); - остановка рекламы подписана ровно один раз. Прогоны: 424 теста рекламы зелёные, статанализ 0, формат чистый. Журнал результатов приёмки дописан разделом на 05.08 - что закрыто, чем закрыто и чего ждём.
106 lines
5.0 KiB
PHP
106 lines
5.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AdCampaign;
|
|
use App\Models\AdWallet;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Advertising\AdWalletService;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
/**
|
|
* Я-Х2 приёмочного листа — запрос мимо экрана.
|
|
*
|
|
* Экран мастера не даёт ввести чушь: поля с ограничениями, кнопки блокируются. Но экран —
|
|
* это вежливость, а не защита: запрос к порталу можно отправить и без него, руками. Всё,
|
|
* что мастер не пускает, обязан не пускать и сам портал.
|
|
*
|
|
* 🔴 Цена ошибки здесь денежная. Дробное или отрицательное число показов, нулевая цена,
|
|
* срок в тысячу лет — любое из этих значений, попав в расчёт, испортит смету, а по смете
|
|
* запираются настоящие деньги клиента. Отдельная строка — ЧУЖОЙ кошелёк: если бы номер
|
|
* клиента можно было передать в запросе, посторонний тратил бы чужие деньги.
|
|
*
|
|
* Сторож проверяет две вещи разом: каждая подделка отбита, и кошелёк после всех попыток
|
|
* остался ровно таким, каким был.
|
|
*/
|
|
uses(DatabaseTransactions::class);
|
|
|
|
beforeEach(function () {
|
|
$this->tenant = Tenant::factory()->create();
|
|
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
|
$this->actingAs($this->user);
|
|
|
|
app(AdWalletService::class)->topup($this->tenant->id, '50000.00', 'yandex', 'приёмка');
|
|
|
|
$this->campaign = AdCampaign::create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'name' => 'Черновик',
|
|
'status' => AdCampaign::STATUS_DRAFT,
|
|
'audience_days' => 10,
|
|
'use_uploaded_list' => false,
|
|
]);
|
|
});
|
|
|
|
it('Я-Х2: подделанные числа отбиваются все до одного, кошелёк не тронут', function () {
|
|
$doBalance = AdWallet::where('tenant_id', $this->tenant->id)->first();
|
|
|
|
$poddelki = [
|
|
'дробное число показов' => ['estimated_impressions' => 10.5],
|
|
'отрицательное число показов' => ['estimated_impressions' => -100],
|
|
'отрицательная смета' => ['budget_rub' => -1000],
|
|
'цена ноль' => ['client_cpm_rub' => 0],
|
|
'отрицательная цена' => ['client_cpm_rub' => -5],
|
|
'срок ноль дней' => ['run_days' => 0],
|
|
'срок тысяча лет' => ['run_days' => 999999],
|
|
'частота ноль' => ['frequency' => 0],
|
|
'глубина сбора в сто лет' => ['audience_days' => 36500],
|
|
'адрес сайта не адрес' => ['landing_url' => 'не-адрес-вовсе'],
|
|
];
|
|
|
|
$proshli = [];
|
|
foreach ($poddelki as $imya => $telo) {
|
|
$otvet = $this->patchJson("/api/advertising/campaigns/{$this->campaign->id}", $telo);
|
|
if ($otvet->getStatusCode() < 400) {
|
|
$proshli[] = $imya.' → '.$otvet->getStatusCode();
|
|
}
|
|
}
|
|
|
|
expect($proshli)->toBe([]);
|
|
|
|
$posle = AdWallet::where('tenant_id', $this->tenant->id)->first();
|
|
expect($posle->balance_rub)->toBe($doBalance->balance_rub)
|
|
->and($posle->frozen_rub)->toBe($doBalance->frozen_rub);
|
|
});
|
|
|
|
it('Я-Х2: честные числа проходят — иначе сторож выше отбивал бы вообще всё', function () {
|
|
$otvet = $this->patchJson("/api/advertising/campaigns/{$this->campaign->id}", [
|
|
'estimated_impressions' => 5000,
|
|
'client_cpm_rub' => 500,
|
|
'run_days' => 7,
|
|
'frequency' => 5,
|
|
'audience_days' => 30,
|
|
'landing_url' => 'https://liderra.ru/promo',
|
|
]);
|
|
|
|
expect($otvet->getStatusCode())->toBeLessThan(400);
|
|
});
|
|
|
|
it('Я-Х2: чужой номер клиента в запросе ничего не меняет — деньги остаются своими', function () {
|
|
$chuzhoy = Tenant::factory()->create();
|
|
app(AdWalletService::class)->topup($chuzhoy->id, '77777.00', 'yandex', 'чужие деньги');
|
|
|
|
// Подделка: пробуем приписать свою кампанию чужому клиенту.
|
|
$this->patchJson("/api/advertising/campaigns/{$this->campaign->id}", [
|
|
'name' => 'Переписанная',
|
|
'tenant_id' => $chuzhoy->id,
|
|
]);
|
|
|
|
expect((int) $this->campaign->fresh()->tenant_id)->toBe((int) $this->tenant->id);
|
|
|
|
// И чужой кошелёк не шелохнулся.
|
|
$chuzhoyKoshelek = AdWallet::where('tenant_id', $chuzhoy->id)->first();
|
|
expect($chuzhoyKoshelek->balance_rub)->toBe('77777.00')
|
|
->and($chuzhoyKoshelek->frozen_rub)->toBe('0.00');
|
|
});
|