*/ protected array $requiredExtensions = [ 'plugins/sirsoft-gdpr', ]; private LayoutExtensionVersionRepository $repository; private Template $template; private LayoutExtension $extension; protected function setUp(): void { parent::setUp(); $this->repository = new LayoutExtensionVersionRepository(new TemplateLayoutExtensionVersion); $this->template = Template::factory()->create(); $this->extension = LayoutExtension::factory()->create(['template_id' => $this->template->id]); } /** * saveVersion - 첫 번째 버전 생성 */ public function test_save_version_creates_first_version(): void { $content = ['extension_point' => 'header', 'components' => []]; $version = $this->repository->saveVersion($this->extension->id, $content); $this->assertInstanceOf(TemplateLayoutExtensionVersion::class, $version); $this->assertEquals(1, $version->version); $this->assertEquals($this->extension->id, $version->extension_id); $this->assertEquals($content, $version->content); } /** * saveVersion - 버전 자동 증가 */ public function test_save_version_auto_increments(): void { TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $this->extension->id, 'version' => 1]); TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $this->extension->id, 'version' => 2]); $newVersion = $this->repository->saveVersion($this->extension->id, ['test' => 'data']); $this->assertEquals(3, $newVersion->version); } /** * getVersions - 최신순 정렬 */ public function test_get_versions_returns_latest_first(): void { foreach ([1, 3, 2] as $v) { TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $this->extension->id, 'version' => $v]); } $versions = $this->repository->getVersions($this->extension->id); $this->assertCount(3, $versions); $this->assertEquals(3, $versions->first()->version); $this->assertEquals(1, $versions->last()->version); } /** * getNextVersion - 버전 없을 때 1 반환 */ public function test_get_next_version_returns_one_when_empty(): void { $this->assertEquals(1, $this->repository->getNextVersion($this->extension->id)); } /** * getNextVersion - max + 1 */ public function test_get_next_version_returns_max_plus_one(): void { foreach ([1, 5, 3] as $v) { TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $this->extension->id, 'version' => $v]); } $this->assertEquals(6, $this->repository->getNextVersion($this->extension->id)); } /** * 버전은 확장별로 격리됨 */ public function test_versions_are_isolated_by_extension(): void { $otherExtension = LayoutExtension::factory()->create(['template_id' => $this->template->id]); TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $otherExtension->id, 'version' => 10]); $this->assertEquals(1, $this->repository->getNextVersion($this->extension->id)); $this->assertCount(0, $this->repository->getVersions($this->extension->id)); } /** * calculateChanges - trait 동작 (라인 단위 카운트) * * changes_summary 는 버전 비교 diff 뷰와 동일한 라인 단위로 센다. 키 정렬 직렬화 * 후 라인 LCS 로 추가/삭제 라인 수를 집계한다(added/removed 는 정수 카운트, modified * 키는 없음 — 값 변경은 삭제+추가 라인으로 표현). */ public function test_calculate_changes_detects_diff(): void { // name 값 변경(삭제+추가), deprecated 삭제(1줄), added 키 추가(1줄) $old = ['name' => 'test', 'deprecated' => 'old']; $new = ['name' => 'changed', 'added' => 'new']; $changes = $this->repository->calculateChanges($old, $new); // added 라인: "added": "new", + "name": "changed", = 2줄 $this->assertSame(2, $changes['added']); // removed 라인: "deprecated": "old", + "name": "test", = 2줄 $this->assertSame(2, $changes['removed']); $this->assertArrayNotHasKey('modified', $changes, 'modified 키는 두지 않는다'); } /** * restoreVersion - 트랜잭션 내 복원 + 새 버전 생성 */ public function test_restore_version_restores_content_and_creates_new_version(): void { $oldContent = ['extension_point' => 'header', 'components' => [['type' => 'basic', 'name' => 'Span']]]; $this->extension->update(['content' => $oldContent]); // 복원 대상 버전 (버전 1) — 이전 content 스냅샷 $versionToRestore = TemplateLayoutExtensionVersion::factory()->create([ 'extension_id' => $this->extension->id, 'version' => 1, 'content' => $oldContent, ]); // 현재 content 를 다른 값으로 변경 $this->extension->update(['content' => ['extension_point' => 'footer']]); $newVersion = $this->repository->restoreVersion($this->extension->id, $versionToRestore->id); // 확장 content 가 복원됨 $this->extension->refresh(); $this->assertEquals($oldContent, $this->extension->content); // 새 버전 생성됨 (버전 2) $this->assertEquals(2, $newVersion->version); } /** * restoreVersion - 존재하지 않는 버전은 예외 */ public function test_restore_version_throws_when_version_not_found(): void { $this->expectException(ModelNotFoundException::class); $this->repository->restoreVersion($this->extension->id, 99999); } /** * getCurrentVersionsByExtensionIds — 확장별 최신 버전 맵 */ public function test_get_current_versions_by_extension_ids_returns_latest_version_map(): void { $other = LayoutExtension::factory()->create(['template_id' => $this->template->id]); $noHistory = LayoutExtension::factory()->create(['template_id' => $this->template->id]); foreach ([1, 2, 3] as $v) { TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $this->extension->id, 'version' => $v]); } TemplateLayoutExtensionVersion::factory()->create(['extension_id' => $other->id, 'version' => 1]); $map = $this->repository->getCurrentVersionsByExtensionIds([ $this->extension->id, $other->id, $noHistory->id, ]); ksort($map); $expected = [$this->extension->id => 3, $other->id => 1]; ksort($expected); $this->assertSame($expected, $map); $this->assertArrayNotHasKey($noHistory->id, $map); } /** * getCurrentVersionsByExtensionIds — 빈 입력/이력 전무 시 빈 맵 */ public function test_get_current_versions_by_extension_ids_returns_empty_for_empty_input(): void { $this->assertSame([], $this->repository->getCurrentVersionsByExtensionIds([])); $this->assertSame([], $this->repository->getCurrentVersionsByExtensionIds([$this->extension->id])); } /** * saveVersion 은 인증 사용자를 created_by 로 기록한다(레이아웃 본체와 동형). */ public function test_save_version_records_authenticated_user_as_creator(): void { $user = User::factory()->create(); $this->actingAs($user); $version = $this->repository->saveVersion($this->extension->id, ['extension_point' => 'header', 'components' => []]); $this->assertSame($user->id, $version->created_by); } /** * restoreVersion 은 확장 lock_version 을 올린다(레이아웃 본체와 동형). */ public function test_restore_version_increments_extension_lock_version(): void { $oldContent = ['extension_point' => 'header', 'components' => [['type' => 'basic', 'name' => 'Span']]]; $this->extension->forceFill(['lock_version' => 2, 'content' => ['extension_point' => 'footer']])->save(); $target = TemplateLayoutExtensionVersion::factory()->create([ 'extension_id' => $this->extension->id, 'version' => 1, 'content' => $oldContent, ]); $this->repository->restoreVersion($this->extension->id, $target->id); $this->extension->refresh(); $this->assertSame(3, (int) $this->extension->lock_version); $this->assertEquals($oldContent, $this->extension->content); } }