Files
Gnuboard7/tests/Unit/Repositories/LayoutExtensionVersionRepositoryTest.php
HeuJung 0b6f329249 fix(core): 레이아웃 버전 변경량 계산의 메모리 초과와 편집기 확장 편집·재로드·충돌 안내·버전 기록 결함 수정
- 버전 이력 변경량(+N/-N 줄) 계산이 변경 영역 (줄 수)² 크기의 LCS 표를 만들어
 큰 공통 레이아웃의 첫 편집기 저장이 PHP 기본 메모리 한도(128M) 서버에서
 500 으로 끝났다. 두 행 DP 로 길이만 구하도록 바꿔 메모리가 줄 수에 비례하고
 표시 숫자는 종전과 같다(참조 구현 동치·메모리 상한 회귀 테스트)
- 확장 편집 모드 저장이 overlay 확장의 injections 를 비우던 결함: 서버가 주입
 노드 출처 메타에 injection 순번을 싣고, 편집기는 그 순번(없으면 원본 노드 id)
 으로 되돌리며, 되돌릴 수 없는 노드가 있으면 저장하지 않고 안내한다
- 서빙 캐시 키를 서버 현재 확장 캐시 버전으로만 조립해 저장·복원 두 번째부터
 재로드·「최신 불러오기」가 옛 내용을 받던 결함 수정(요청 v 는 HTTP 캐시 우회용)
- 409 충돌 안내가 errors 아래의 버전을 읽지 못해 「최신 버전: -1」 로 표시되던
 결함을 세 저장 경로 공용 판독으로 수정
- 버전 저장 시 저장자를 기록하고, 복원 시에도 잠금 번호를 올린다
- 회귀 테스트(PHPUnit·Vitest), 트러블슈팅 사례 32~34, 규정·API 문서, ja 언어팩,
 편집기 번들 재빌드, 이력 문서 동반
2026-09-09 12:19:12 +09:00

250 lines
9.2 KiB
PHP

<?php
namespace Tests\Unit\Repositories;
use App\Models\LayoutExtension;
use App\Models\Template;
use App\Models\TemplateLayoutExtensionVersion;
use App\Models\User;
use App\Repositories\LayoutExtensionVersionRepository;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* 레이아웃 확장 버전 리포지토리 테스트
*/
class LayoutExtensionVersionRepositoryTest extends TestCase
{
use RefreshDatabase;
/**
* 같은 스위트의 레이아웃/GDPR 미들웨어 의존 테스트와 migrate:fresh 정합성을
* 맞추기 위해 GDPR 플러그인 마이그레이션을 일관 선언한다.
*
* @var array<string>
*/
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);
}
}