efcb8034a0
Хвосты Этапа 1 (журнал В-37). Строк приёмочного листа не закрывают — уборка. 1. Графа «кто внёс» в общем стоп-листе портала снесена. Она была не пустой, а врущей: при открытом в том же браузере обычном кабинете туда записывался id КЛИЕНТСКОГО пользователя — число, неотличимое от id администратора. Проверено пробой: пользователь 1 → в графе 1. Админ-зона закрыта паролем nginx, своего входа Laravel у неё нет, а сессия кабинета видна и там — то же эхо коллизии 24.07. Заполнить правдой нечем: настоящий вход админа ждёт Б-1, соседние экраны пишут id служебной заглушки, то есть одно число во всех строках. Остались номер, причина словами и дата — этого хватает и для разбора жалобы, и для договора с МТС. Возврат — down() миграции. 2. Все 16 миграций модуля начинаются с «уже сделано — выходим», уникальный индекс ключа заказа создаётся с IF NOT EXISTS. Причина не теоретическая: на бою SQL миграций подаётся в базу руками, памяти «этот файл уже применён» там нет. У тарифов и настроек это особенно важно — они засевают строки, и повторный запуск завёл бы ВТОРУЮ строку настроек молча, без ошибки. 3. Восемь GRANT … TO crm_app_user стояли без проверки существования роли — на чистой базе migrate падал бы целиком. Теперь все в гарде, как в v9.00 и v9.07. Своя ловушка гарда: опечатка в имени роли внутри IF EXISTS ошибки НЕ даёт, права просто не выдаются — а это ровно блокер выката В-36. Поэтому заведён сторож: тест спрашивает у самой базы has_table_privilege / has_sequence_privilege по каждой таблице и счётчику модуля. Заодно закрыта дыра — у фикса В-36 теста не было вовсе. Хвост «7 замечаний squawk» проверить его же инструментом нельзя: squawk читает SQL, а миграции у нас PHP. Чужой отчёт не пересказываю — проверка своя и воспроизводимая: тест прогоняет up() каждой миграции второй раз. Тесты: +3 (повторный запуск, права ролей, «чужого следа не остаётся»), один переписан. ClientSms 169/169, приём лидов 17/17, phpstan 0, pint чисто. Три выреза, все покраснели: испорченное имя роли, снятая защита от повторного запуска, возвращённая графа. Живой прогон: вошёл в обычный кабинет (та самая опасная обстановка), внёс номер через админ-раздел — в базе ровно три поля, чужого следа нет; убрал кнопкой. Пять миграций запущены по второму разу прямо на dev-базе — прошли, тарифов 5, строка настроек одна. Контроль: голый CREATE TABLE та же база отвергает. Запись схемы — v9.10.
61 lines
2.7 KiB
PHP
61 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
/**
|
|
* Миграции модуля должны выдерживать ПОВТОРНЫЙ запуск (журнал В-73).
|
|
*
|
|
* Зачем это вообще нужно. Обычно повторный запуск невозможен: Laravel помнит,
|
|
* какие файлы уже применены. Но на боевом кластере миграции накатываются иначе —
|
|
* SQL вынимается командой `migrate --pretend` и подаётся в базу руками одной
|
|
* транзакцией. Там памяти «этот файл уже применён» нет: повторный накат того же
|
|
* куска — после сорвавшегося выката, после отката на середине — упирается в
|
|
* «таблица уже существует» и валит всю транзакцию целиком.
|
|
*
|
|
* Тест прогоняет `up()` каждой миграции модуля ВТОРОЙ раз — сразу после того, как
|
|
* её уже применил RefreshDatabase. Ничего не должно сломаться и ничего не должно
|
|
* измениться. Проверяется вырезанием: убери защиту в любой миграции — тест краснеет
|
|
* с именем этой миграции.
|
|
*/
|
|
uses(RefreshDatabase::class);
|
|
|
|
/** Миграции клиентского СМС-модуля — по маске имени, чтобы новые попадали сюда сами. */
|
|
function clientSmsMigrationFiles(): array
|
|
{
|
|
$all = glob(database_path('migrations/*.php')) ?: [];
|
|
|
|
$mine = array_values(array_filter(
|
|
$all,
|
|
fn (string $path) => str_contains(basename($path), 'client_sms')
|
|
|| str_contains(basename($path), 'sms_global_optouts'),
|
|
));
|
|
|
|
// Модуль продаж (`sales_sms`) — чужая работа, сюда не берём.
|
|
return array_values(array_filter(
|
|
$mine,
|
|
fn (string $path) => ! str_contains(basename($path), 'sales_sms'),
|
|
));
|
|
}
|
|
|
|
it('все миграции модуля переживают повторный запуск', function () {
|
|
$files = clientSmsMigrationFiles();
|
|
|
|
expect($files)->not->toBeEmpty();
|
|
|
|
foreach ($files as $path) {
|
|
$migration = require $path;
|
|
|
|
try {
|
|
$migration->up();
|
|
} catch (Throwable $e) {
|
|
$this->fail(sprintf(
|
|
"Миграция %s не переживает повторный запуск:\n%s",
|
|
basename($path),
|
|
$e->getMessage(),
|
|
));
|
|
}
|
|
}
|
|
});
|