fix(notification): 비활성 확장이 제공하던 알림 채널을 발송 대상에서 제외
비활성 상태의 확장이 등록했던 발송 수단(문자·알림톡 등)을 코어 via가 저장된 채널 설정만 보고 계속 발송·"건너뜀" 로깅하던 문제를 수정한다. 사용 가능한 채널 목록에 없는 채널은 로그 없이 조용히 제외하며, 알림 설정·템플릿은 그대로 유지되어 확장을 다시 켜면 복구된다. 아울러 발송이 타임아웃·연결실패로 최종 실패할 때 pending 으로 방치되던 이력을 failed 로 마감한다(webhook 이 먼저 확정한 건은 덮어쓰지 않음).
This commit is contained in:
1 parent
cd11fc60ce
commit
8aa395c3a1
6 files changed
+186
-5
No files matched your search
@@ -130,18 +130,39 @@ class SendMessageJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 재시도 소진 후 최종 실패 시 로그를 남깁니다.
|
||||
* 모든 재시도 소진 후 최종 실패 시 로그를 남기고 발송 이력을 실패로 마감합니다.
|
||||
*
|
||||
* 타임아웃·연결실패(ConnectionException)는 결과코드 없는 예외로 handle() 의 상태 갱신
|
||||
* 분기를 우회하므로, 재시도 소진 후 이력이 pending 인 채 방치된다. 여기서 refkey 로 이력을
|
||||
* 조회해 failed 로 마감한다(전송 실패라 최종 실패는 webhook 으로도 회수 불가). 이미 확정
|
||||
* (success/failed)된 이력은 webhook 이 먼저 결과를 확정한 경우이므로 덮어쓰지 않는다(멱등).
|
||||
*
|
||||
* @param Throwable $exception 마지막으로 발생한 예외
|
||||
*/
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
$resultCode = $exception instanceof BizppurioApiException
|
||||
? $exception->getResultCode()
|
||||
: null;
|
||||
|
||||
Log::error('비즈뿌리오 발송 Job 최종 실패', [
|
||||
'refkey' => $this->refkey,
|
||||
'error' => $exception->getMessage(),
|
||||
'result_code' => $exception instanceof BizppurioApiException
|
||||
? $exception->getResultCode()
|
||||
: null,
|
||||
'result_code' => $resultCode,
|
||||
]);
|
||||
|
||||
$dispatches = app(BizppurioDispatchRepositoryInterface::class);
|
||||
$dispatch = $dispatches->findByRefkey($this->refkey);
|
||||
|
||||
// 이력 없음(비정상) 또는 이미 확정(webhook 선반영) → 마감 불필요(멱등)
|
||||
if ($dispatch === null || $dispatch->status->isFinal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dispatches->update($dispatch, [
|
||||
'status' => DispatchStatus::Failed->value,
|
||||
'result_code' => $resultCode,
|
||||
'result_message' => $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Plugins\Sirsoft\MessageBizppurio\Tests\Unit\Jobs;
|
||||
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Mockery;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Enums\DispatchStatus;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException;
|
||||
@@ -113,6 +114,55 @@ class SendMessageJobTest extends PluginTestCase
|
||||
$this->assertTrue($job->afterCommit);
|
||||
}
|
||||
|
||||
public function test_최종실패_콜백은_pending이력을_failed로_마감하고_사유를_기록한다(): void
|
||||
{
|
||||
// 타임아웃·연결실패는 결과코드 없는 ConnectionException 으로 재시도 소진 후 failed() 로 온다.
|
||||
$dispatch = $this->seedPending('ref1');
|
||||
|
||||
$job = new SendMessageJob($this->payload, 'ref1');
|
||||
$job->failed(new ConnectionException('cURL error 28: Connection timed out'));
|
||||
|
||||
$dispatch->refresh();
|
||||
$this->assertSame(DispatchStatus::Failed, $dispatch->status);
|
||||
$this->assertNull($dispatch->result_code); // 전송 실패라 결과코드 없음
|
||||
$this->assertStringContainsString('timed out', (string) $dispatch->result_message);
|
||||
}
|
||||
|
||||
public function test_최종실패_콜백은_bizppurio_api_exception의_결과코드를_보존한다(): void
|
||||
{
|
||||
$dispatch = $this->seedPending('ref1');
|
||||
|
||||
$job = new SendMessageJob($this->payload, 'ref1');
|
||||
$job->failed(new BizppurioApiException('일시 오류', resultCode: '5003'));
|
||||
|
||||
$dispatch->refresh();
|
||||
$this->assertSame(DispatchStatus::Failed, $dispatch->status);
|
||||
$this->assertSame('5003', $dispatch->result_code);
|
||||
}
|
||||
|
||||
public function test_이미_확정된_이력은_최종실패_콜백이_덮어쓰지_않는다(): void
|
||||
{
|
||||
// webhook 이 먼저 success 로 확정한 뒤 failed() 가 늦게 불려도 멱등해야 한다.
|
||||
$dispatch = $this->seedPending('ref1');
|
||||
$dispatch->update(['status' => DispatchStatus::Success->value, 'result_code' => '4000']);
|
||||
|
||||
$job = new SendMessageJob($this->payload, 'ref1');
|
||||
$job->failed(new ConnectionException('timed out'));
|
||||
|
||||
$dispatch->refresh();
|
||||
$this->assertSame(DispatchStatus::Success, $dispatch->status);
|
||||
$this->assertSame('4000', $dispatch->result_code);
|
||||
}
|
||||
|
||||
public function test_이력이_없으면_최종실패_콜백은_아무것도_하지_않는다(): void
|
||||
{
|
||||
// 이력 seed 없이 호출 — 예외 없이 조용히 종료해야 한다.
|
||||
$job = new SendMessageJob($this->payload, 'missing-ref');
|
||||
$job->failed(new ConnectionException('timed out'));
|
||||
|
||||
$this->assertDatabaseMissing('bizppurio_dispatches', ['refkey' => 'missing-ref']);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
|
||||
+13
@@ -223,4 +223,17 @@ class SmsChannelDriverTest extends PluginTestCase
|
||||
|
||||
Bus::assertNotDispatched(SendMessageJob::class);
|
||||
}
|
||||
|
||||
public function test_정상_조건에서_발송하고_이력을_생성한다(): void
|
||||
{
|
||||
// 비활성 확장 채널의 발송 차단은 코어 via() 책임이며 GenericNotificationViaTest 가 검증한다.
|
||||
// 이 드라이버 테스트는 정상 조건(활성 전제)에서의 발송·이력 생성만 검증한다.
|
||||
Bus::fake();
|
||||
$member = User::factory()->create(['mobile' => '010-1234-5678']);
|
||||
|
||||
$this->makeDriver($this->fakeTemplate())->send($member, $this->notification());
|
||||
|
||||
Bus::assertDispatched(SendMessageJob::class);
|
||||
$this->assertDatabaseCount('bizppurio_dispatches', 1);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user