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