이슈 후속(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.
144 lines
6.2 KiB
PHP
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 경로'
|
|
);
|
|
}
|
|
}
|