Files
2026-05-12 17:58:34 +09:00

243 lines
8.2 KiB
PHP

<?php
namespace Tests\Unit\Extension\Upgrade;
use App\Extension\Upgrade\DataSnapshot;
use App\Extension\Upgrade\SnapshotApplier;
use App\Extension\UpgradeContext;
use Illuminate\Support\Facades\File;
use RuntimeException;
use Tests\TestCase;
/**
* DataSnapshot 의 manifest 로딩 / Applier 동적 로드 / versionedNamespace 검증.
*
* 본 테스트는 실제 DB 를 건드리지 않고 fixture 디렉토리만 사용.
*/
class DataSnapshotTest extends TestCase
{
private string $fixtureDir;
protected function setUp(): void
{
parent::setUp();
$this->fixtureDir = storage_path('framework/testing/datasnapshot-'.uniqid());
File::ensureDirectoryExists($this->fixtureDir);
}
protected function tearDown(): void
{
if (File::isDirectory($this->fixtureDir)) {
File::deleteDirectory($this->fixtureDir);
}
parent::tearDown();
}
public function test_empty_returns_no_appliers(): void
{
$snapshot = DataSnapshot::empty();
$this->assertSame([], $snapshot->appliers);
}
public function test_fromManifest_returns_empty_when_manifest_absent(): void
{
$context = $this->makeContext('9.9.9-test.empty');
$snapshot = DataSnapshot::fromManifest($this->fixtureDir, $context);
$this->assertSame([], $snapshot->appliers);
}
public function test_versionedNamespace_converts_version_to_namespace(): void
{
$context = $this->makeContext('7.0.0-beta.5');
$this->assertSame(
'App\\Upgrades\\Data\\V7_0_0_beta_5',
DataSnapshot::versionedNamespace($context),
);
$context2 = $this->makeContext('1.2.3');
$this->assertSame(
'App\\Upgrades\\Data\\V1_2_3',
DataSnapshot::versionedNamespace($context2),
);
}
public function test_versionedNamespace_uses_core_pattern_when_source_path_is_core(): void
{
$context = $this->makeContext('7.0.0-beta.5');
$this->assertSame(
'App\\Upgrades\\Data\\V7_0_0_beta_5',
DataSnapshot::versionedNamespace(
$context,
'/var/www/upgrades/data/7.0.0-beta.5',
),
);
}
public function test_versionedNamespace_inserts_module_segment_when_source_path_is_bundled_module(): void
{
$context = $this->makeContext('1.2.0');
$this->assertSame(
'App\\Upgrades\\Data\\Ext\\Modules\\SirsoftEcommerce\\V1_2_0',
DataSnapshot::versionedNamespace(
$context,
'/var/www/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.2.0',
),
);
}
public function test_versionedNamespace_inserts_plugin_segment_when_source_path_is_bundled_plugin(): void
{
$context = $this->makeContext('2.0.0');
$this->assertSame(
'App\\Upgrades\\Data\\Ext\\Plugins\\SirsoftPayment\\V2_0_0',
DataSnapshot::versionedNamespace(
$context,
'/var/www/plugins/_bundled/sirsoft-payment/upgrades/data/2.0.0',
),
);
}
public function test_versionedNamespace_uses_ext_segment_for_external_extension_paths(): void
{
$context = $this->makeContext('0.3.1');
// 외부 확장 (modules/{not _bundled}) 도 동일 격리 패턴
$this->assertSame(
'App\\Upgrades\\Data\\Ext\\Modules\\VendorExternalMod\\V0_3_1',
DataSnapshot::versionedNamespace(
$context,
'/var/www/modules/vendor-external-mod/upgrades/data/0.3.1',
),
);
}
public function test_versionedNamespace_different_extensions_same_step_version_produce_different_namespaces(): void
{
// 본 PR 이 해결한 namespace 충돌 회귀 — 같은 step 버전 + 다른 확장 → 다른 namespace.
$context = $this->makeContext('1.0.0');
$nsA = DataSnapshot::versionedNamespace(
$context,
'/var/www/modules/_bundled/vendor-foo/upgrades/data/1.0.0',
);
$nsB = DataSnapshot::versionedNamespace(
$context,
'/var/www/modules/_bundled/vendor-bar/upgrades/data/1.0.0',
);
$nsCore = DataSnapshot::versionedNamespace(
$context,
'/var/www/upgrades/data/1.0.0',
);
$this->assertNotSame($nsA, $nsB, '두 다른 확장은 같은 step 버전이라도 다른 namespace 가 되어야 함');
$this->assertNotSame($nsA, $nsCore);
$this->assertNotSame($nsB, $nsCore);
}
public function test_fromManifest_loads_applier_with_versioned_namespace(): void
{
$version = '0.0.1-snapshot.test';
$context = $this->makeContext($version);
// fixture: manifest + delta JSON + Applier (버전 namespace)
File::put($this->fixtureDir.'/manifest.json', json_encode([
'version' => $version,
'files' => [
['kind' => 'sample', 'file' => 'sample.delta.json'],
],
]));
File::put($this->fixtureDir.'/sample.delta.json', json_encode(['added' => []]));
File::ensureDirectoryExists($this->fixtureDir.'/appliers');
$token = 'V'.str_replace(['.', '-'], '_', $version);
File::put($this->fixtureDir.'/appliers/SampleApplier.php', <<<PHP
<?php
namespace App\Upgrades\Data\\{$token}\Appliers;
use App\Extension\Upgrade\SnapshotApplier;
use App\Extension\UpgradeContext;
class SampleApplier implements SnapshotApplier
{
public function __construct(public readonly string \$jsonPath) {}
public function apply(UpgradeContext \$context): void
{
// no-op
}
}
PHP);
$snapshot = DataSnapshot::fromManifest($this->fixtureDir, $context);
$this->assertCount(1, $snapshot->appliers);
$this->assertInstanceOf(SnapshotApplier::class, $snapshot->appliers[0]);
$this->assertSame(
$this->fixtureDir.'/sample.delta.json',
$snapshot->appliers[0]->jsonPath,
);
}
public function test_fromManifest_throws_when_applier_file_missing(): void
{
$context = $this->makeContext('0.0.2-missing.applier');
File::put($this->fixtureDir.'/manifest.json', json_encode([
'version' => '0.0.2-missing.applier',
'files' => [
['kind' => 'permissions', 'file' => 'permissions.delta.json'],
],
]));
File::put($this->fixtureDir.'/permissions.delta.json', json_encode(['added' => []]));
// appliers/ 디렉토리에 PermissionsApplier 파일 미작성
$this->expectException(RuntimeException::class);
$this->expectExceptionMessageMatches('/Applier file not found/');
DataSnapshot::fromManifest($this->fixtureDir, $context);
}
public function test_fromManifest_throws_when_namespace_mismatch(): void
{
$version = '0.0.3-wrong.ns';
$context = $this->makeContext($version);
File::put($this->fixtureDir.'/manifest.json', json_encode([
'version' => $version,
'files' => [
['kind' => 'foo', 'file' => 'foo.delta.json'],
],
]));
File::put($this->fixtureDir.'/foo.delta.json', json_encode(['added' => []]));
File::ensureDirectoryExists($this->fixtureDir.'/appliers');
// 잘못된 namespace 로 클래스 선언 (V0_0_3_wrong_ns 가 아닌 임의 namespace)
File::put($this->fixtureDir.'/appliers/FooApplier.php', <<<'PHP'
<?php
namespace App\Upgrades\Data\WrongNamespace\Appliers;
use App\Extension\Upgrade\SnapshotApplier;
use App\Extension\UpgradeContext;
class FooApplier implements SnapshotApplier
{
public function __construct(public readonly string $jsonPath) {}
public function apply(UpgradeContext $context): void {}
}
PHP);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessageMatches('/Applier class not declared in expected namespace/');
DataSnapshot::fromManifest($this->fixtureDir, $context);
}
private function makeContext(string $version): UpgradeContext
{
return new UpgradeContext(
fromVersion: '0.0.0',
toVersion: $version,
currentStep: $version,
);
}
}