Files
Gnuboard7/tests/Feature/Api/Public/LayoutOverlayInjectionIndexMetaTest.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

138 lines
5.5 KiB
PHP

<?php
namespace Tests\Feature\Api\Public;
use App\Enums\ExtensionStatus;
use App\Enums\LayoutExtensionType;
use App\Enums\LayoutSourceType;
use App\Models\LayoutExtension;
use App\Models\Template;
use App\Services\LayoutExtensionService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* [case:backend-34] overlay 확장이 주입한 노드의 `__source` 에 `injectionIndex` 가 실린다
*
* 편집기 확장 편집 모드는 호스트 병합 트리(`with_source_meta=1`)에서 현재 확장의 노드를
* 추출해 `injections[].components` 로 되돌려 저장한다. 어느 injection 에서 왔는지가
* 메타에 없으면 재조립이 모든 노드를 버려 저장본의 injections 가 통째로 비워진다
* (실측: 이커머스 → `_user_base` 헤더 통화 선택기 18,405B → 148B). 백엔드가 주입 시점에
* injection 순번을 메타로 싣는 것이 SSoT 다.
*/
class LayoutOverlayInjectionIndexMetaTest extends TestCase
{
use RefreshDatabase;
/**
* @var array<string>
*/
protected array $requiredExtensions = [
'plugins/sirsoft-gdpr',
];
#[Test]
public function overlay_injected_nodes_carry_injection_index_in_source_meta(): void
{
$template = Template::factory()->create([
'identifier' => 'sirsoft-basic',
'type' => 'user',
'status' => ExtensionStatus::Active->value,
]);
$extension = LayoutExtension::create([
'template_id' => $template->id,
'extension_type' => LayoutExtensionType::Overlay,
'target_name' => '_user_base',
// 출처를 템플릿 자신으로 두어 모듈 활성 여부 게이트(isExtensionSourceActive)에 걸리지 않게 한다.
'source_type' => LayoutSourceType::Template,
'source_identifier' => 'sirsoft-basic',
'content' => [
'target_layout' => '_user_base',
'injections' => [
// 0 — inject_props (components 없음). 순번은 이 injection 도 센다.
['target_id' => 'anchor_a', 'position' => 'inject_props', 'props' => ['className' => 'x']],
// 1 — 컴포넌트 주입
[
'target_id' => 'anchor_b',
'position' => 'append_child',
'components' => [
[
'type' => 'basic',
'name' => 'Span',
'id' => 'ext_b',
'children' => [
['type' => 'basic', 'name' => 'Span', 'id' => 'ext_b_child'],
],
],
],
],
],
'priority' => 320,
],
'priority' => 320,
'is_active' => true,
]);
$layout = [
'layout_name' => '_user_base',
'meta' => ['title' => 'Base'],
'components' => [
['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_a'],
['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_b'],
],
];
// 편집 모드(with_source_meta) 병합 — PublicLayoutController::serve 가 같은 인자로 호출한다.
$merged = $this->app->make(LayoutExtensionService::class)
->applyExtensions($layout, $template->id, true);
$injected = $this->findNodeById($merged['components'], 'ext_b');
$this->assertNotNull($injected, '주입 노드 ext_b 가 병합 결과에 있어야 한다');
$this->assertSame('extension', $injected['__source']['kind'] ?? null);
$this->assertSame($extension->id, $injected['__source']['extensionId'] ?? null);
$this->assertSame(
1,
$injected['__source']['injectionIndex'] ?? null,
'주입 노드 진입점의 __source 에 원래 injection 순번(1)이 실려야 편집기가 저장 시 되돌릴 수 있다'
);
// 자식도 같은 순번을 상속(진입점 판정은 자식으로 내려가지 않지만 메타는 일관).
$child = $this->findNodeById($merged['components'], 'ext_b_child');
$this->assertSame(1, $child['__source']['injectionIndex'] ?? null);
// 운영 렌더(with_source_meta=false)에는 메타가 전혀 없다 — 비파괴.
$plain = $this->app->make(LayoutExtensionService::class)
->applyExtensions($layout, $template->id, false);
$plainInjected = $this->findNodeById($plain['components'], 'ext_b');
$this->assertNotNull($plainInjected);
$this->assertArrayNotHasKey('__source', $plainInjected);
}
/**
* 트리에서 id 로 노드를 찾는다.
*
* @param array<int, mixed>|null $nodes 컴포넌트 배열
* @param string $id 찾을 id
* @return array<string, mixed>|null 노드
*/
private function findNodeById(?array $nodes, string $id): ?array
{
foreach ($nodes ?? [] as $node) {
if (! is_array($node)) {
continue;
}
if (($node['id'] ?? null) === $id) {
return $node;
}
$found = $this->findNodeById($node['children'] ?? null, $id);
if ($found !== null) {
return $found;
}
}
return null;
}
}