- 템플릿 update 는 레이아웃 변경 여부와 무관하게 캐시 버전을 올리고(실패 복원 뒤에도), 자산 주소 방식 전환 3경로도 bump 한다 - config:cache 가 컨테이너 인스턴스를 덮어 이후 terminating 재게시가 사라지던 결함을 복원 헬퍼로 차단 - custom/ 변경 감지와 게시가 같은 열거자(재귀·크기 포함)를 쓰고, 서명은 호스트별로 저장 - 캐시 버전·서명 키를 만료시키지 않는다(기본 TTL 24h 로 매일 전체 재생성되던 문제) - 관리자 > 환경설정 > 일반 「초기 화면 정적 파일」 카드: 상태 조회 + 지금 다시 만들기(확인 모달), 대시보드 알림 버튼 연결, CLI 와 같은 statusReport 소비 - sudo core:update 경로의 root 소유 잔존(설정 디렉토리·임시 폴더·로그·업그레이드 마이그레이션 산출물) 상속·정합화, ext-static 명령 root 경고와 sudo -u 힌트 - 안내·문서·트러블슈팅 4건·audit 룰 ext-cache-version-raw-read·ja 언어팩 동기
146 lines
5.6 KiB
PHP
146 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Extension;
|
|
|
|
use App\Extension\Helpers\ExtensionPendingHelper;
|
|
use App\Extension\ModuleManager;
|
|
use App\Extension\PluginManager;
|
|
use App\Extension\TemplateManager;
|
|
use Illuminate\Support\Facades\File;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* 확장 설정 시드·업데이트 임시 폴더의 소유권 상속 회귀 테스트 (#651 B2·B4).
|
|
*
|
|
* sudo 코어 업데이트(번들 확장 업데이트 프롬프트)가 root 로 `storage/app/{modules,plugins}/{id}/settings/`
|
|
* 를 만들면 그 서브트리는 restore_ownership 의도적 제외 경로라 코어 업데이트로도 되돌려지지 않고,
|
|
* 이후 웹 프로세스의 설정 저장이 영구 실패한다. 임시 폴더의 부모 `storage/app/temp` 도 최초 생성자가
|
|
* root 면 같은 결말이다(자식만 삭제된다). 소유권 축은 Windows 에서 실측할 수 없으므로 저장소 관례
|
|
* (소스 훑기)로 상속 호출의 존재를 잠근다.
|
|
*/
|
|
class ExtensionSettingsDirectoryOwnershipTest extends TestCase
|
|
{
|
|
/**
|
|
* 클래스 소스에서 주석을 제거한 본문을 돌려줍니다.
|
|
*/
|
|
private function strippedSource(string $class): string
|
|
{
|
|
$path = (new \ReflectionClass($class))->getFileName();
|
|
$stripped = '';
|
|
|
|
foreach (token_get_all((string) file_get_contents($path)) as $token) {
|
|
if (is_array($token) && in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) {
|
|
continue;
|
|
}
|
|
$stripped .= is_array($token) ? $token[1] : $token;
|
|
}
|
|
|
|
return $stripped;
|
|
}
|
|
|
|
/**
|
|
* `makeDirectory($settingsDir …)` 뒤 5줄 안에 상속 호출이 있는지 판정합니다.
|
|
*/
|
|
private function assertMakeDirectoryInheritsOwnership(string $class): void
|
|
{
|
|
$source = $this->strippedSource($class);
|
|
$offset = 0;
|
|
$found = 0;
|
|
|
|
while (($pos = strpos($source, 'File::makeDirectory($settingsDir', $offset)) !== false) {
|
|
$window = substr($source, $pos, 400);
|
|
$lines = array_slice(explode("\n", $window), 0, 6);
|
|
|
|
$this->assertStringContainsString(
|
|
'FilePermissionHelper::inheritOwnershipFromParent($settingsDir)',
|
|
implode("\n", $lines),
|
|
$class.': settings 디렉토리 생성 뒤 부모 소유권 상속이 없다 — sudo 코어 업데이트 뒤 그 확장의 설정 저장이 영구 실패한다'
|
|
);
|
|
|
|
$found++;
|
|
$offset = $pos + 1;
|
|
}
|
|
|
|
$this->assertGreaterThan(0, $found, $class.': settings 디렉토리 생성 지점을 찾지 못했다');
|
|
}
|
|
|
|
/**
|
|
* @effects settings_directory_seed_inherits_ownership
|
|
*/
|
|
#[Test]
|
|
public function 모듈_설정_시드_디렉토리와_파일은_부모_소유권을_상속한다(): void
|
|
{
|
|
$this->assertMakeDirectoryInheritsOwnership(ModuleManager::class);
|
|
|
|
$source = $this->strippedSource(ModuleManager::class);
|
|
$this->assertStringContainsString(
|
|
"File::put(\$filePath, \$jsonContent);\n FilePermissionHelper::inheritOwnershipFromParent(\$filePath);",
|
|
$source,
|
|
'ModuleManager: 설정 시드 파일 쓰기 뒤 부모 소유권 상속이 없다'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @effects settings_directory_seed_inherits_ownership
|
|
*/
|
|
#[Test]
|
|
public function 플러그인_설정_시드_디렉토리와_파일은_부모_소유권을_상속한다(): void
|
|
{
|
|
$this->assertMakeDirectoryInheritsOwnership(PluginManager::class);
|
|
|
|
$source = $this->strippedSource(PluginManager::class);
|
|
$this->assertStringContainsString(
|
|
"File::put(\$settingsPath, \$content);\n FilePermissionHelper::inheritOwnershipFromParent(\$settingsPath);",
|
|
$source,
|
|
'PluginManager: 설정 시드 파일 쓰기 뒤 부모 소유권 상속이 없다'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 세 매니저의 업데이트 임시 폴더 확보가 공유 헬퍼를 경유한다 — 복사본은 갈라진다.
|
|
*
|
|
* @effects update_temp_directory_inherits_ownership
|
|
*/
|
|
#[Test]
|
|
public function 업데이트_임시_폴더는_공유_헬퍼로_확보한다(): void
|
|
{
|
|
foreach ([ModuleManager::class, PluginManager::class, TemplateManager::class] as $class) {
|
|
$source = $this->strippedSource($class);
|
|
|
|
$this->assertStringContainsString(
|
|
'ExtensionPendingHelper::ensureUpdateTempDirectory($tempDir)',
|
|
$source,
|
|
$class.': 업데이트 임시 폴더를 공유 헬퍼로 확보하지 않는다'
|
|
);
|
|
$this->assertStringNotContainsString(
|
|
"File::ensureDirectoryExists(\$tempDir);\n",
|
|
$source,
|
|
$class.': 임시 폴더를 직접 생성하는 옛 경로가 남아 있다 — 부모 storage/app/temp 가 root 로 굳는다'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공유 헬퍼는 부모를 먼저 확보하고 자식에 소유권을 상속시킨다 (동작).
|
|
*
|
|
* @effects update_temp_directory_inherits_ownership
|
|
*/
|
|
#[Test]
|
|
public function 공유_헬퍼는_부모와_자식을_함께_확보한다(): void
|
|
{
|
|
$parent = storage_path('framework/testing/temp-parent-'.uniqid());
|
|
$child = $parent.'/module_update_'.uniqid();
|
|
|
|
try {
|
|
ExtensionPendingHelper::ensureUpdateTempDirectory($child);
|
|
|
|
$this->assertDirectoryExists($parent);
|
|
$this->assertDirectoryExists($child);
|
|
$this->assertTrue(is_writable($child));
|
|
} finally {
|
|
File::deleteDirectory($parent);
|
|
}
|
|
}
|
|
}
|