2fc844756c
Находка приёмки Этапа 5. В Этапе 4 уже находили, что рабочей роли не выдано право на счётчик номеров таблицы заморозок ad_wallet_holds_id_seq, из-за чего заморозка денег под боевой ролью не проходила ВОВСЕ. Тогда стенд починили руками и переписали памятку выката. Приёмка проверила это на базе, собранной ТОЛЬКО миграциями — то есть на точной модели того, что получит боевой после выката. Права там снова нет: починка жила лишь в памятке. Ни одна миграция её не несла. Цена промаха денежная и широкая: заморозка делается при КАЖДОМ заказе рассылки, при заказе имени отправителя и при запуске рекламной кампании Яндекса. Выкат, на котором забудут перезапустить db/02_grants.sql, ломает всё это разом и молча. Что сделано: - миграция 2026_08_01_101400 выдаёт USAGE, SELECT на три счётчика кошелька рабочей и админской ролям. Роли не на глаз, а спрошены у базы: INSERT на сами таблицы кошелька есть ровно у этих двух, служебному работнику кошелёк не выдавался вовсе; - сторож tests/Feature/Advertising/AdWalletGrantsTest.php спрашивает у базы, а не читает текст миграции: опечатка в имени роли внутри миграции ошибки не даёт, права просто молча не выдаются; - запись v9.32 в журнале схемы. Структуру она не меняет — только права. Порядок работы соблюдён: сторож написан ДО миграции и покраснел ровно на том, на чём должен — «не выдано право на счётчик ad_wallet_holds_id_seq». После миграции зелёный. Живой прогон под ролью crm_app_user на базе из одних миграций: до — «нет доступа к последовательности», после — заморозка проходит и возвращает номер строки. Номер записи журнала взят v9.32, а не следующий свободный v9.26: номера v9.26-v9.31 уже заняты в других, ещё не сведённых ветках, и v9.26 повторил бы столкновение номеров, которое разбирали 01.08. Разрыв закроется при сведении с main. Памятку выката это НЕ отменяет: на уже накатанных базах миграция доносит право, но перезапуск db/02_grants.sql остаётся правильным шагом для всего остального. Статанализ добавил одно замечание — ложный класс Pest: анализатор не понимает $this внутри тестов Pest и не видит markTestSkipped. Тот же класс сидит в давно закоммиченном MigrationGrantsTest. Доказательство ложности прямое: тест проходит, не будь метода — упал бы. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
63 lines
3.3 KiB
PHP
63 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Сторож прав рабочей роли на СЧЁТЧИКИ рекламного кошелька.
|
|
*
|
|
* Зачем он появился (приёмка Этапа 5, 01.08.2026). В Этапе 4 нашли, что у рабочей
|
|
* роли нет права на счётчик номеров таблицы заморозок `ad_wallet_holds_id_seq`, и
|
|
* заморозка денег под боевой ролью не проходила ВОВСЕ — «нет доступа к
|
|
* последовательности» (журнал В-190). Тогда стенд починили руками и переписали
|
|
* памятку выката.
|
|
*
|
|
* Приёмка Этапа 5 проверила это на базе, собранной ТОЛЬКО миграциями — то есть на
|
|
* точной модели того, что получит боевой после выката, — и права там снова НЕТ.
|
|
* Починка жила лишь в памятке и в голове человека.
|
|
*
|
|
* Цена промаха денежная и широкая: заморозка делается при КАЖДОМ заказе рассылки,
|
|
* при заказе имени отправителя и при запуске рекламной кампании Яндекса. Забыли шаг
|
|
* памятки — сломалось всё это разом.
|
|
*
|
|
* Проверяется не текст миграции, а результат: реально ли роль может пользоваться
|
|
* счётчиком. Право на таблицу счётчик НЕ покрывает — это разные права.
|
|
*
|
|
* ⚠️ Честное ограничение: доказывает это только там, где роли заведены
|
|
* (`db/00_create_roles.sql`). Где ролей нет — тест пропускается и не доказывает ничего.
|
|
*/
|
|
uses(RefreshDatabase::class);
|
|
|
|
function adWalletRoleExists(string $role): bool
|
|
{
|
|
return DB::selectOne('SELECT 1 AS ok FROM pg_roles WHERE rolname = ?', [$role]) !== null;
|
|
}
|
|
|
|
it('у рабочей роли есть право пользоваться счётчиками рекламного кошелька', function () {
|
|
if (! adWalletRoleExists('crm_app_user')) {
|
|
$this->markTestSkipped('Рабочей роли на этой базе нет — проверять нечего.');
|
|
}
|
|
|
|
$sequences = DB::select(<<<'SQL'
|
|
SELECT sequencename FROM pg_sequences
|
|
WHERE schemaname = 'public' AND sequencename LIKE 'ad\_wallet%'
|
|
ORDER BY sequencename
|
|
SQL);
|
|
|
|
expect($sequences)->not->toBeEmpty();
|
|
|
|
foreach ($sequences as $row) {
|
|
$granted = DB::selectOne(
|
|
'SELECT has_sequence_privilege(?, ?, ?) AS ok',
|
|
['crm_app_user', $row->sequencename, 'USAGE'],
|
|
)->ok;
|
|
|
|
expect($granted)->toBeTrue(
|
|
"Рабочей роли не выдано право на счётчик {$row->sequencename} — "
|
|
.'заморозка денег под боевой ролью не пройдёт'
|
|
);
|
|
}
|
|
});
|