64 lines
2.7 KiB
PHP
64 lines
2.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Tests\Unit\Services\Project;
|
||
|
||
use App\Http\Controllers\Api\ProjectController;
|
||
use App\Services\Project\ProjectService;
|
||
use ReflectionMethod;
|
||
use Tests\TestCase;
|
||
|
||
/**
|
||
* Гарантирует, что переключение `is_active` всегда сопровождается записью `paused_at`:
|
||
* - is_active = false → paused_at := NOW()
|
||
* - is_active = true → paused_at := null
|
||
*
|
||
* Без этого SupplierSnapshotGuard для bulk-paused проектов начнёт считать их
|
||
* "защищёнными навсегда" (paused_at NULL trait), и удаление никогда не разблокируется.
|
||
*
|
||
* Тест читает исходник методов и проверяет наличие явной записи `paused_at` рядом
|
||
* с записью `is_active`. Это структурный smoke — поведенческие тесты (через БД)
|
||
* пишутся отдельно (Task 14 final regression).
|
||
*
|
||
* Spec: docs/superpowers/plans/2026-05-26-supplier-snapshot-guard.md (Task 11).
|
||
*/
|
||
class PausedAtWriteSideTest extends TestCase
|
||
{
|
||
public function test_project_service_bulk_pause_resume_writes_paused_at(): void
|
||
{
|
||
$body = $this->methodBody(ProjectService::class, 'bulkPauseResume');
|
||
|
||
$this->assertStringContainsString('paused_at', $body,
|
||
'bulkPauseResume должен явно обновлять paused_at вместе с is_active');
|
||
$this->assertStringContainsString('is_active', $body);
|
||
}
|
||
|
||
public function test_project_controller_toggle_active_writes_paused_at(): void
|
||
{
|
||
$body = $this->methodBody(ProjectController::class, 'toggleActive');
|
||
|
||
$this->assertStringContainsString('paused_at', $body,
|
||
'toggleActive должен явно обновлять paused_at вместе с is_active');
|
||
$this->assertStringContainsString('is_active', $body);
|
||
}
|
||
|
||
public function test_bulk_delete_distinguishes_supplier_snapshot_lock_from_has_deals(): void
|
||
{
|
||
$body = $this->methodBody(ProjectService::class, 'bulkDelete');
|
||
|
||
$this->assertStringContainsString('supplier_snapshot_locked', $body,
|
||
'bulkDelete должен помечать пропущенные проекты reason="supplier_snapshot_locked" при guard-блоке');
|
||
$this->assertStringContainsString('has_deals', $body);
|
||
}
|
||
|
||
private function methodBody(string $class, string $method): string
|
||
{
|
||
$rm = new ReflectionMethod($class, $method);
|
||
$lines = file($rm->getFileName());
|
||
$body = array_slice($lines, $rm->getStartLine() - 1, $rm->getEndLine() - $rm->getStartLine() + 1);
|
||
|
||
return implode('', $body);
|
||
}
|
||
}
|