7d8c32bb53
Найдено при разборе шва с веткой телеграм-рекламы: обе ветки правили один денежный файл с разных сторон, и сравнение вскрыло поломку у нас. Снятие заморозки не удаляет строку брони, а метит её снятой. На броне висит запрет двух одинаковых записей по четвёрке тенант-канал-тип-источник. Повторная заморозка той же кампании заводила строку заново и падала на дубле ключа. По-человечески: клиент ставил кампанию на паузу и больше не мог её включить. Та же дорога на новом пути отказ модерации - Исправить - отправить заново. Почему 391 зелёный тест этого не видел. Есть два теста, и каждый честен по отдельности: первый морозит и снимает, второй берёт кампанию, которую никогда не морозили, и морозит. Последовательность снять и заморозить снова не проверял никто - шов между двумя половинками остался голым. Проверено прогоном, не рассуждением: база ответила дублирующееся значение ключа нарушает ограничение уникальности ad_wallet_holds по ключу yandex campaign 1. Починка взята у ветки телеграм-рекламы, которая наткнулась на то же самое: не заводить бронь заново, а оживлять снятую. Оба сторожа написаны до починки и проверены вырезанием - без неё падают, с ней проходят. Реклама 393 из 393 при 1249 проверках, было 391. Админка и кошелёк 23 из 23. На боевой не выкатывалось, никуда не отправлялось, рубильник Директа выключен. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
35 lines
2.0 KiB
PHP
35 lines
2.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AdWallet;
|
|
use App\Models\Tenant;
|
|
use App\Services\Advertising\AdWalletService;
|
|
|
|
/**
|
|
* ШОВ между паузой и возобновлением. Два зелёных теста в
|
|
* AdvertisingCampaignPauseResumeTest покрывают половинки по отдельности:
|
|
* «пауза снимает заморозку» замораживает и снимает, «возобновление снова морозит»
|
|
* стартует с кампании, которую НИКОГДА не морозили. Последовательность
|
|
* заморозить → снять → заморозить снова не проверяет никто.
|
|
*
|
|
* А в жизни она обязательна: пауза → возобновление, отказ модерации → «Исправить» →
|
|
* повторный запуск. release() не удаляет строку брони, а метит её released,
|
|
* при этом на ad_wallet_holds висит уникальный ключ
|
|
* (tenant_id, channel, source_type, source_id) — значит второй freeze() падает
|
|
* на дубле ключа.
|
|
*/
|
|
it('морозит повторно после снятия заморозки — пауза и возобновление той же кампании', function () {
|
|
$tenant = Tenant::factory()->create();
|
|
AdWallet::create(['tenant_id' => $tenant->id, 'balance_rub' => '3000.00', 'frozen_rub' => '0.00']);
|
|
$svc = app(AdWalletService::class);
|
|
|
|
$svc->freeze($tenant->id, 'yandex', 'campaign', 1, '1000.00');
|
|
$svc->release($tenant->id, 'yandex', 'campaign', 1);
|
|
|
|
// Возобновление той же кампании: та же четвёрка ключа, бронь уже released.
|
|
$svc->freeze($tenant->id, 'yandex', 'campaign', 1, '1000.00');
|
|
|
|
expect((string) AdWallet::where('tenant_id', $tenant->id)->first()->frozen_rub)->toBe('1000.00');
|
|
});
|