2026-05-08 09:37:16 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace Tests;
|
|
|
|
|
|
|
|
|
|
|
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
2026-07-02 11:07:22 +03:00
|
|
|
|
use Illuminate\Support\Facades\Artisan;
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2026-05-08 09:37:16 +03:00
|
|
|
|
|
|
|
|
|
|
abstract class TestCase extends BaseTestCase
|
|
|
|
|
|
{
|
2026-05-16 09:57:32 +03:00
|
|
|
|
protected function setUp(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
|
|
// Quirk 72: supplier code (SupplierPortalClient::loadSession,
|
|
|
|
|
|
// RefreshSupplierSessionJob, CsvReconcileJob, RouteSupplierLeadJob)
|
|
|
|
|
|
// hardcodes Cache::store('redis'), bypassing phpunit.xml's
|
|
|
|
|
|
// CACHE_STORE=array. The Redis store is a shared external service —
|
|
|
|
|
|
// under `pest --parallel` every worker collides on the global
|
|
|
|
|
|
// `supplier:session` key, so one worker's afterEach forget()/flush()
|
|
|
|
|
|
// races another worker's mid-test loadSession(). Repoint the `redis`
|
|
|
|
|
|
// cache store at the in-process `array` driver: each worker then gets
|
|
|
|
|
|
// a hermetic, worker-local cache. Production keeps the real `redis`
|
|
|
|
|
|
// driver — this override only ever runs under APP_ENV=testing.
|
|
|
|
|
|
config(['cache.stores.redis.driver' => 'array']);
|
|
|
|
|
|
}
|
2026-07-02 11:07:22 +03:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Laravel вызывает этот хук из трейта RefreshDatabase сразу после migrate:fresh.
|
|
|
|
|
|
*
|
|
|
|
|
|
* migrate:fresh пересоздаёт только родительские партиционированные таблицы;
|
|
|
|
|
|
* месячные партиции — отдельная команда (только вперёд). Тесты с датами
|
|
|
|
|
|
* прошлых месяцев иначе падают на отсутствии партиции. Диапазон: 2 назад +
|
|
|
|
|
|
* текущий + 3 вперёд. Идемпотентно.
|
|
|
|
|
|
*
|
|
|
|
|
|
* DDL идёт через pgsql_supplier (как в MonthlyPartitionManager::DDL_CONNECTION),
|
|
|
|
|
|
* иначе на проде crm_app_user не имеет прав создавать партиции.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function afterRefreshingDatabase()
|
|
|
|
|
|
{
|
|
|
|
|
|
Artisan::call('partitions:create-months', ['--ahead' => 3]);
|
|
|
|
|
|
|
|
|
|
|
|
// Окно партиций считаем от РЕАЛЬНОГО системного времени (new DateTimeImmutable),
|
|
|
|
|
|
// а не от now()/Carbon: отдельные тесты замораживают Carbon::setTestNow на
|
|
|
|
|
|
// прошлые/будущие даты, и тогда единственный migrate:fresh пересоздавал бы
|
|
|
|
|
|
// месяцы вокруг замороженной даты, не покрывая реальный прошлый месяц (тесты
|
|
|
|
|
|
// с датами вроде now()->subDays(10) падали на отсутствии партиции). Диапазон
|
|
|
|
|
|
// -3..+5 месяцев. Идемпотентно. DDL через pgsql_supplier (DDL_CONNECTION) —
|
|
|
|
|
|
// у crm_app_user нет прав на создание партиций.
|
|
|
|
|
|
$base = new \DateTimeImmutable('first day of this month 00:00:00');
|
|
|
|
|
|
$parents = DB::select("select c.relname from pg_class c join pg_partitioned_table p on p.partrelid = c.oid where c.relkind = 'p'");
|
|
|
|
|
|
for ($m = -3; $m <= 5; $m++) {
|
|
|
|
|
|
$start = $base->modify(($m >= 0 ? '+' : '-').abs($m).' months');
|
|
|
|
|
|
$end = $start->modify('+1 month');
|
|
|
|
|
|
$suffix = '_y'.$start->format('Y').'_m'.$start->format('m');
|
|
|
|
|
|
foreach ($parents as $p) {
|
|
|
|
|
|
$t = $p->relname;
|
|
|
|
|
|
DB::connection('pgsql_supplier')->statement(
|
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS \"{$t}{$suffix}\" PARTITION OF \"{$t}\" FOR VALUES FROM ('{$start->format('Y-m-d')}') TO ('{$end->format('Y-m-d')}')"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-08 09:37:16 +03:00
|
|
|
|
}
|