32afe9530 에서 게시판 반응 기능과 함께 임시 제거됐던 비즈뿌리오 메시징 플러그인 본체(137 파일)와 일본어 번들 언어팩(9 파일)을 삭제 직전 상태 (32afe9530~1)로 복구한다. 템플릿 신청·승인 개편의 기반 작업. 복구 범위 - plugins/_bundled/sirsoft-message_bizppurio 전체 + 번들 ja 언어팩 - 공유 파일의 비즈뿌리오 참조 복원: build-language-pack-ja.cjs 팩 정의, api-doc-unfilled-baseline.json(32건), docs/backend/api/README.md 표 행, audit coverage 노트·vite-sourcemap-env-gate 룰 주석, via 테스트 주석, ·AGENTS.md 확장 API 표(자동 재생성), 라우팅 패리티 스냅샷(+1) 삭제 이후 강화된 규정 2건 정합화 - TokenCheckController: 예외 원문을 메시지 키 자리에 전달하던 422 응답을 키(token_check.failed) + errors.bizppurio_message 페이로드로 분리 (GenericCatchStatusCodeContractTest 계약). 관리자 토스트는 errors 페이로드로 상세 사유를 계속 표시하도록 레이아웃 동기 수정, lang ko/en/ja 키 추가 - AlimtalkTemplateController::index: base Request 주입 금지 룰에 따라 AlimtalkTemplateListRequest FormRequest 신설 (형태 검증만 — kapi 위임 유지) 미복원(의도) - 게시판·이커머스 CHANGELOG 의 알림톡 연결 문구 2줄은 연결 방식이 로 재설계되므로 되살리지 않고, 완료 시점에 새 동작 기준으로 차기 버전에 기재 검증: TokenCheck 7 + AlimtalkController 10 + GenericCatch 계약 3 (PHPUnit), 플러그인 레이아웃 Vitest 137건, BindingShape 라우팅 패리티 8건 green. audit 는 복구 전부터 baseline 처리된 API 문서 미채움 32건만 잔존.
229 lines
8.6 KiB
PHP
229 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace Plugins\Sirsoft\MessageBizppurio\Tests\Feature\Notification;
|
|
|
|
use App\Models\NotificationLog;
|
|
use App\Models\Permission;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use Plugins\Sirsoft\MessageBizppurio\Models\BizppurioDispatch;
|
|
use Plugins\Sirsoft\MessageBizppurio\Tests\PluginTestCase;
|
|
|
|
/**
|
|
* 코어 알림 발송 이력 결과 조회 엔드포인트 테스트 (A-2 표시 주입).
|
|
*
|
|
* 코어 "알림 발송 이력" 화면에 얹은 결과 컬럼이 현재 페이지의 코어 알림 로그 id 배열을 넘겨
|
|
* 비즈뿌리오 결과(상태·사유·잔액부족·대체발송)를 한 번에 조회한다. 조회는 messaging.view 권한을
|
|
* 요구하고, 매칭되지 않는 로그 id 는 결과 맵에서 빠진다(빈 셀).
|
|
*/
|
|
class DispatchResultEndpointTest extends PluginTestCase
|
|
{
|
|
private const URL = '/api/plugins/sirsoft-message_bizppurio/admin/dispatch-results/lookup';
|
|
|
|
/**
|
|
* 지정 권한을 가진 admin 사용자를 만듭니다.
|
|
*
|
|
* @param array<int, string> $permissionIds 부여할 권한 식별자
|
|
*/
|
|
private function adminWith(array $permissionIds): User
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$adminRole = Role::firstOrCreate(
|
|
['identifier' => 'admin'],
|
|
['name' => json_encode(['ko' => '관리자', 'en' => 'Admin']), 'type' => 'admin']
|
|
);
|
|
|
|
$permIds = [];
|
|
foreach ($permissionIds as $identifier) {
|
|
$permission = Permission::firstOrCreate(
|
|
['identifier' => $identifier],
|
|
['name' => json_encode(['ko' => $identifier, 'en' => $identifier]), 'type' => 'admin']
|
|
);
|
|
$permIds[] = $permission->id;
|
|
}
|
|
|
|
$testRole = Role::create([
|
|
'identifier' => 'bizppurio_result_test_'.uniqid(),
|
|
'name' => json_encode(['ko' => '테스트', 'en' => 'Test']),
|
|
'type' => 'admin',
|
|
]);
|
|
$testRole->permissions()->sync($permIds);
|
|
|
|
$user->roles()->attach($adminRole->id, ['assigned_at' => now(), 'assigned_by' => null]);
|
|
$user->roles()->attach($testRole->id, ['assigned_at' => now(), 'assigned_by' => null]);
|
|
|
|
return $user->fresh();
|
|
}
|
|
|
|
/**
|
|
* 사용자 토큰 헤더를 만듭니다.
|
|
*/
|
|
private function authHeaders(User $user): array
|
|
{
|
|
return [
|
|
'Authorization' => 'Bearer '.$user->createToken('test')->plainTextToken,
|
|
'Accept' => 'application/json',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 코어 알림 로그 1건 + 연결된 dispatch 1건을 만들고 로그 id 를 반환합니다.
|
|
*
|
|
* @param array<string, mixed> $dispatchAttrs dispatch 오버라이드
|
|
*/
|
|
private function seedLinkedLog(array $dispatchAttrs = []): int
|
|
{
|
|
$log = NotificationLog::create([
|
|
'channel' => 'sms',
|
|
'notification_type' => 'welcome',
|
|
'extension_type' => 'core',
|
|
'extension_identifier' => 'core',
|
|
'recipient_identifier' => '01011112222',
|
|
'recipient_name' => '홍길동',
|
|
'status' => 'sent',
|
|
'source' => 'notification',
|
|
'sent_at' => now(),
|
|
]);
|
|
|
|
BizppurioDispatch::create(array_merge([
|
|
'refkey' => 'rk_'.uniqid(),
|
|
'channel' => 'sms',
|
|
'to_number' => '01011112222',
|
|
'content' => '본문',
|
|
'notification_type' => 'welcome',
|
|
'notification_log_id' => $log->id,
|
|
'status' => 'success',
|
|
'result_code' => '4100',
|
|
'source' => 'auto',
|
|
'sent_at' => now(),
|
|
], $dispatchAttrs));
|
|
|
|
return (int) $log->id;
|
|
}
|
|
|
|
private const RECENT_URL = '/api/plugins/sirsoft-message_bizppurio/admin/dispatch-results/recent';
|
|
|
|
public function test_인증_없이_조회는_401이다(): void
|
|
{
|
|
$this->postJson(self::URL, ['notification_log_ids' => [1]])->assertStatus(401);
|
|
}
|
|
|
|
public function test_recent_는_view_권한_없으면_403이다(): void
|
|
{
|
|
$user = $this->adminWith([]);
|
|
|
|
$this->getJson(self::RECENT_URL, $this->authHeaders($user))->assertStatus(403);
|
|
}
|
|
|
|
public function test_recent_는_파라미터_없이_최근_결과맵을_반환한다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
$logId = $this->seedLinkedLog(['result_code' => '4100']);
|
|
|
|
$result = $this->getJson(self::RECENT_URL, $this->authHeaders($user))
|
|
->assertOk()
|
|
->json("data.results.{$logId}");
|
|
|
|
$this->assertSame('success', $result['status']);
|
|
$this->assertSame('4100', $result['result_code']);
|
|
}
|
|
|
|
public function test_view_권한_없으면_403이다(): void
|
|
{
|
|
$user = $this->adminWith([]); // 권한 없음
|
|
$logId = $this->seedLinkedLog();
|
|
|
|
$this->postJson(self::URL, ['notification_log_ids' => [$logId]], $this->authHeaders($user))
|
|
->assertStatus(403);
|
|
}
|
|
|
|
public function test_로그_id_키가_아예_없으면_422다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
|
|
$this->postJson(self::URL, [], $this->authHeaders($user))
|
|
->assertStatus(422)
|
|
->assertJsonValidationErrors(['notification_log_ids']);
|
|
}
|
|
|
|
public function test_빈_배열은_200과_빈_결과맵을_반환한다(): void
|
|
{
|
|
// 코어 알림 발송 이력이 0건(또는 아직 미로드)일 때 화면은 빈 배열을 보낸다.
|
|
// 이 경우 422 가 아니라 200 + 빈 결과 맵이어야 화면이 깨지지 않는다(회귀: 브라우저 실측).
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
|
|
$response = $this->postJson(self::URL, ['notification_log_ids' => []], $this->authHeaders($user))
|
|
->assertOk();
|
|
|
|
$this->assertSame([], $response->json('data.results'));
|
|
}
|
|
|
|
public function test_성공_결과를_로그id_키_맵으로_반환한다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
$logId = $this->seedLinkedLog();
|
|
|
|
$response = $this->postJson(self::URL, ['notification_log_ids' => [$logId]], $this->authHeaders($user))
|
|
->assertOk();
|
|
|
|
$result = $response->json("data.results.{$logId}");
|
|
$this->assertSame('success', $result['status']);
|
|
$this->assertSame('4100', $result['result_code']);
|
|
$this->assertFalse($result['is_low_balance']);
|
|
}
|
|
|
|
public function test_실패_음영지역은_사유_코드_라벨을_반환한다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
$logId = $this->seedLinkedLog(['status' => 'failed', 'result_code' => '4400']);
|
|
|
|
$result = $this->postJson(self::URL, ['notification_log_ids' => [$logId]], $this->authHeaders($user))
|
|
->assertOk()
|
|
->json("data.results.{$logId}");
|
|
|
|
// result_label 은 `사유 (코드)` 형식 — result_codes lang 에 4400 정의가 있으면 사유가 붙는다.
|
|
$this->assertSame('failed', $result['status']);
|
|
$this->assertStringContainsString('4400', (string) $result['result_label']);
|
|
}
|
|
|
|
public function test_잔액부족_코드는_is_low_balance_true다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
$logId = $this->seedLinkedLog(['channel' => 'alimtalk', 'status' => 'failed', 'result_code' => '7436']);
|
|
|
|
$result = $this->postJson(self::URL, ['notification_log_ids' => [$logId]], $this->authHeaders($user))
|
|
->assertOk()
|
|
->json("data.results.{$logId}");
|
|
|
|
$this->assertTrue($result['is_low_balance']);
|
|
}
|
|
|
|
public function test_매칭되지_않는_로그id는_결과맵에서_빠진다(): void
|
|
{
|
|
$user = $this->adminWith(['sirsoft-message_bizppurio.messaging.view']);
|
|
$linkedId = $this->seedLinkedLog();
|
|
|
|
// 연결되지 않은 코어 로그(메일 등) — dispatch 없음
|
|
$mailLog = NotificationLog::create([
|
|
'channel' => 'mail',
|
|
'notification_type' => 'welcome',
|
|
'extension_type' => 'core',
|
|
'extension_identifier' => 'core',
|
|
'recipient_identifier' => 'a@b.com',
|
|
'status' => 'sent',
|
|
'source' => 'notification',
|
|
'sent_at' => now(),
|
|
]);
|
|
|
|
$results = $this->postJson(
|
|
self::URL,
|
|
['notification_log_ids' => [$linkedId, $mailLog->id]],
|
|
$this->authHeaders($user)
|
|
)->assertOk()->json('data.results');
|
|
|
|
$this->assertArrayHasKey((string) $linkedId, $results);
|
|
$this->assertArrayNotHasKey((string) $mailLog->id, $results);
|
|
}
|
|
}
|