stubStepFiles as $file) { if (File::exists($file)) { File::delete($file); } } parent::tearDown(); } public function test_throws_when_beta5_step_does_not_extend_abstract(): void { // stale 메모리 가드 우회를 위해 메모리 버전을 target 이상으로 config(['app.version' => '7.9.9']); // 7.9.9-test.guard.a 는 7.0.0-beta.5 보다 명확히 큼 (version_compare 의 pre-release // 해석에 영향 받지 않음). stub 은 AbstractUpgradeStep 미상속 → fatal 가드 발동. $version = '7_9_9_test_guard_a'; $className = 'Upgrade_'.$version; $path = base_path('upgrades/'.$className.'.php'); $this->stubStepFiles[] = $path; File::put($path, <<expectException(CoreUpdateOperationException::class); $this->expectExceptionMessageMatches('/must extend App\\\\Extension\\\\AbstractUpgradeStep/'); $service = app(CoreUpdateService::class); $service->runUpgradeSteps('7.0.0-beta.4', '7.9.9-test.guard.a'); } public function test_passes_when_beta5_step_extends_abstract(): void { config(['app.version' => '7.9.9']); $version = '7_9_9_test_guard_b'; $className = 'Upgrade_'.$version; $path = base_path('upgrades/'.$className.'.php'); $this->stubStepFiles[] = $path; File::put($path, <<runUpgradeSteps( '7.0.0-beta.4', '7.9.9-test.guard.b', function (string $v) use (&$executed): void { $executed[] = $v; }, ); $this->assertContains('7.9.9-test.guard.b', $executed); } public function test_legacy_pre_beta5_step_bypasses_guard(): void { // legacy beta.4 이하 step 은 AbstractUpgradeStep 미상속이어도 통과 (호환) config(['app.version' => '7.0.0-beta.5']); $version = '7_0_0_beta_4_test_guard_legacy'; $className = 'Upgrade_'.$version; $path = base_path('upgrades/'.$className.'.php'); $this->stubStepFiles[] = $path; File::put($path, <<runUpgradeSteps( '7.0.0-beta.3', '7.0.0-beta.4.test.guard.legacy', function (string $v) use (&$executed): void { $executed[] = $v; }, ); $this->assertContains('7.0.0-beta.4.test.guard.legacy', $executed); } }