fix(message_bizppurio): 결과코드 5002 언어팩 누락 + 알림톡 템플릿 저장 승인검증 부재 수정
PR 준비 최종 검토 중 문제코드 처리 대조표를 재검증하며 발견한 결함 2건. - ResultCodeResolver 가 5002(요청 과다)를 재시도 대상으로 분류하지만 ko/en/ja 언어팩 어디에도 사유가 없어 코드값만 노출되던 결함 수정 - 알림톡 템플릿 연결 저장이 승인 상태(RDY/ACT) 재검증 없이 그대로 upsert되어, 화면 드롭다운(승인 템플릿만 노출)을 우회한 API 직접 호출 시 미승인 템플릿도 저장 가능했던 결함 수정 — bind 에 카카오 승인 목록 대조 추가, 조회 자체 실패 시에도 안전측으로 저장 거부
This commit is contained in:
@@ -67,11 +67,13 @@ class GenericNotification extends BaseNotification
|
||||
// 대상 채널이 지정된 경우 (template별 발송) — 게스트 게이트 + 확장 토글 + readiness 확인
|
||||
if ($this->channel !== null) {
|
||||
try {
|
||||
$channelService = app(NotificationChannelService::class);
|
||||
|
||||
// 사용 가능한 채널 목록에 없는 채널은 발송 후보에서 제외한다 (로그도 남기지 않음).
|
||||
// 채널을 제공하던 확장이 비활성/삭제되면 그 채널이 getAvailableChannels() 에서 빠지지만,
|
||||
// 저장된 채널 설정·템플릿은 남아 있어 죽은 채널로 발송·"건너뜀" 로깅이 발생한다. 살아 있지
|
||||
// 않은 채널은 "존재하지 않는 것"으로 취급해 조용히 제외한다(skipped 기록 대상 아님).
|
||||
if (! $this->isChannelAvailable($this->channel)) {
|
||||
if (! $channelService->isChannelAvailable($this->channel)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -85,7 +87,6 @@ class GenericNotification extends BaseNotification
|
||||
}
|
||||
|
||||
// 0단계: 확장 단위 채널 전역 활성 여부 확인
|
||||
$channelService = app(NotificationChannelService::class);
|
||||
if (! $channelService->isChannelEnabledForExtension(
|
||||
$this->extensionType,
|
||||
$this->extensionIdentifier,
|
||||
@@ -136,7 +137,7 @@ class GenericNotification extends BaseNotification
|
||||
foreach ($channels as $channel) {
|
||||
// 사용 가능한 채널 목록에 없는 채널(비활성/삭제된 확장이 제공하던 채널)은 로그 없이
|
||||
// 조용히 제외한다 — 죽은 채널을 "건너뜀"으로 기록하지 않는다(채널 지정 경로와 동일 정책).
|
||||
if (! $this->isChannelAvailable($channel)) {
|
||||
if (! $channelService->isChannelAvailable($channel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -208,31 +209,6 @@ class GenericNotification extends BaseNotification
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 채널이 현재 사용 가능한(살아 있는) 채널 목록에 있는지 확인합니다.
|
||||
*
|
||||
* getAvailableChannels() 는 코어 기본 채널(mail/database)과 활성 확장이 filter 훅으로 추가한
|
||||
* 채널만 담는다. 확장이 비활성/삭제되면 그 채널이 이 목록에서 자동으로 빠지므로, 특정 확장
|
||||
* 이름을 알지 못해도 "죽은 채널"을 판별할 수 있다. 조회 실패 시(부팅 초기 등) 안전측으로 true
|
||||
* 를 반환해 기존 게이트(확장 토글·readiness)가 판정하도록 위임한다.
|
||||
*
|
||||
* @param string $channel 채널 식별자
|
||||
* @return bool 사용 가능하면 true
|
||||
*/
|
||||
private function isChannelAvailable(string $channel): bool
|
||||
{
|
||||
try {
|
||||
$availableIds = array_column(
|
||||
app(NotificationChannelService::class)->getAvailableChannels(),
|
||||
'id'
|
||||
);
|
||||
|
||||
return in_array($channel, $availableIds, true);
|
||||
} catch (\Throwable $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 비회원 수신자에게 해당 채널 발송이 차단되는지 확인합니다.
|
||||
*
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
### Added
|
||||
|
||||
- 비즈뿌리오 메시징 플러그인(sirsoft-message_bizppurio)의 일본어 번들 언어팩을 제공합니다. 환경설정·알림톡 템플릿 관리·발송 이력 화면과 발송 결과 코드 안내가 일본어 로케일에서 자연스럽게 표시됩니다.
|
||||
- 비즈뿌리오 메시징 플러그인(sirsoft-message_bizppurio)의 일본어 번들 언어팩을 제공합니다. 환경설정·알림톡 템플릿 관리·발송 이력 화면과 발송 결과 코드 안내가 일본어 로케일에서 자연스럽게 표시됩니다. 요청 과다로 인한 일시적 발송 실패 사유, 미승인 템플릿 연결 시도 시 안내 문구도 포함됩니다.
|
||||
|
||||
@@ -57,6 +57,7 @@ return [
|
||||
'kakao_credentials_missing' => 'カカオ管理API 使用のためにアイディとAPI キーを先に設定してください。',
|
||||
'kakao_request_failed' => 'カカオ管理API リクエストに失敗しました。',
|
||||
'sender_key_missing' => 'アラートトーク発信プロフィールキーを先に設定してください。',
|
||||
'template_not_sendable' => '送信可能(承認)ステータスではないテンプレートです。(コード: :code)',
|
||||
],
|
||||
'send_skipped' => [
|
||||
'alimtalk_binding_missing' => 'アラートトークテンプレートが接続されていないため送信をスキップしました。(通知タイプ: :type)',
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'4430' => 'スパム',
|
||||
'4431' => '送信制限受信拒否(スパム)',
|
||||
'4443' => 'スパムブロック',
|
||||
'5002' => 'リクエストが多すぎます',
|
||||
'5003' => '一時的な送信エラー',
|
||||
'5004' => '一時的な送信エラー',
|
||||
'5005' => '一時的な送信エラー',
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
### Added
|
||||
|
||||
- 비즈뿌리오 연동 환경설정 화면과 문자(SMS/LMS)·카카오 알림톡 발송 채널을 추가했습니다. 회원가입·주문 등 코어 알림에 자동 연결되며, 검수/운영 환경을 구분해 운영합니다.
|
||||
- 설정 화면에서 카카오 알림톡 템플릿을 조회해 알림에 연결하고 실제로 발송할 수 있습니다. 템플릿 등록·검수는 비즈뿌리오 콘솔에서 진행합니다. 게시판·이커머스 알림 설정 화면에서도 코어와 동일하게 연결할 수 있습니다.
|
||||
- 비즈뿌리오 webhook 으로 발송 결과를 수신해 "알림 발송 이력" 화면에 성공/실패와 사유를 기록하며, 지갑 잔액 부족 등으로 발송이 막히면 관리자에게 알립니다.
|
||||
- 설정 화면에서 카카오 알림톡 템플릿을 조회해 알림에 연결하고 실제로 발송할 수 있습니다. 템플릿 등록·검수는 비즈뿌리오 콘솔에서 진행합니다. 게시판·이커머스 알림 설정 화면에서도 코어와 동일하게 연결할 수 있습니다. 발송 가능(승인) 상태가 아닌 템플릿을 연결하려 하면 저장을 거부하고 사유를 안내합니다.
|
||||
- 비즈뿌리오 webhook 으로 발송 결과를 수신해 "알림 발송 이력" 화면에 성공/실패와 사유를 기록하며, 지갑 잔액 부족 등으로 발송이 막히면 관리자에게 알립니다. 요청 과다로 인한 일시적 발송 실패도 사유가 표시됩니다.
|
||||
- 플러그인을 삭제하면 추가했던 발송 채널이 함께 정리되고, 재설치 시 자동 복원됩니다.
|
||||
|
||||
@@ -144,12 +144,13 @@ HTTP/1.1 200
|
||||
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
|
||||
| 403 | Forbidden | 요구 권한(`sirsoft-message_bizppurio.messaging.manage`)이 없는 경우 |
|
||||
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) |
|
||||
| 422 | Unprocessable Entity | `template_code` 가 카카오 승인 상태(RDY/ACT)가 아니거나, 카카오 승인 목록 조회 자체가 실패(자격증명 미설정·장애)한 경우 (`error.kakao_message`/`error.result_code`) |
|
||||
|
||||
<!-- @generated:end -->
|
||||
|
||||
**설명**
|
||||
|
||||
알림에 알림톡 템플릿을 연결(생성/갱신)한다. 알림톡 탭 편집 모달의 [저장] 이 호출한다. `(notification_type, alimtalk)` 당 1개 연결이므로, 이미 연결이 있으면 갱신(upsert)된다. 저장은 코어 알림 설정 저장 버튼과 무관하게 이 API 로 직접 수행되어 코어 우회가 없다(§6-2). 승인되지 않은 템플릿 코드도 저장 자체는 가능하나(운영자 선택 자유), 편집 모달 드롭다운은 승인(RDY/ACT) 템플릿만 노출해 실수를 방지한다.
|
||||
알림에 알림톡 템플릿을 연결(생성/갱신)한다. 알림톡 탭 편집 모달의 [저장] 이 호출한다. `(notification_type, alimtalk)` 당 1개 연결이므로, 이미 연결이 있으면 갱신(upsert)된다. 저장은 코어 알림 설정 저장 버튼과 무관하게 이 API 로 직접 수행되어 코어 우회가 없다(§6-2). 저장 전 카카오 승인 상태(RDY/ACT)를 서버측에서 재검증하며, 미승인 코드거나 카카오 조회 자체가 실패하면 422 로 거부한다(편집 모달 드롭다운의 승인 템플릿 필터는 화면 단계일 뿐이라, 이를 우회한 직접 API 호출로 미승인 템플릿이 저장되는 것을 막기 위함). 연결 해제(`template_code` 빈 값)는 이 검증을 거치지 않는다.
|
||||
|
||||
|
||||
### GET /api/plugins/sirsoft-message_bizppurio/admin/notification-bindings/approved-templates
|
||||
|
||||
@@ -80,6 +80,7 @@ return [
|
||||
'kakao_credentials_missing' => 'Please set the ID and API key to use the Kakao management API.',
|
||||
'kakao_request_failed' => 'The Kakao management API request failed.',
|
||||
'sender_key_missing' => 'Please set the alimtalk sender profile key first.',
|
||||
'template_not_sendable' => 'This template is not in a sendable (approved) state. (code: :code)',
|
||||
],
|
||||
|
||||
// Send skipped (channel driver send() precondition not met — recorded as "Failed" in core notification log)
|
||||
|
||||
@@ -29,6 +29,7 @@ return [
|
||||
'3010' => 'IP not in allowlist',
|
||||
'3011' => 'Unknown error (Bizppurio)',
|
||||
'3013' => 'Message not completed',
|
||||
'5002' => 'Too many requests',
|
||||
'5003' => 'Temporary delivery error',
|
||||
'5004' => 'Temporary delivery error',
|
||||
'5005' => 'Temporary delivery error',
|
||||
|
||||
@@ -80,6 +80,7 @@ return [
|
||||
'kakao_credentials_missing' => '카카오 관리 API 사용을 위해 아이디와 API 키를 먼저 설정하세요.',
|
||||
'kakao_request_failed' => '카카오 관리 API 요청에 실패했습니다.',
|
||||
'sender_key_missing' => '알림톡 발신프로필 키를 먼저 설정하세요.',
|
||||
'template_not_sendable' => '발송 가능(승인) 상태가 아닌 템플릿입니다. (코드: :code)',
|
||||
],
|
||||
|
||||
// 발송 건너뜀 (채널 드라이버 send() 사전 조건 미충족 — 코어 발송 이력에 "실패"로 기록됨)
|
||||
|
||||
@@ -32,6 +32,7 @@ return [
|
||||
'3010' => '접속 허용 IP 불일치',
|
||||
'3011' => '알 수 없는 오류(비즈뿌리오)',
|
||||
'3013' => '완료 처리되지 않은 메시지',
|
||||
'5002' => '요청 과다',
|
||||
'5003' => '일시적 전송 오류',
|
||||
'5004' => '일시적 전송 오류',
|
||||
'5005' => '일시적 전송 오류',
|
||||
|
||||
+11
-9
@@ -83,15 +83,17 @@ class NotificationBindingController extends AdminBaseController
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$this->service->applyFromTemplateSave(
|
||||
$validated['notification_type'],
|
||||
$validated['template_code'] ?? null,
|
||||
$validated['template_name'] ?? null,
|
||||
(bool) ($validated['fallback_sms_enabled'] ?? false),
|
||||
);
|
||||
return $this->guard(function () use ($validated) {
|
||||
$this->service->applyFromTemplateSave(
|
||||
$validated['notification_type'],
|
||||
$validated['template_code'] ?? null,
|
||||
$validated['template_name'] ?? null,
|
||||
(bool) ($validated['fallback_sms_enabled'] ?? false),
|
||||
);
|
||||
|
||||
return ResponseHelper::success('messages.binding.saved', [
|
||||
'bindings' => $this->service->all(),
|
||||
]);
|
||||
return ResponseHelper::success('messages.binding.saved', [
|
||||
'bindings' => $this->service->all(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+32
-1
@@ -130,20 +130,49 @@ class NotificationBindingService
|
||||
/**
|
||||
* 알림에 알림톡 템플릿을 연결(생성/갱신)합니다 (연동 모달 저장).
|
||||
*
|
||||
* 저장 전 카카오 승인 상태(RDY/ACT)를 재검증한다 — 드롭다운이 승인 템플릿만 보여주지만
|
||||
* 그 필터는 화면 단계일 뿐이라, API 를 직접 호출하면 미승인 템플릿도 저장될 수 있었다(회귀).
|
||||
* 카카오 조회 자체가 실패(장애·자격증명 미설정)하면 승인 여부를 판정할 수 없으므로 안전측으로
|
||||
* 저장을 거부한다 — 조회 실패를 "승인됨"으로 잘못 해석해 미승인 템플릿이 새는 것을 막는다.
|
||||
*
|
||||
* @param string $notificationType 코어 notification_definitions.type
|
||||
* @param array<string, mixed> $data template_code / template_name / fallback_sms_enabled
|
||||
* @return BizppurioNotificationBinding 저장된 연동
|
||||
*
|
||||
* @throws BizppurioApiException 카카오 조회 실패, 또는 미승인·존재하지 않는 템플릿 코드
|
||||
*/
|
||||
public function bind(string $notificationType, array $data): BizppurioNotificationBinding
|
||||
{
|
||||
$templateCode = (string) $data['template_code'];
|
||||
|
||||
$this->assertSendable($templateCode);
|
||||
|
||||
return $this->bindings->upsert($notificationType, self::CHANNEL, [
|
||||
'template_code' => (string) $data['template_code'],
|
||||
'template_code' => $templateCode,
|
||||
'template_name' => (string) $data['template_name'],
|
||||
'fallback_sms_enabled' => (bool) ($data['fallback_sms_enabled'] ?? false),
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 코드가 발송 가능(승인) 상태인지 검증합니다. 아니면 예외를 던집니다.
|
||||
*
|
||||
* @param string $templateCode 검증할 카카오 템플릿 코드
|
||||
*
|
||||
* @throws BizppurioApiException 카카오 조회 실패, 또는 미승인·존재하지 않는 템플릿 코드
|
||||
*/
|
||||
private function assertSendable(string $templateCode): void
|
||||
{
|
||||
$sendableCodes = array_column($this->approvedTemplates(), 'template_code');
|
||||
|
||||
if (! in_array($templateCode, $sendableCodes, true)) {
|
||||
throw new BizppurioApiException(
|
||||
__('sirsoft-message_bizppurio::messages.error.template_not_sendable', ['code' => $templateCode]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림의 알림톡 연동을 해제(삭제)합니다.
|
||||
*
|
||||
@@ -184,6 +213,8 @@ class NotificationBindingService
|
||||
* @param string|null $templateCode 연결할 카카오 템플릿 코드 (빈 값=해제)
|
||||
* @param string|null $templateName 템플릿 이름 스냅샷 (고아 감지용)
|
||||
* @param bool $fallbackSmsEnabled 실패 시 SMS 대체발송 여부
|
||||
*
|
||||
* @throws BizppurioApiException 카카오 조회 실패, 또는 미승인·존재하지 않는 템플릿 코드 (해제 시에는 미발생)
|
||||
*/
|
||||
public function applyFromTemplateSave(
|
||||
string $notificationType,
|
||||
|
||||
+60
@@ -5,7 +5,10 @@ namespace Plugins\Sirsoft\MessageBizppurio\Tests\Feature\Notification;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Models\BizppurioNotificationBinding;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Services\AlimtalkTemplateService;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Tests\PluginTestCase;
|
||||
|
||||
/**
|
||||
@@ -20,6 +23,37 @@ class NotificationBindingEndpointTest extends PluginTestCase
|
||||
{
|
||||
private const BASE = '/api/plugins/sirsoft-message_bizppurio/admin/notification-bindings';
|
||||
|
||||
/**
|
||||
* AlimtalkTemplateService mock 을 컨테이너에 바인딩하고 반환한다.
|
||||
*
|
||||
* bind() 가 저장 전 카카오 승인 목록을 조회하므로(회귀 방지 재검증), 실제 kapi 호출 없이
|
||||
* 저장 경로를 검증하려면 이 mock 으로 승인 템플릿 목록을 지정해야 한다.
|
||||
*/
|
||||
private function mockTemplateService(): MockInterface
|
||||
{
|
||||
$mock = Mockery::mock(AlimtalkTemplateService::class);
|
||||
$this->app->instance(AlimtalkTemplateService::class, $mock);
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정 template_code 목록을 승인(ACT) 상태로 반환하는 mock 을 등록한다.
|
||||
*
|
||||
* @param array<int, string> $codes 승인 처리할 template_code 목록
|
||||
*/
|
||||
private function stubApprovedTemplates(array $codes): void
|
||||
{
|
||||
$templates = array_map(
|
||||
fn (string $code) => ['templateCode' => $code, 'templateName' => $code, 'serviceStatus' => 'ACT'],
|
||||
$codes,
|
||||
);
|
||||
|
||||
$this->mockTemplateService()
|
||||
->shouldReceive('list')
|
||||
->andReturn(['templates' => $templates, 'pagination' => []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 지정 권한 식별자들을 가진 admin 사용자를 만듭니다.
|
||||
*
|
||||
@@ -126,6 +160,7 @@ class NotificationBindingEndpointTest extends PluginTestCase
|
||||
|
||||
public function test_manage_권한으로_연동을_즉시_저장한다(): void
|
||||
{
|
||||
$this->stubApprovedTemplates(['TW_1236']);
|
||||
$admin = $this->adminWith([
|
||||
'sirsoft-message_bizppurio.messaging.view',
|
||||
'sirsoft-message_bizppurio.messaging.manage',
|
||||
@@ -147,6 +182,29 @@ class NotificationBindingEndpointTest extends PluginTestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_미승인_템플릿_코드로_저장하면_422이고_저장되지_않는다(): void
|
||||
{
|
||||
// 회귀: 화면 드롭다운은 승인 템플릿만 보여주지만, 그 필터를 우회해 API 를 직접
|
||||
// 호출하면 미승인 template_code 도 저장되던 결함. bind() 서버측 재검증으로 차단.
|
||||
$this->stubApprovedTemplates(['TW_OTHER']);
|
||||
$admin = $this->adminWith([
|
||||
'sirsoft-message_bizppurio.messaging.view',
|
||||
'sirsoft-message_bizppurio.messaging.manage',
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders($this->authHeaders($admin))->postJson(self::BASE, [
|
||||
'notification_type' => 'welcome',
|
||||
'template_code' => 'TW_UNAPPROVED',
|
||||
'template_name' => '미승인',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$this->assertDatabaseMissing('bizppurio_notification_bindings', [
|
||||
'notification_type' => 'welcome',
|
||||
'template_code' => 'TW_UNAPPROVED',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_빈_코드로_저장하면_연동을_해제한다(): void
|
||||
{
|
||||
BizppurioNotificationBinding::create([
|
||||
@@ -163,6 +221,7 @@ class NotificationBindingEndpointTest extends PluginTestCase
|
||||
'sirsoft-message_bizppurio.messaging.manage',
|
||||
]);
|
||||
|
||||
// 해제는 카카오 승인 조회를 거치지 않으므로 mock 불필요.
|
||||
$response = $this->withHeaders($this->authHeaders($admin))->postJson(self::BASE, [
|
||||
'notification_type' => 'welcome',
|
||||
'template_code' => '',
|
||||
@@ -182,6 +241,7 @@ class NotificationBindingEndpointTest extends PluginTestCase
|
||||
'sirsoft-message_bizppurio.messaging.manage',
|
||||
]);
|
||||
|
||||
// FormRequest 검증(notification_type required)이 서비스 호출보다 먼저 실패하므로 mock 불필요.
|
||||
$response = $this->withHeaders($this->authHeaders($admin))->postJson(self::BASE, [
|
||||
'template_code' => 'TW_1236',
|
||||
]);
|
||||
|
||||
+62
-5
@@ -162,9 +162,22 @@ class NotificationBindingServiceTest extends PluginTestCase
|
||||
$this->assertNotContains('TW_D', $codes, 'REJ(반려)는 제외되어야 한다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* bind() 승인 검증을 통과시키는 템플릿 목록으로 서비스를 만듭니다 (bind 계열 테스트 공용 fixture).
|
||||
*
|
||||
* @param array<int, string> $approvedCodes 승인(발송 가능) 처리할 template_code 목록
|
||||
*/
|
||||
private function makeServiceWithApprovedCodes(array $approvedCodes): NotificationBindingService
|
||||
{
|
||||
return $this->makeService(array_map(
|
||||
fn (string $code) => ['templateCode' => $code, 'templateName' => $code, 'serviceStatus' => 'ACT'],
|
||||
$approvedCodes,
|
||||
));
|
||||
}
|
||||
|
||||
public function test_bind는_연동을_생성하고_unbind는_삭제한다(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
$service = $this->makeServiceWithApprovedCodes(['TW_1236']);
|
||||
|
||||
$service->bind('welcome', [
|
||||
'template_code' => 'TW_1236',
|
||||
@@ -189,7 +202,7 @@ class NotificationBindingServiceTest extends PluginTestCase
|
||||
|
||||
public function test_bind는_같은_알림에_대해_갱신한다(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
$service = $this->makeServiceWithApprovedCodes(['TW_1', 'TW_2']);
|
||||
|
||||
$service->bind('welcome', ['template_code' => 'TW_1', 'template_name' => '첫번째']);
|
||||
$service->bind('welcome', ['template_code' => 'TW_2', 'template_name' => '두번째']);
|
||||
@@ -201,9 +214,53 @@ class NotificationBindingServiceTest extends PluginTestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_bind는_미승인_템플릿이면_예외를_던지고_저장하지_않는다(): void
|
||||
{
|
||||
// 회귀: bind() 가 승인 상태를 재검증하지 않아, 드롭다운(화면) 필터를 우회해 API 를
|
||||
// 직접 호출하면 미승인 템플릿도 저장되던 결함.
|
||||
$service = $this->makeServiceWithApprovedCodes(['TW_OTHER']);
|
||||
|
||||
$this->expectException(\Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException::class);
|
||||
|
||||
try {
|
||||
$service->bind('welcome', ['template_code' => 'TW_UNAPPROVED', 'template_name' => '미승인']);
|
||||
} finally {
|
||||
$this->assertDatabaseMissing('bizppurio_notification_bindings', [
|
||||
'notification_type' => 'welcome',
|
||||
'template_code' => 'TW_UNAPPROVED',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_bind는_카카오_조회_실패시_예외를_던지고_저장하지_않는다(): void
|
||||
{
|
||||
// 승인 여부를 판정할 수 없으면 안전측으로 저장을 거부한다(조회 실패="승인됨" 오인 방지).
|
||||
$throwingTemplates = Mockery::mock(AlimtalkTemplateService::class);
|
||||
$throwingTemplates->shouldReceive('list')
|
||||
->andThrow(new \Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException('자격증명 미설정'));
|
||||
|
||||
$service = new NotificationBindingService(
|
||||
new BizppurioNotificationBindingRepository,
|
||||
$throwingTemplates,
|
||||
$this->fakeKakaoContent(),
|
||||
);
|
||||
|
||||
$this->expectException(\Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException::class);
|
||||
|
||||
try {
|
||||
$service->bind('welcome', ['template_code' => 'TW_1', 'template_name' => '가입환영']);
|
||||
} finally {
|
||||
$this->assertDatabaseMissing('bizppurio_notification_bindings', [
|
||||
'notification_type' => 'welcome',
|
||||
'template_code' => 'TW_1',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_apply_from_template_save는_코드가_있으면_연동을_저장한다(): void
|
||||
{
|
||||
$this->makeService()->applyFromTemplateSave('welcome', 'TW_9', '가입환영', true);
|
||||
$this->makeServiceWithApprovedCodes(['TW_9'])
|
||||
->applyFromTemplateSave('welcome', 'TW_9', '가입환영', true);
|
||||
|
||||
$this->assertDatabaseHas('bizppurio_notification_bindings', [
|
||||
'notification_type' => 'welcome',
|
||||
@@ -215,10 +272,10 @@ class NotificationBindingServiceTest extends PluginTestCase
|
||||
|
||||
public function test_apply_from_template_save는_코드가_비면_연동을_해제한다(): void
|
||||
{
|
||||
$service = $this->makeService();
|
||||
$service = $this->makeServiceWithApprovedCodes(['TW_1']);
|
||||
$service->bind('welcome', ['template_code' => 'TW_1', 'template_name' => '기존']);
|
||||
|
||||
// 편집 모달에서 "연결 안 함"으로 저장 → 빈 코드 → 해제
|
||||
// 편집 모달에서 "연결 안 함"으로 저장 → 빈 코드 → 해제 (승인 검증 대상 아님)
|
||||
$service->applyFromTemplateSave('welcome', '', null, false);
|
||||
|
||||
$this->assertDatabaseMissing('bizppurio_notification_bindings', [
|
||||
|
||||
+13
@@ -64,6 +64,19 @@ class ResultCodeResolverTest extends PluginTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 재시도 대상으로 분류된 코드는 전부 사유가 lang 에 정의돼 있어야 한다(코드값만 노출 금지).
|
||||
*
|
||||
* 회귀: RETRYABLE_CODES 에 5002 가 분류돼 있었으나 lang/{ko,en}/result_codes.php 와 ja
|
||||
* 번들에 5002 키가 누락되어 label('5002') 가 사유 없이 코드만 반환하던 결함.
|
||||
*/
|
||||
public function test_all_retryable_codes_have_reason(): void
|
||||
{
|
||||
foreach (['5002', '5003', '5004', '5005', '9000', '3011', '3013', '7306', '7307', '7421', '7437'] as $code) {
|
||||
$this->assertNotNull($this->resolver->reason($code), "코드 {$code} 사유 누락");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 일시오류 코드는 Retry.
|
||||
*
|
||||
|
||||
@@ -181,6 +181,8 @@ class GenericNotificationViaTest extends TestCase
|
||||
});
|
||||
$mock->shouldReceive('getAvailableChannels')
|
||||
->andReturn(array_map(static fn (string $id) => ['id' => $id], $availableIds));
|
||||
$mock->shouldReceive('isChannelAvailable')
|
||||
->andReturnUsing(fn (string $channel) => in_array($channel, $availableIds, true));
|
||||
$this->app->instance(NotificationChannelService::class, $mock);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user