Files
portal/app/tests/Support/MtsMediaFixture.php
T
Дмитрий 3864f6f11a feat,телеграм-реклама: цена показа по виду медиа вместо ступеней по объёму
Мы продавали дешевле, чем покупали. Тариф давал скидку за объём — 0.45 → 0.36 ₽
за показ, — а у МТС такой скидки нет: прайс кабинета, стр. 13, берёт 0,48 ₽ за
показ плоско. Скидку давали мы, а нам её не давал никто: на объёме от 50 000
наценка 1.40 превращалась в 5%.

Второе. Кабинет показывает CPM БЕЗ НДС — колонка списка так и названа. Счёт
кампании 2231134 сошёлся: 420 × 400 ₽/1000 × 1,2 = 201,60 ₽. Мы ИП на УСН, НДС
не возмещается, это расход. Себестоимость с НДС: 0.480 без медиа, 0.720 с
картинкой, 0.816 с видео.

Третье. Вид медиа до расчёта не доходил вовсе — объявление с видео продавалось
по цене объявления без картинки. На видео уходили в минус до 176 ₽ с тысячи, и
портал нигде свою цену с ценой МТС не сравнивал. Песочница выключена с 02.08 —
деньги живые.

Что сделано:
- миграция client_tg_cena_po_media: min_qty → media_kind, точность цены 6,2 →
  6,3, три строки вместо пяти ступеней, media_kind на кампании и авто-правиле;
- вид медиа определяется по СОДЕРЖИМОМУ файла, а не по расширению имени;
- экран админки переделан: три фиксированные строки по видам медиа, рядом цена
  за тысячу для сверки с кабинетом, добавлять и удалять нечего;
- запись v9.65 в журнале схемы.

Замеры этой смены: телеграм-модуль 313 тестов 0 падений, экраны 242 файла
1841 тест 0 падений, статанализ 0, форматтер чисто. Проверка типов — 6 ошибок,
все в чужих файлах, были до нас.

Тест экрана тарифов проверен вырезанием, а не только зелёным: сломал передачу
media_kind — красный; сломал расчёт цены за тысячу — красный.

Осталось незакрытым: числа 0.720 и 0.816 — вывод по правилу «кабинет пишет без
НДС», а не замер. Счёт кампании с картинкой живьём не снимали, шаг «Стоимость»
недостижим без загрузки живых номеров. Замерите — правится одной строкой.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 22:15:46 +03:00

69 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Support;
use Illuminate\Http\UploadedFile;
/**
* Синтетические файлы медиа под требования кабинета МТС.
*
* 🪤 Почему классом, а не функциями в тесте: в Pest помощники из разных файлов живут
* в ОДНОМ пространстве имён, и одноимённые функции в двух наборах роняют прогон
* «Cannot redeclare» — на этом уже спотыкались при сведении веток 01.08.2026.
* Класс автозагружается (Tests\ → tests/) и столкнуться не может.
*
* Настоящих пикселей внутри нет — только те поля заголовка, которые читает
* App\Support\Mp4Probe: длительность (mvhd) и размер кадра (tkhd).
*/
final class MtsMediaFixture
{
/** Один бокс контейнера ISO-BMFF: длина (4 байта) + тип (4 байта) + содержимое. */
private static function box(string $type, string $payload): string
{
return pack('N', 8 + strlen($payload)).$type.$payload;
}
/** Байты mp4 с настоящими mvhd (длительность) и tkhd (размер кадра). */
public static function mp4Bytes(float $seconds = 10.0, int $width = 1280, int $height = 720): string
{
$timescale = 1000;
$duration = (int) round($seconds * $timescale);
$mvhd = "\x00\x00\x00\x00" // version 0 + flags
.pack('N', 0).pack('N', 0) // создан / изменён
.pack('N', $timescale).pack('N', $duration) // масштаб времени и длительность
.pack('N', 0x00010000) // rate
.pack('n', 0x0100) // volume
.str_repeat("\x00", 2 + 8 + 36 + 24) // reserved + matrix + pre_defined
.pack('N', 2); // next_track_ID
$tkhd = "\x00\x00\x00\x07" // version 0 + флаги «дорожка включена»
.pack('N', 0).pack('N', 0) // создан / изменён
.pack('N', 1) // track_ID
.pack('N', 0) // reserved
.pack('N', $duration) // длительность дорожки
.str_repeat("\x00", 8) // reserved
.pack('n', 0).pack('n', 0) // layer, alternate_group
.pack('n', 0).pack('n', 0) // volume, reserved
.str_repeat("\x00", 36) // matrix
.pack('N', $width << 16).pack('N', $height << 16); // ширина/высота 16.16
return self::box('ftyp', 'isom'.pack('N', 512).'isomiso2mp41')
.self::box('moov', self::box('mvhd', $mvhd).self::box('trak', self::box('tkhd', $tkhd)));
}
/** Годное видео файлом — ровно то, что кабинет принимает. */
public static function video(float $seconds = 10.0, int $width = 1280, int $height = 720): UploadedFile
{
return UploadedFile::fake()->createWithContent('ad.mp4', self::mp4Bytes($seconds, $width, $height));
}
/** Годная картинка файлом. */
public static function kartinka(int $width = 1280, int $height = 720): UploadedFile
{
return UploadedFile::fake()->image('ad.jpg', $width, $height);
}
}