Files
Gnuboard7/tests/Unit/Helpers/SettingsMigratorOwnershipTest.php
T
HeuJung de18159986 fix(core,admin_basic): 정적 게시 재게시 누락 3건 수정 + 관리자 수동 복구 UI + sudo core:update 소유권 정합
- 템플릿 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 언어팩 동기
2026-09-06 15:22:52 +09:00

156 lines
6.1 KiB
PHP

<?php
namespace Tests\Unit\Helpers;
use App\Extension\Helpers\FilePermissionHelper;
use App\Extension\Helpers\SettingsMigrator;
use Illuminate\Support\Facades\File;
use Tests\TestCase;
/**
* SettingsMigrator owner 상속 회귀 테스트.
*
* sudo update 흐름에서 모듈/플러그인 upgrade step 이 root 로 실행될 때
* `SettingsMigrator::writeJsonFile` 가 만드는 *.json 파일이 root 소유로 영구 잔존하는
* 회귀 차단:
*
* - 변경 1 로 storage/app/{modules,plugins} 가 chown 비대상이 된 후, settings *.json
* 파일에 후속 PHP-FPM 이 update 시도 시 쓰기 실패하는 케이스 차단
* - writeJsonFile 후 부모 디렉토리(예: storage/app/modules/{id}/settings/) 의 owner/group
* 을 상속하여 PHP-FPM 시드 시점 owner 가 PHP-FPM 이라면 자동 일치
*
* 본 테스트는 `FilePermissionHelper::inheritOwnershipFromParent` 가 public static 으로
* 노출되어 외부(SettingsMigrator) 가 호출 가능한지 검증.
*/
class SettingsMigratorOwnershipTest extends TestCase
{
/**
* @var array<int, string>
*/
private array $tempDirs = [];
protected function tearDown(): void
{
foreach ($this->tempDirs as $dir) {
if (File::isDirectory($dir)) {
File::deleteDirectory($dir);
}
}
parent::tearDown();
}
private function createTempDir(): string
{
$dir = storage_path('test_settings_owner_'.uniqid());
File::ensureDirectoryExists($dir);
$this->tempDirs[] = $dir;
return $dir;
}
/**
* inheritOwnershipFromParent 가 public static 으로 노출되어야 함.
*
* 변경 7-(a): `SettingsMigrator::writeJsonFile` 같은 외부 호출처가 본 helper 를
* 사용해 부모 owner/group 을 상속하도록 노출.
*
* @return void
*/
public function test_inherit_ownership_from_parent_is_public(): void
{
$reflection = new \ReflectionMethod(FilePermissionHelper::class, 'inheritOwnershipFromParent');
$this->assertTrue(
$reflection->isPublic(),
'FilePermissionHelper::inheritOwnershipFromParent 가 public 이어야 외부 호출 가능 (SettingsMigrator 등)',
);
$this->assertTrue($reflection->isStatic(), 'static 메서드여야 함');
}
/**
* inheritOwnershipFromParent — 부모 디렉토리 stat 기반으로 owner/group 적용.
*
* POSIX 환경 전용. 일반 user 권한에서는 다른 user 로 chown 시도가 실패할 수 있으나,
* 자기 자신 owner 인 경우 chown 자체가 발생하지 않으므로 멱등.
*
* @return void
*/
public function test_inherit_ownership_from_parent_idempotent_for_self_owner(): void
{
if (DIRECTORY_SEPARATOR !== '/' || ! function_exists('chown')) {
$this->markTestSkipped('POSIX 환경 전용 (Windows 자동 스킵)');
}
$parent = $this->createTempDir();
$child = $parent.'/child.json';
file_put_contents($child, '{}');
$parentOwnerBefore = fileowner($parent);
$childOwnerBefore = fileowner($child);
// 자기 자신 owner 면 부모와 자식 owner 가 동일 → 멱등
FilePermissionHelper::inheritOwnershipFromParent($child);
$this->assertSame($parentOwnerBefore, fileowner($parent));
$this->assertSame($childOwnerBefore, fileowner($child));
$this->assertSame($parentOwnerBefore, fileowner($child), '자식 owner 가 부모와 동일');
}
/**
* settings 디렉토리 생성도 파일과 대칭으로 부모 소유권을 상속한다 (#651 B1).
*
* 파일(`writeJsonFile`)만 상속하고 디렉토리(`makeDirectory`)는 상속하지 않던 비대칭 — sudo
* 업그레이드 스텝이 디렉토리를 root 로 만들면 `storage/app/{modules,plugins}` 는 restore_ownership
* 의도적 제외 경로라 되돌려지지 않고, 이후 웹 프로세스의 그 모듈 설정 저장이 영구 실패한다.
*
* @effects settings_directory_seed_inherits_ownership
*/
public function test_settings_migrator_make_directory_invokes_inherit_ownership(): void
{
$body = (string) file_get_contents((new \ReflectionClass(SettingsMigrator::class))->getFileName());
$pos = strpos($body, 'File::makeDirectory($settingsDir');
$this->assertNotFalse($pos, 'SettingsMigrator 의 settings 디렉토리 생성 지점을 찾지 못했다');
$window = implode("\n", array_slice(explode("\n", substr($body, $pos)), 0, 4));
$this->assertStringContainsString(
'FilePermissionHelper::inheritOwnershipFromParent($settingsDir)',
$window,
'SettingsMigrator: settings 디렉토리 생성 뒤 부모 소유권 상속이 없다 (파일만 상속하는 비대칭)'
);
}
/**
* SettingsMigrator::writeJsonFile 호출 후 *.json 의 owner 가 부모와 일치.
*
* `SettingsMigrator` 는 protected 메서드 + non-static 이라 직접 호출이 어려우므로,
* 핵심 통합 보장 = "writeJsonFile 메서드 안에서 inheritOwnershipFromParent 호출" 을
* reflection 으로 검증.
*
* @return void
*/
public function test_settings_migrator_write_json_file_invokes_inherit_ownership(): void
{
$reflection = new \ReflectionClass(SettingsMigrator::class);
$method = $reflection->getMethod('writeJsonFile');
$body = file_get_contents($method->getFileName());
// writeJsonFile 메서드 내부에 inheritOwnershipFromParent 호출이 존재해야 함
$startLine = $method->getStartLine();
$endLine = $method->getEndLine();
$methodBody = implode("\n", array_slice(
explode("\n", $body),
$startLine - 1,
$endLine - $startLine + 1
));
$this->assertStringContainsString(
'inheritOwnershipFromParent',
$methodBody,
'SettingsMigrator::writeJsonFile 가 sudo update 시 root 로 만든 *.json 의 owner 를 부모로 상속해야 함 (FilePermissionHelper::inheritOwnershipFromParent 호출)',
);
}
}