From 958d881b4be623441a89af338d940260afde11d5 Mon Sep 17 00:00:00 2001 From: Gabriel Sancho Date: Thu, 26 Mar 2026 18:54:15 -0300 Subject: [PATCH] =?UTF-8?q?test(backend):=20add=20failing=20tests=20for=20?= =?UTF-8?q?step=208=20=E2=80=94=20EntityScheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/EntitySchedulerTest.kt | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt diff --git a/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt b/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt new file mode 100644 index 0000000..f631a1f --- /dev/null +++ b/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt @@ -0,0 +1,158 @@ +package com.condado.newsletter.scheduler + +import com.condado.newsletter.model.DispatchLog +import com.condado.newsletter.model.DispatchStatus +import com.condado.newsletter.model.EmailContext +import com.condado.newsletter.model.ParsedAiResponse +import com.condado.newsletter.model.VirtualEntity +import com.condado.newsletter.repository.DispatchLogRepository +import com.condado.newsletter.service.AiService +import com.condado.newsletter.service.AiServiceException +import com.condado.newsletter.service.EmailReaderService +import com.condado.newsletter.service.EmailSenderService +import com.condado.newsletter.service.PromptBuilderService +import io.mockk.called +import io.mockk.capture +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mutableListOf +import io.mockk.runs +import io.mockk.slot +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.LocalDateTime +import java.util.UUID + +class EntitySchedulerTest { + + private val emailReaderService: EmailReaderService = mockk() + private val promptBuilderService: PromptBuilderService = mockk() + private val aiService: AiService = mockk() + private val emailSenderService: EmailSenderService = mockk() + private val dispatchLogRepository: DispatchLogRepository = mockk() + + private lateinit var scheduler: EntityScheduler + + private val entity = VirtualEntity( + name = "João Gerente", + email = "joao@condado.com", + jobTitle = "Gerente de Nada", + personality = "Muito formal", + scheduleCron = "0 9 * * *", + contextWindowDays = 3, + active = true + ).apply { id = UUID.randomUUID() } + + private val inactiveEntity = VirtualEntity( + name = "Maria Inativa", + email = "maria@condado.com", + jobTitle = "Consultora de Vibe", + active = false + ).apply { id = UUID.randomUUID() } + + @BeforeEach + fun setUp() { + scheduler = EntityScheduler( + emailReaderService = emailReaderService, + promptBuilderService = promptBuilderService, + aiService = aiService, + emailSenderService = emailSenderService, + dispatchLogRepository = dispatchLogRepository, + recipients = "recipient@example.com", + inboxFolder = "INBOX" + ) + } + + @Test + fun should_runFullPipeline_when_entityIsTriggered() { + val emails = listOf(EmailContext("sender@example.com", "Subject", "Body", LocalDateTime.now())) + val prompt = "Generated prompt" + val aiResponse = ParsedAiResponse(subject = "Weekly memo", body = "Dear colleagues...") + + every { emailReaderService.readEmails("INBOX", 3) } returns emails + every { promptBuilderService.buildPrompt(entity, emails) } returns prompt + every { aiService.generate(prompt) } returns aiResponse + every { emailSenderService.send(entity.email, listOf("recipient@example.com"), aiResponse.subject, aiResponse.body) } just runs + every { dispatchLogRepository.save(any()) } answers { firstArg() } + + scheduler.runPipeline(entity) + + verify(exactly = 1) { emailReaderService.readEmails("INBOX", 3) } + verify(exactly = 1) { promptBuilderService.buildPrompt(entity, emails) } + verify(exactly = 1) { aiService.generate(prompt) } + verify(exactly = 1) { emailSenderService.send(entity.email, listOf("recipient@example.com"), aiResponse.subject, aiResponse.body) } + } + + @Test + fun should_saveDispatchLogWithStatusSent_when_pipelineSucceeds() { + val emails = emptyList() + val prompt = "prompt" + val aiResponse = ParsedAiResponse(subject = "Subject", body = "Body") + val savedLog = slot() + + every { emailReaderService.readEmails("INBOX", 3) } returns emails + every { promptBuilderService.buildPrompt(entity, emails) } returns prompt + every { aiService.generate(prompt) } returns aiResponse + every { emailSenderService.send(any(), any(), any(), any()) } just runs + every { dispatchLogRepository.save(capture(savedLog)) } answers { firstArg() } + + scheduler.runPipeline(entity) + + assertThat(savedLog.isCaptured).isTrue() + assertThat(savedLog.captured.status).isEqualTo(DispatchStatus.SENT) + assertThat(savedLog.captured.emailSubject).isEqualTo("Subject") + assertThat(savedLog.captured.emailBody).isEqualTo("Body") + } + + @Test + fun should_saveDispatchLogWithStatusFailed_when_aiServiceThrows() { + val emails = emptyList() + val prompt = "prompt" + val savedLog = slot() + + every { emailReaderService.readEmails("INBOX", 3) } returns emails + every { promptBuilderService.buildPrompt(entity, emails) } returns prompt + every { aiService.generate(prompt) } throws AiServiceException("API error") + every { dispatchLogRepository.save(capture(savedLog)) } answers { firstArg() } + + scheduler.runPipeline(entity) + + assertThat(savedLog.isCaptured).isTrue() + assertThat(savedLog.captured.status).isEqualTo(DispatchStatus.FAILED) + assertThat(savedLog.captured.errorMessage).contains("API error") + } + + @Test + fun should_saveDispatchLogWithStatusFailed_when_emailSenderThrows() { + val emails = emptyList() + val prompt = "prompt" + val aiResponse = ParsedAiResponse(subject = "Subject", body = "Body") + val savedLog = slot() + + every { emailReaderService.readEmails("INBOX", 3) } returns emails + every { promptBuilderService.buildPrompt(entity, emails) } returns prompt + every { aiService.generate(prompt) } returns aiResponse + every { emailSenderService.send(any(), any(), any(), any()) } throws RuntimeException("SMTP error") + every { dispatchLogRepository.save(capture(savedLog)) } answers { firstArg() } + + scheduler.runPipeline(entity) + + assertThat(savedLog.isCaptured).isTrue() + assertThat(savedLog.captured.status).isEqualTo(DispatchStatus.FAILED) + assertThat(savedLog.captured.errorMessage).contains("SMTP error") + } + + @Test + fun should_notTrigger_when_entityIsInactive() { + scheduler.runPipeline(inactiveEntity) + + verify { emailReaderService wasNot called } + verify { promptBuilderService wasNot called } + verify { aiService wasNot called } + verify { emailSenderService wasNot called } + verify { dispatchLogRepository wasNot called } + } +}