Files
Gnuboard7/tests/Feature/Console/CoreUpdateResumeGuidanceTest.php
HeuJung a10200164c fix(core): 코어 업데이트 스텝 재실행 안내에 실행 권한별 4분기 지침 추가
sudo(root) 로 core:update 를 실행하면 업그레이드 스텝 spawn 자식이 실패할 때
운영자에게 core:execute-upgrade-steps 재실행을 안내하는데, 이 명령을 root 로
그대로 재실행하면 스텝이 만드는 파일이 root 소유가 되어 이후 웹서버(www-data)
요청이 그 경로에 쓰기 실패한다.

재실행 안내를 실행 환경 4가지로 분기한다:
- non_root: 일반 SSH 사용자 / 공유 호스팅(웹서버=PHP=실행 유저) / posix 미지원 → 명령만
- root_web_known: 웹서버 계정 식별 → sudo -u {계정} + 계정명 경고
- root_web_symmetric: root 서비스 구성 → 명령만
- root_web_unknown: 계정 추정 실패 → placeholder + 확인 방법 안내

웹서버 계정은 FilePermissionHelper::inferWebServerOwnership 로 추정한다.
출력 전담 renderResumeGuidance 를 분리해 4분기 출력 계약을 회귀 테스트로 고정.
2026-07-03 19:16:11 +09:00

108 lines
4.6 KiB
PHP

<?php
namespace Tests\Feature\Console;
use App\Console\Commands\Core\CoreUpdateCommand;
use Illuminate\Console\OutputStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Tests\TestCase;
/**
* CoreUpdateCommand 의 업그레이드 스텝 재실행 권한 안내 (renderResumeGuidance) 계약 테스트.
*
* 배경: sudo(root) 로 `core:update` 를 실행하면 업그레이드 스텝 spawn 자식이 root 로
* 돌아가고, 그 자식이 실패하면 스텝이 미실행 상태로 남아 운영자에게 재실행을 안내한다.
* 이때 실행 사용자 권한에 따라 4가지 경우로 분기해 안내해야 한다:
*
* 1. non_root — 일반 SSH 사용자 / 공유 호스팅(웹서버=PHP=실행 유저) / posix 미지원
* → 명령만 그대로. sudo -u 접두사·경고 없음.
* 2. root_web_known — root 실행 + 웹서버 계정 식별 가능
* → `sudo -u {webUser}` + 계정명 명시 경고.
* 3. root_web_symmetric — root 실행 + 웹서버 계정이 root 로 추정 (root 서비스 구성)
* → 명령만 그대로.
* 4. root_web_unknown — root 실행 + 웹서버 계정 식별 불가
* → `sudo -u <웹서버계정>` placeholder + 계정명 미상 경고.
*
* `classifyResumeExecutionContext()` 는 posix/파일시스템 상태에 의존하므로, 판정 결과를
* 입력으로 받아 출력만 담당하는 `renderResumeGuidance()` 를 리플렉션으로 직접 검증한다.
*/
class CoreUpdateResumeGuidanceTest extends TestCase
{
private const RESUME = 'php artisan core:execute-upgrade-steps --from=7.0.0 --to=7.0.1 --force';
/**
* 모드/유저를 입력해 renderResumeGuidance 를 실행하고 콘솔 출력 문자열을 반환한다.
*/
private function render(string $mode, ?string $webUser): string
{
$command = app(CoreUpdateCommand::class);
$input = new ArrayInput([]);
$output = new BufferedOutput;
$style = new OutputStyle($input, $output);
$reflection = new \ReflectionClass($command);
$property = $reflection->getProperty('output');
$property->setAccessible(true);
$property->setValue($command, $style);
$method = new \ReflectionMethod(CoreUpdateCommand::class, 'renderResumeGuidance');
$method->setAccessible(true);
$method->invoke($command, self::RESUME, $mode, $webUser);
return $output->fetch();
}
/**
* 케이스 1: non_root — 명령만 그대로. sudo -u 접두사·경고 없음.
*/
public function test_non_root_prints_command_verbatim_without_permission_warning(): void
{
$out = $this->render('non_root', null);
$this->assertStringContainsString(self::RESUME, $out);
$this->assertStringNotContainsString('sudo -u', $out);
$this->assertStringNotContainsString('root', $out);
}
/**
* 케이스 2: root_web_known — sudo -u {webUser} 접두사 + 계정명 명시 경고.
*/
public function test_root_web_known_prefixes_sudo_and_warns_with_account_name(): void
{
$out = $this->render('root_web_known', 'www-data');
$this->assertStringContainsString('sudo -u www-data '.self::RESUME, $out);
// 계정명이 경고 문구에 노출되어야 한다
$this->assertStringContainsString('www-data', $out);
$this->assertStringContainsString('root', $out);
// placeholder 는 나오면 안 된다 (계정 식별 성공 경로)
$this->assertStringNotContainsString('<웹서버계정>', $out);
}
/**
* 케이스 3: root_web_symmetric — root 서비스 구성 → 명령만 그대로.
*/
public function test_root_web_symmetric_prints_command_verbatim(): void
{
$out = $this->render('root_web_symmetric', null);
$this->assertStringContainsString(self::RESUME, $out);
$this->assertStringNotContainsString('sudo -u', $out);
}
/**
* 케이스 4: root_web_unknown — placeholder + 계정명 미상 경고.
*/
public function test_root_web_unknown_uses_placeholder_and_generic_warning(): void
{
$out = $this->render('root_web_unknown', null);
$this->assertStringContainsString('sudo -u <웹서버계정> '.self::RESUME, $out);
$this->assertStringContainsString('식별하지 못했습니다', $out);
// 대표 웹서버 계정 예시를 안내에 포함해 운영자가 직접 확인하도록 유도
$this->assertStringContainsString('www-data', $out);
}
}