Files
Gnuboard7/tests/Unit/TestCaseKillStaleConnectionsTest.php
HeuJung 42eb21320b fix(ecommerce,core): 현금영수증 UI 보완 + 하네스 결함 6건 + 확장점 룰 사각 제거
현금영수증/환불계좌 기능(S3)의 감사 보완과, 그 과정에서 발견한 하네스 결함을
같은 세션에서 일괄 처리한다. 지시에 따라 무관 산출물(namuwiki 문서 초안 포함)도
단일 커밋으로 묶는다.

- 현금영수증 발급 이력 접기/펼치기(W-3) + 결제행별 독립 토글 + aria 상태
- vendor-bundle manifest 해시 재생성 — composer.json version bump 미반영분 정합
- 시나리오 매니페스트 거짓 경로 정정(A/B) + 미작성 부채 :test-files 정직 표기(C)
- layout-* 룰의 resources/extensions 검사 사각 제거 + 확장점 전용 root 스키마 분기
- validate-test-scenario.cjs: 맵형 test_files 크래시 + fallback 파서 covers 오검출 수정
- tosspayments 확장점 setState target 규약 위반 수정
2026-08-06 12:36:31 +09:00

114 lines
4.2 KiB
PHP

<?php
namespace Tests\Unit;
use Illuminate\Support\Facades\DB;
use PDO;
use ReflectionMethod;
use Tests\TestCase;
/**
* TestCase::killStaleTestingConnections() 회귀 테스트.
*
* 배경: mysql 커넥션은 read/write 분리 구조(config/database.php)라 최상위 'database'
* 키가 존재하지 않는다. 과거 구현은 `config('database.connections.mysql.database')` 를
* 읽어 항상 null 을 얻었고, 그 결과
*
* if (($process->db ?? '') === $testingDb && ...) // $testingDb === null
*
* 조건이 어떤 커넥션과도 매칭되지 않았다(`'' === null` 도, `'g7_testing' === null` 도 false).
* 즉 좀비 커넥션 정리가 전면 무력화된 채로 조용히 통과하고 있었다. 정리 실패는 예외를
* 던지지 않으므로 아무도 눈치채지 못한다.
*
* 본 테스트는 테스트 DB 에 붙은 별도 커넥션(좀비 대역)을 만들어 두고 메서드를 실행해,
* 그 커넥션이 실제로 KILL 되는지를 확인한다. 판정값이 null 로 회귀하면 좀비가 살아남아 red.
*/
class TestCaseKillStaleConnectionsTest extends TestCase
{
/**
* 테스트 DB 에 붙은 별도 PDO 커넥션을 만듭니다 (좀비 대역).
*
* @return array{0: PDO, 1: int} PDO 와 그 커넥션 ID
*/
private function openZombie(): array
{
$c = config('database.connections.mysql.write');
$pdo = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s', $c['host'][0] ?? $c['host'], $c['port'], $c['database']),
$c['username'],
$c['password'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$id = (int) $pdo->query('SELECT CONNECTION_ID()')->fetchColumn();
return [$pdo, $id];
}
/**
* PROCESSLIST 에 해당 커넥션 ID 가 남아 있는지 확인합니다.
*/
private function connectionAlive(int $id): bool
{
foreach (DB::select('SHOW PROCESSLIST') as $p) {
if ((int) $p->Id === $id) {
return true;
}
}
return false;
}
private function invokeKiller(): void
{
$m = new ReflectionMethod(TestCase::class, 'killStaleTestingConnections');
$m->setAccessible(true);
$m->invoke($this);
}
public function test_좀비는_정리하고_자기_커넥션은_살려둔다(): void
{
$testingDb = DB::connection()->getDatabaseName();
$selfId = (int) DB::selectOne('SELECT CONNECTION_ID() AS id')->id;
[$pdo, $zombieId] = $this->openZombie();
$this->assertTrue($this->connectionAlive($zombieId), '좀비 대역이 만들어져야 한다');
$this->assertNotSame($selfId, $zombieId, '좀비는 자기 커넥션과 달라야 한다');
$this->invokeKiller();
$this->assertFalse(
$this->connectionAlive($zombieId),
'테스트 DB 에 붙은 좀비 커넥션이 정리되어야 한다 '
.'(대상 DB 판정이 null 이면 어떤 행과도 매칭되지 않아 살아남는다)'
);
// 자기 커넥션은 KILL 대상에서 제외되므로 정리 직후에도 쿼리가 가능해야 한다.
// (연결 ID 동일성으로 단정하지 않는다 — 다른 커넥션을 KILL 하면 Laravel 이
// 재연결하며 ID 가 바뀔 수 있고, 그것은 결함이 아니다.)
$this->assertSame(
$testingDb,
DB::selectOne('SELECT DATABASE() AS d')->d,
'정리 직후에도 자기 커넥션으로 후속 쿼리가 가능해야 한다'
);
unset($pdo);
}
public function test_대상_db_판정이_실제_접속_db와_일치한다(): void
{
$target = DB::connection()->getDatabaseName();
$actual = DB::selectOne('SELECT DATABASE() AS d')->d;
$this->assertNotSame('', $target, '대상 DB 가 비면 좀비 정리가 무력화된다');
$this->assertSame($actual, $target, '판정값이 실제 접속 DB 와 달라서는 안 된다');
// 과거 구현이 읽던 최상위 키는 존재하지 않는다(구조적 사실).
$this->assertNull(
config('database.connections.mysql.database'),
'read/write 분리 커넥션에는 최상위 database 키가 없다'
);
}
}