Files
Gnuboard7/modules/_bundled/sirsoft-board/tests/Feature/Seo/PostSeoOgImageRenderingTest.php
T
HeuJung 124433148c feat(seo): 사이트맵 재생성 알림·폴링 폴백 + 코어 7.0.6 확정
이슈 후속(S7) 마감 + 릴리스 버전 확정.

기능(S7):
- 사이트맵 전체 재생성 완료/실패 시 실행 관리자에게 알림(수동 실행 한정).
 스케줄러·증분·봇 재생성은 알림 없음(triggered_by 부재 시 skip).
- 최종 실패 전용 알림 + 재생성 전용 큐 + WS 진행상황 payload 봉투 정정.
- Reverb OFF 폴링 폴백 경로 수정(실브라우저 폴링에서만 드러난 결함).

버전(D28 — 정정, D20 번복):
- 릴리스를 7.0.5→7.0.6(patch)로 확정. 7.1.0(minor)은 다른 브랜치에서 별도 진행.
- config/app.php + .env + README + INSTALL + CHANGELOG + 모듈 3 requires 를 7.0.6 로.
- D19 SEO 캐시 이행 업그레이드 스텝을 Upgrade_7_0_6 로 리네임.
 이유: 스텝 버전 게이팅이 version<=toVersion 이라, 7.1.0 이름이면
 7.0.5→7.0.6 업그레이드에서 이행이 실행되지 않는다.

검증: composer test-smoke OK(2·29) · 재생성 알림/업그레이드 이행/진행상황 테스트 green ·
version-sync/version-bump(new-bump)/extension-require green · audit 0 error.
2026-07-19 18:57:51 +09:00

