send) سقوط کند. */ class SmsServiceLookupOnlyTest extends TestCase { private KavehNegarProvider&MockObject $kavenegar; private SmsLogRepository&MockObject $logRepo; private MessageBusInterface&MockObject $bus; private SmsMessageTemplateRepository&MockObject $messageTemplateRepo; private SmsTextResolver&MockObject $textResolver; private SmsService $service; protected function setUp(): void { $this->kavenegar = $this->createMock(KavehNegarProvider::class); $this->kavenegar->method('getName')->willReturn('kavenegar'); $this->logRepo = $this->createMock(SmsLogRepository::class); $this->bus = $this->createMock(MessageBusInterface::class); $this->messageTemplateRepo = $this->createMock(SmsMessageTemplateRepository::class); $this->textResolver = $this->createMock(SmsTextResolver::class); $this->service = new SmsService( $this->kavenegar, $this->createMock(RanginehProvider::class), $this->logRepo, $this->bus, $this->messageTemplateRepo, $this->textResolver, $this->createMock(LoggerInterface::class), ); } public function testSendNowWithoutTemplateCodeRefusesRawSendAndLogsFailure(): void { $this->kavenegar->expects($this->never())->method('send'); $this->kavenegar->expects($this->never())->method('sendTemplate'); $captured = null; $this->logRepo->expects($this->once())->method('save') ->willReturnCallback(function (SmsLog $log) use (&$captured) { $captured = $log; }); $msg = new SendSmsMessage('09120000000', 'hello', 'kavenegar', tag: SmsLog::TAG_GLOBAL); $ok = $this->service->sendNow($msg); $this->assertFalse($ok); $this->assertNotNull($captured); $this->assertFalse($captured->toArray()['success']); } public function testSendNowWithTemplateCodeUsesLookup(): void { $this->kavenegar->expects($this->never())->method('send'); $this->kavenegar->expects($this->once())->method('sendTemplate') ->with('09120000000', 'clinicpro-otp', ['token' => '1234']) ->willReturn(true); $captured = null; $this->logRepo->expects($this->once())->method('save') ->willReturnCallback(function (SmsLog $log) use (&$captured) { $captured = $log; }); $msg = new SendSmsMessage( '09120000000', 'code 1234', 'kavenegar', templateVars: ['token' => '1234'], templateCode: 'clinicpro-otp', tag: SmsLog::TAG_OTP, ); $this->assertTrue($this->service->sendNow($msg)); // template به‌کاررفته باید در SmsLog ثبت شود تا در پنل ادمین دیده شود. $this->assertSame('clinicpro-otp', $captured->toArray()['template_code']); } public function testDispatchTemplateForTagWithoutTemplateDoesNotEnqueueAndLogsFailure(): void { // TAG_GLOBAL در DEFAULTS نیست و در DB هم چیزی نداریم → باید رد شود، نه ارسال خام. $this->messageTemplateRepo->method('findByTag')->willReturn(null); $this->textResolver->method('resolve')->willReturn('any body'); $this->bus->expects($this->never())->method('dispatch'); $this->logRepo->expects($this->once())->method('save'); $this->service->dispatchTemplate(SmsLog::TAG_GLOBAL, '09120000000', ['x' => 'y']); } public function testDispatchTemplateForKnownTagEnqueuesWithTemplateCode(): void { // TAG_OTP در DEFAULTS الگو + token_map دارد → باید با templateCode به صف برود. $this->messageTemplateRepo->method('findByTag')->willReturn(null); $this->textResolver->method('resolve')->willReturn('کد شما: 1234'); $captured = null; $this->bus->expects($this->once())->method('dispatch') ->willReturnCallback(function (SendSmsMessage $m) use (&$captured) { $captured = $m; return new Envelope($m); }); $this->logRepo->expects($this->never())->method('save'); $this->service->dispatchTemplate(SmsLog::TAG_OTP, '09120000000', ['code' => '1234', 'site' => 'یزد']); $this->assertNotNull($captured); $this->assertSame('clinicpro-otp', $captured->templateCode); $this->assertArrayHasKey('token', $captured->templateVars); $this->assertSame('1234', $captured->templateVars['token']); } }