144 lines
6.2 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature\Seo;
use App\Extension\TemplateManager;
use App\Seo\SeoCacheManager;
use Illuminate\Support\Facades\Http;
use Modules\Sirsoft\Board\Models\Attachment;
use Modules\Sirsoft\Board\Models\Board;
use Modules\Sirsoft\Board\Models\Post;
use Modules\Sirsoft\Board\Tests\ModuleTestCase;
/**
* 게시판 게시물 SEO 통합 회귀 테스트.
*
* 회귀: 첨부 이미지가 있는 게시물의 SEO 응답에 og:image 가 포함되어야 함.
* (페이스북·슬랙·쓰레드 미리보기 카드 노출 조건)
*
* 셋업: ModuleTestCase (DatabaseTransactions — 빠름) + 의존성 모듈/플러그인/템플릿 활성화 +
* SeoRenderer 의 DataSourceResolver 가 사용하는 Http facade 만 같은 process 의
* internal kernel 로 redirect (외부 PHP-FPM 의 별도 connection 으로 인한 트랜잭션 미가시 회피).
* SeoRenderer / BoardModule.seoOgDefaults / DataSourceResolver 의 비즈니스 로직 자체는 실제 코드.
*/
class PostSeoOgImageRenderingTest extends ModuleTestCase
{
protected function setUp(): void
{
parent::setUp();
// 의존성 모듈 (sirsoft-ecommerce / sirsoft-page) 을 active 상태로 등록 — 템플릿 의존 만족용
// (전체 install 은 비용 크므로 Module DB 행만 직접 셋업)
// 버전은 번들 매니페스트 실버전으로 등록한다. sirsoft-basic 템플릿은 의존성 제약
// (예: sirsoft-ecommerce >=1.0.2) 을 두므로, 하드코딩 버전은 확장 버전 bump 시 stale 되어
// installTemplate() 이 "버전이 맞지 않습니다" 로 실패 → 활성 user 템플릿 부재 →
// SEO 라우트 미해석 → og:image 미출력으로 이어진다. updateOrCreate 로 기존 행의
// stale 버전까지 매니페스트 값으로 교정한다(트랜잭션 롤백으로 격리).
foreach ([
['sirsoft-ecommerce', 'sirsoft', '이커머스', 'Ecommerce'],
['sirsoft-page', 'sirsoft', '페이지', 'Page'],
] as [$id, $vendor, $ko, $en]) {
$manifestVersion = json_decode(
(string) @file_get_contents(base_path("modules/_bundled/{$id}/module.json")),
true
)['version'] ?? '1.0.0';
\App\Models\Module::updateOrCreate(
['identifier' => $id],
[
'vendor' => $vendor,
'name' => ['ko' => $ko, 'en' => $en],
'status' => \App\Enums\ExtensionStatus::Active->value,
'version' => $manifestVersion,
'config' => [],
]
);
}
\App\Models\Plugin::firstOrCreate(
['identifier' => 'sirsoft-daum_postcode'],
[
'vendor' => 'sirsoft',
'name' => ['ko' => '다음 우편번호', 'en' => 'Daum Postcode'],
'status' => \App\Enums\ExtensionStatus::Active->value,
'version' => '1.0.0',
'config' => [],
]
);
// 템플릿 활성화
$tm = app(TemplateManager::class);
try { $tm->installTemplate('sirsoft-basic'); } catch (\Throwable) {}
try { $tm->activateTemplate('sirsoft-basic'); } catch (\Throwable) {}
// SEO 캐시 격리
try { app(SeoCacheManager::class)->clearAll(); } catch (\Throwable) {}
// Http facade redirect — DataSourceResolver 가 호출하는 외부 HTTP 를 같은 process 의 internal kernel 로
Http::fake(function (\Illuminate\Http\Client\Request $request) {
$url = $request->url();
$path = parse_url($url, PHP_URL_PATH);
$query = parse_url($url, PHP_URL_QUERY);
$internal = $this->call($request->method(), $path.($query ? '?'.$query : ''));
return Http::response($internal->getContent(), $internal->getStatusCode(), $internal->headers->all());
});
}
/**
* 회귀: 첨부 이미지가 있는 게시물 SEO 응답에 og:image 절대 URL 포함.
*/
public function test_post_with_image_attachment_emits_og_image(): void
{
config([
'g7_settings.core.seo.bot_detection_enabled' => true,
'g7_settings.core.seo.bot_detection_library_enabled' => true,
'g7_settings.modules.sirsoft-board.seo.seo_post_detail' => true,
]);
$board = Board::factory()->create([
'slug' => 'gallery-'.uniqid(),
'is_active' => true,
]);
// guest role 에 board posts read 권한 부여 — 봇/비로그인이 게시글 SEO 받을 수 있도록
$readPerm = \App\Models\Permission::firstOrCreate(
['identifier' => "sirsoft-board.{$board->slug}.posts.read"],
['name' => ['ko' => '게시글 조회', 'en' => 'Read Posts'], 'type' => 'user']
);
\App\Models\Role::where('identifier', 'guest')->first()->permissions()->syncWithoutDetaching([$readPerm->id]);
$post = Post::create([
'board_id' => $board->id,
'title' => '첨부 이미지 게시글',
'content' => '본문 내용',
'ip_address' => '127.0.0.1',
'is_secret' => false,
'user_id' => null,
]);
Attachment::create([
'board_id' => $board->id,
'post_id' => $post->id,
'hash' => substr(md5(uniqid('a', true)), 0, 12),
'original_filename' => 'sample.jpg',
'stored_filename' => 'sample.jpg',
'disk' => 'local',
'path' => 'attachments/sample.jpg',
'mime_type' => 'image/jpeg',
'size' => 1024,
]);
$response = $this->withHeaders(['User-Agent' => 'facebookexternalhit/1.1'])
->get("/board/{$board->slug}/{$post->id}");
$body = (string) $response->getContent();
$this->assertStringContainsString('og:image', $body, 'og:image 메타태그 출력 필수');
$this->assertMatchesRegularExpression(
'#<meta property="og:image" content="https?://[^"]+/api/modules/sirsoft-board/[^"]*/preview"#',
$body,
'og:image 는 절대 URL + attachment preview 경로'
);
}
}