diff --git a/build.gradle.kts b/build.gradle.kts index ea2d7e6..cee9f87 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - + testImplementation("io.cucumber:cucumber-spring:7.22.2") testImplementation(platform("org.junit:junit-bom:5.12.2")) testImplementation(platform("io.cucumber:cucumber-bom:7.22.2")) testImplementation(platform("org.assertj:assertj-bom:3.27.3")) diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/config/WhatsappApiConfig.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/config/WhatsappApiConfig.kt index 673f2bb..1c7db0e 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/config/WhatsappApiConfig.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/config/WhatsappApiConfig.kt @@ -6,8 +6,10 @@ import com.whatsapp.api.impl.WhatsappBusinessCloudApi import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile import java.beans.BeanProperty +@Profile("!test") @Configuration class WhatsappApiConfig { @Value("\${whatsapp.api.token}") diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/controller/GlobalExceptionHandler.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/controller/GlobalExceptionHandler.kt new file mode 100644 index 0000000..7f7dfd7 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/controller/GlobalExceptionHandler.kt @@ -0,0 +1,19 @@ +package ufrpe.sbpc.botpcd.controller + +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler +import org.slf4j.Logger + +@ControllerAdvice +class GlobalExceptionHandler { + val logger: Logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java) + + @ExceptionHandler(RuntimeException::class) + fun runtimeExceptionHandler(runtimeException: RuntimeException): ResponseEntity> { + logger.error("Erro inesperado ao chamar a API", runtimeException) + return ResponseEntity(mapOf("message" to "Some error ocorrer calling the API"), HttpStatus.INTERNAL_SERVER_ERROR) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/controller/WhatsappWebhookController.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/controller/WhatsappWebhookController.kt index 2ef7260..c9b4152 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/controller/WhatsappWebhookController.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/controller/WhatsappWebhookController.kt @@ -2,19 +2,22 @@ package ufrpe.sbpc.botpcd.controller import com.whatsapp.api.domain.webhook.WebHook import com.whatsapp.api.domain.webhook.WebHookEvent +import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository import ufrpe.sbpc.botpcd.service.FirstContactService @RestController -class WhatsappWebhookController(private val firstContactService: FirstContactService) { +class WhatsappWebhookController(private val firstContactService: FirstContactService, private val messageExchangeRepository: MessageExchangeRepository) { @Value("\${whatsapp.verify.token}") lateinit var VERIFY_TOKEN: String - var logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java) + val logger: Logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java) @PostMapping("/webhooks") fun eventNotification( @@ -27,11 +30,15 @@ class WhatsappWebhookController(private val firstContactService: FirstContactSer val event: WebHookEvent = WebHook.constructEvent(body) for(entry in event.entry) { for(change in entry.changes) { - firstContactService.redirectFluxByUserType(change.value.contacts[0].waId, change) + if(change.value?.messages == null) { + return ResponseEntity.ok("We don't handle this type of message") + } + val userPhoneNumber = change.value.contacts[0].waId + firstContactService.redirectFluxByUserType(userPhoneNumber, change) } } // Opcional: validar assinatura com 'signature' - return ResponseEntity.ok("Evento processado") + return ResponseEntity.ok("Event process") } @GetMapping("/webhooks") diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendance.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendance.kt index 97ab3a3..895af06 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendance.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendance.kt @@ -27,9 +27,13 @@ class Attendance( var pwd: PWD, @ManyToOne - @JoinColumn(name = "monitor_id") - @NotNull(message = "The monitor is required") - var monitor: Monitor, + @JoinColumn(name = "attendant_id") + @NotNull(message = "The attendant ID is required") + var attendant: Attendant, + + @Enumerated(EnumType.STRING) + @NotNull(message = "The attendant type is required") + var attendantType: Provider, // Campos opcionais que serão atualizados durante o ciclo de vida do atendimento var acceptDateTime: LocalDateTime? = null, @@ -41,4 +45,3 @@ class Attendance( var endDateTime: LocalDateTime? = null ) - diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendant.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendant.kt new file mode 100644 index 0000000..294c025 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendant.kt @@ -0,0 +1,16 @@ +package ufrpe.sbpc.botpcd.entity + +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.Table +import jakarta.validation.constraints.NotEmpty + +@Entity +@Table(name = "tb_attendant") +abstract class Attendant( + name: String, + phoneNumber: String, + @Enumerated(EnumType.STRING) + var status: UserStatus = UserStatus.AVAILABLE +) : User(name = name, phoneNumber = phoneNumber) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/CommitteeMember.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/CommitteeMember.kt index d7074bd..37cb184 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/CommitteeMember.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/CommitteeMember.kt @@ -10,5 +10,6 @@ import jakarta.persistence.Table @Table(name = "committee_member") class CommitteeMember( name: String, - phoneNumber: String -): User(name = name, phoneNumber = phoneNumber) \ No newline at end of file + phoneNumber: String, + status: UserStatus +): Attendant(name = name, phoneNumber = phoneNumber, status = status) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Disability.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Disability.kt index 091ad24..e90a9f4 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Disability.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Disability.kt @@ -12,16 +12,23 @@ enum class Disability(val textOption: String) { PHYSICAL_DISABILITY("Deficiência física"), MOBILITY_IMPAIRED("Não tenho deficiência, mas tenho mobilidade reduzida"); + companion object { - fun parse(text: String): Disability { + fun getByText(text: String): Disability { val disability = Disability.entries.find { it.textOption.equals(text, ignoreCase = true) } if(disability == null) { throw IllegalArgumentException("Invalid disability type: $text") } return disability } - fun textList(): Array { - return Disability.entries.map { it.textOption }.toTypedArray() + fun getOptions(): String { + var message = "Olá, qual sua deficiência?\n" + for (disability in Disability.entries) { + message += "- Digite ${disability.ordinal + 1} para ${disability.textOption}\n" + } + message += "- Digite 7 para Não preciso de suporte." + return message } + fun getByOrdinal(ordinal: Int) = Disability.entries.find { it.ordinal == ordinal } } } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt new file mode 100644 index 0000000..a858c05 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt @@ -0,0 +1,23 @@ +package ufrpe.sbpc.botpcd.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import org.hibernate.annotations.CreationTimestamp +import java.time.LocalDateTime + +@Entity +class MessageExchange( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + var fromPhoneNumber: String, + var toPhoneNumber: String, + @Column(columnDefinition = "TEXT") + var message: String +) { + @CreationTimestamp + var createAt: LocalDateTime? = null +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Monitor.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Monitor.kt index 1ebc1b7..d34f521 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Monitor.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/Monitor.kt @@ -14,7 +14,8 @@ import jakarta.validation.constraints.NotEmpty class Monitor( name: String, phoneNumber: String, + status: UserStatus, @Enumerated(value = EnumType.STRING) @NotEmpty(message = "Monitor needs to have an assistance type") - var assistanceTypes: MonitorAssistanceType, -) : User(name = name, phoneNumber = phoneNumber) \ No newline at end of file + var assistanceTypes: MonitorAssistanceType +) : Attendant(name = name, phoneNumber = phoneNumber, status = status) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt index 5ce8c89..e082ed4 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt @@ -1,8 +1,14 @@ package ufrpe.sbpc.botpcd.entity +import jakarta.persistence.CascadeType +import jakarta.persistence.ElementCollection import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated +import jakarta.persistence.CollectionTable +import jakarta.persistence.Column +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToMany import jakarta.persistence.Table import jakarta.validation.constraints.NotEmpty; import org.springframework.data.repository.NoRepositoryBean @@ -17,8 +23,9 @@ import org.springframework.data.repository.NoRepositoryBean class PWD( name: String? = null, phoneNumber: String, - phoneNumberId: String, - @NotEmpty(message = "The PWD needs to have a disability") - @Enumerated(value = EnumType.STRING) - var disability: MutableSet -): User(name = name, phoneNumber = phoneNumber, phoneNumberId = phoneNumberId) \ No newline at end of file + @ElementCollection(targetClass = Disability::class) + @CollectionTable(name = "tb_pwd_disabilities", joinColumns = [JoinColumn(name = "pwd_id")]) + @Column(name = "disability") + @Enumerated(EnumType.STRING) + var disabilities: MutableSet = mutableSetOf() +): User(name = name, phoneNumber = phoneNumber) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWDDisability.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWDDisability.kt new file mode 100644 index 0000000..c2e888e --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWDDisability.kt @@ -0,0 +1,26 @@ +package ufrpe.sbpc.botpcd.entity + +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table + +@Entity +@Table(name="tb_pwd_disability") +class PWDDisability( + @Id + @GeneratedValue(strategy= GenerationType.IDENTITY) + val id: Long?= null, + + @Enumerated(EnumType.STRING) + val disability: Disability, + + @ManyToOne + @JoinColumn(name="pwd_id") + val pwd: PWD +) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt index 30590d1..9d90e05 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt @@ -3,23 +3,69 @@ package ufrpe.sbpc.botpcd.entity /** * */ -sealed class ServiceType(val provider: Provider, val disability: Set) { - object Libras : ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.DEAFNESS)) { +sealed class ServiceType( + val provider: Provider, + val disability: Set, + val description: String +) { + object Libras : ServiceType( + provider = Provider.MONITOR, + disability = mutableSetOf(Disability.DEAFNESS), + description = "informações em Libras" + ) { val monitorAssistanceType = MonitorAssistanceType.LIBRAS_MONITOR } - object LibrasInterpreter: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.DEAFNESS)) - object Mobility: ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY, Disability.BLINDED)) { + object LibrasInterpreter : ServiceType( + provider = Provider.COMMITTEE_MEMBER, + disability = mutableSetOf(Disability.DEAFNESS), + description = "atividade com interpretação em Libras" + ) + object Mobility : ServiceType( + provider = Provider.MONITOR, + disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY, Disability.BLINDED), + description = "ajuda na mobilidade" + ) { val monitorAssistanceType = MonitorAssistanceType.MOBILITY_MONITOR } - object AudioDescription: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.BLINDED)) - object NeurodivergentSupport: ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.NEURODIVERGENT)) { + + object AudioDescription : ServiceType( + provider = Provider.COMMITTEE_MEMBER, + disability = mutableSetOf(Disability.BLINDED), + description = "programação com audiodescrição" + ) + + object NeurodivergentSupport : ServiceType( + provider = Provider.MONITOR, + disability = mutableSetOf(Disability.NEURODIVERGENT), + description = "suporte para pessoas neurodivergentes" + ) { val monitorAssistanceType = MonitorAssistanceType.NEURODIVERGENT_SUPPORT_MONITOR } - object GuideInterpreter: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.DEAFBLINDNESS)) - object HygieneAndNutrition: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY)) - object Car : ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.PHYSICAL_DISABILITY, Disability.MOBILITY_IMPAIRED - )); - fun getServicesByDisability(disability: Disability): List { - return ServiceType::class.sealedSubclasses.mapNotNull{ it.objectInstance }.filter{ disability in it.disability } + + object GuideInterpreter : ServiceType( + provider = Provider.COMMITTEE_MEMBER, + disability = mutableSetOf(Disability.DEAFBLINDNESS), + description = "guia-intérprete" + ) + + object HygieneAndNutrition : ServiceType( + provider = Provider.COMMITTEE_MEMBER, + disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY), + description = "ajuda com alimentação e higiene" + ) + + object Car : ServiceType( + provider = Provider.COMMITTEE_MEMBER, + disability = mutableSetOf(Disability.PHYSICAL_DISABILITY, Disability.MOBILITY_IMPAIRED), + description = "transporte para deslocamento no evento" + ) + + companion object { + @JvmStatic + fun getServicesByDisability(disability: Disability): List { + return ServiceType::class.sealedSubclasses + .mapNotNull { it.objectInstance } + .filter { disability in it.disability } + } } } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/User.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/User.kt index 64b65c9..99dda86 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/User.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/User.kt @@ -25,6 +25,5 @@ abstract class User( var id: Long? = null, var name: String? = null, @NotEmpty(message = "The User needs to have a phone number") - var phoneNumber: String, - var phoneNumberId: String? = null + var phoneNumber: String ) \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/entity/UserStatus.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/UserStatus.kt new file mode 100644 index 0000000..bafb739 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/entity/UserStatus.kt @@ -0,0 +1,12 @@ +package ufrpe.sbpc.botpcd.entity + +/* +(DISPONIVEL) ele pode receber um atendimento +(OCUPADO) ele esta em atendimento, mas quando finalizar o codigo trocara para disponivel +(INDISPONIVEL) ele foi ao banheiro / esta ocupado. Nao vai receber chamado ate que altere seu status manualmente para available +*/ +enum class UserStatus(val text: String) { + AVAILABLE("Disponível"), + BUSY("Ocupado"), + UNAVAILABLE("Indisponível") +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt new file mode 100644 index 0000000..67c3dfc --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/AttendanceRepository.kt @@ -0,0 +1,6 @@ +package ufrpe.sbpc.botpcd.repository + +import org.springframework.data.jpa.repository.JpaRepository +import ufrpe.sbpc.botpcd.entity.Attendance + +interface AttendanceRepository : JpaRepository \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/CommitteeMemberRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/CommitteeMemberRepository.kt index 505e6ad..62302cf 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/CommitteeMemberRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/CommitteeMemberRepository.kt @@ -2,7 +2,9 @@ package ufrpe.sbpc.botpcd.repository import org.springframework.data.jpa.repository.JpaRepository import ufrpe.sbpc.botpcd.entity.CommitteeMember +import ufrpe.sbpc.botpcd.entity.UserStatus interface CommitteeMemberRepository: JpaRepository { fun findByPhoneNumber(phoneNumber: String): CommitteeMember? + fun findByStatus(status: UserStatus): List } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MessageExchangeRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MessageExchangeRepository.kt new file mode 100644 index 0000000..a84eba6 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MessageExchangeRepository.kt @@ -0,0 +1,12 @@ +package ufrpe.sbpc.botpcd.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import ufrpe.sbpc.botpcd.entity.MessageExchange + +interface MessageExchangeRepository: JpaRepository { + @Query( + "SELECT m FROM MessageExchange m WHERE m.toPhoneNumber = :toPhoneNumber AND m.fromPhoneNumber = :fromPhoneNumber ORDER BY m.createAt DESC LIMIT 1" + ) + fun lastExchangeMessage(toPhoneNumber: String, fromPhoneNumber: String): MessageExchange? +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt index f1e6cc0..a4984b4 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/MonitorRepository.kt @@ -2,7 +2,9 @@ package ufrpe.sbpc.botpcd.repository import org.springframework.data.jpa.repository.JpaRepository import ufrpe.sbpc.botpcd.entity.Monitor +import ufrpe.sbpc.botpcd.entity.UserStatus interface MonitorRepository : JpaRepository{ fun findByPhoneNumber(phoneNumber: String): Monitor? + fun findByStatus(status: UserStatus): List } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/PWDRepository.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/PWDRepository.kt index 1dfcda3..c6a4e55 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/repository/PWDRepository.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/repository/PWDRepository.kt @@ -1,8 +1,11 @@ package ufrpe.sbpc.botpcd.repository import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import ufrpe.sbpc.botpcd.entity.PWD interface PWDRepository: JpaRepository { fun findByPhoneNumber(phoneNumber: String): PWD? + @Query("SELECT p FROM PWD p JOIN FETCH p.disabilities WHERE p.phoneNumber = :phoneNumber") + fun findByPhoneNumberWithDisabilities(phoneNumber: String): PWD? } \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt new file mode 100644 index 0000000..10ca497 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendanceService.kt @@ -0,0 +1,18 @@ +package ufrpe.sbpc.botpcd.service + +import org.springframework.stereotype.Service +import ufrpe.sbpc.botpcd.entity.Disability +import ufrpe.sbpc.botpcd.entity.ServiceType +import ufrpe.sbpc.botpcd.repository.AttendanceRepository + +@Service +class AttendanceService( + private val attendanceRepository: AttendanceRepository, + private val whatsappService: WhatsappService, +) { + fun sendServices(botNumber: String, userNumber: String, disability: Disability) { + val serviceList = ServiceType.getServicesByDisability(disability) + val message = whatsappService.createOptions(serviceList.map { it -> it.description }) + whatsappService.sendMessage(botNumber, userNumber, message) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt new file mode 100644 index 0000000..432616c --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/AttendantStatusService.kt @@ -0,0 +1,65 @@ +package ufrpe.sbpc.botpcd.service + +import org.springframework.stereotype.Service +import ufrpe.sbpc.botpcd.entity.* +import ufrpe.sbpc.botpcd.repository.MonitorRepository +import ufrpe.sbpc.botpcd.repository.CommitteeMemberRepository +import org.springframework.transaction.annotation.Transactional + +@Service +class AttendantStatusService( + private val monitorRepository: MonitorRepository, + private val committeeMemberRepository: CommitteeMemberRepository +) { + + @Transactional + fun setMonitorStatus(monitor: Monitor, status: UserStatus) { + monitor.status = status + monitorRepository.save(monitor) + } + + @Transactional + fun setCommitteeMemberStatus(member: CommitteeMember, status: UserStatus) { + member.status = status + committeeMemberRepository.save(member) + } + + fun sendStatusChanger(attendant: Attendant) { + if (attendant.status == UserStatus.UNAVAILABLE) { + /*1 - Ficar Disponível*/ + /* + "Olá, você está Indisponível no momento. Deseja ficar Disponível para receber atendimentos?" + */ + } + if (attendant.status == UserStatus.BUSY) { + /* + 1 - Encerrar Atendimento (vai internamente mudar o status dele pra disponível) + + 2 - Ficar Indisponível (ele para de ser chamado para atendimentos) + */ + /* + "Olá, você está em atendimento. Deseja encerrá-lo e continuar Disnpoível ou deseja ficar Indisponível?" + */ + } + if (attendant.status == UserStatus.AVAILABLE) { + /*1 - Ficar Indisponível*/ + /* + "Olá, você está Disponível no momento. Deseja ficar Indisponível para não receber atendimentos?" + */ + } + } + + fun findAvailableMonitors(): List { + return monitorRepository.findByStatus(UserStatus.AVAILABLE) + } + + fun findAvailableCommitteeMembers(): List { + return committeeMemberRepository.findByStatus(UserStatus.AVAILABLE) + } + + // Método para filtrar por tipo de assistência também + fun findAvailableMonitorsByType(assistanceType: MonitorAssistanceType): List { + val availableMonitors = findAvailableMonitors() + return availableMonitors.filter { it.assistanceTypes == assistanceType } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt index 2503065..79d2f1d 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/FirstContactService.kt @@ -1,8 +1,14 @@ package ufrpe.sbpc.botpcd.service import com.whatsapp.api.domain.webhook.Change +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import ufrpe.sbpc.botpcd.controller.WhatsappWebhookController +import ufrpe.sbpc.botpcd.entity.Disability +import ufrpe.sbpc.botpcd.entity.MessageExchange import ufrpe.sbpc.botpcd.repository.CommitteeMemberRepository +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository import ufrpe.sbpc.botpcd.repository.MonitorRepository import ufrpe.sbpc.botpcd.repository.PWDRepository @@ -11,26 +17,53 @@ class FirstContactService( private val pwdRepository: PWDRepository, private val monitorRepository: MonitorRepository, private val committeeMemberRepository: CommitteeMemberRepository, - private val registerService: RegisterPWDService + private val registerPWDService: RegisterPWDService, + private val whatsappService: WhatsappService, + private val attendanceService: AttendanceService, + private val messageExchangeRepository: MessageExchangeRepository ) { + val logger: Logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java) + fun redirectFluxByUserType(phoneNumber: String, change: Change) { - // to get a message from the change change.value.messages[0].text.body + val disabilityNumberOptions = Disability.entries.map { (it.ordinal + 1).toString() }.toMutableList().apply { this.add("7") } + val botNumber = change.value.metadata.phoneNumberId + val message = change.value.messages[0].text.body.trim() + messageExchangeRepository.save(MessageExchange(fromPhoneNumber = phoneNumber, toPhoneNumber = botNumber, message = message)) + val lastBotMessage = messageExchangeRepository.lastExchangeMessage(fromPhoneNumber = botNumber, toPhoneNumber = phoneNumber) when { pwdRepository.findByPhoneNumber(phoneNumber) != null -> { val pwd = pwdRepository.findByPhoneNumber(phoneNumber)!! - if(pwd.name == null) { -// registerService. + // Nome ainda não registrado + if((lastBotMessage?.message ?: "") == "Qual o seu nome?" && pwd.name == null) { + registerPWDService.registerName(pwd, message) + whatsappService.sendMessage(botNumber, phoneNumber, "Cadastro realizado.") + } else { + // Completar com o resto da menssagem + whatsappService.sendMessage(botNumber, phoneNumber, "Olá, ${pwd.name}.") + attendanceService.sendServices(botNumber,phoneNumber, pwd.disabilities.first()) } } - monitorRepository.findByPhoneNumber(phoneNumber) != null -> { + monitorRepository.findByPhoneNumber(phoneNumber) != null || committeeMemberRepository.findByPhoneNumber(phoneNumber) != null-> { } - committeeMemberRepository.findByPhoneNumber(phoneNumber) != null -> { - + (lastBotMessage?.message ?: "") == Disability.getOptions() && message in disabilityNumberOptions -> { + val disabilityNumber = message.toInt() + val ordinalDisability = disabilityNumber - 1 + val disability = Disability.getByOrdinal(ordinalDisability) + if(disabilityNumber == 7){ + whatsappService.sendMessage(botNumber, phoneNumber, "Agradecemos o contato! Este canal é exclusivo para atendimento de pessoas com deficiência ou mobilidade reduzida que participarão do evento. Desejamos a você uma excelente participação na 77ª Reunião Anual da SBPC.") + } else if(disability == null) { + logger.warn("Foi passado um numero de deficiencia incorreto numero da disability $disabilityNumber") + whatsappService.sendMessage(botNumber, phoneNumber, "Digite um número válido.") + } else { + registerPWDService.registerDisability(botNumber,phoneNumber, disability) + whatsappService.sendMessage(botNumber, phoneNumber, "Entendi que você ${disability.textOption}") + registerPWDService.whatsIsYourName(botNumber, phoneNumber) + } } else -> { // Usuario não cadastrado -// registerService.registerPWD(phoneNumber, change) + registerPWDService.whatIsYourDisability(botNumber, phoneNumber) } } } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/RegisterPWDService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/RegisterPWDService.kt index 8ecf374..316c2df 100644 --- a/src/main/kotlin/ufrpe/sbpc/botpcd/service/RegisterPWDService.kt +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/RegisterPWDService.kt @@ -2,14 +2,30 @@ package ufrpe.sbpc.botpcd.service import org.springframework.stereotype.Service import ufrpe.sbpc.botpcd.repository.PWDRepository -import com.whatsapp.api.impl.WhatsappBusinessCloudApi +import ufrpe.sbpc.botpcd.entity.Disability +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.entity.PWD +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository @Service class RegisterPWDService( private val pwdRepository: PWDRepository, - private val whatsappBusinessCloudApi: WhatsappBusinessCloudApi, + private val whatsappService: WhatsappService, + private val messageExchangeRepository: MessageExchangeRepository ) { - fun registerDisability() { + fun registerDisability(botPhoneNumber: String, pwdPhoneNumber: String, disability: Disability) { + val pwd = PWD(disabilities = mutableSetOf(disability), phoneNumber = pwdPhoneNumber) + pwdRepository.save(pwd) + } + fun registerName(pwd: PWD, name: String) { + pwd.name = name + pwdRepository.save(pwd) + } + fun whatIsYourDisability(botPhoneNumber: String, pwdPhoneNumber: String) { + whatsappService.sendMessage(botPhoneNumber,pwdPhoneNumber, Disability.getOptions()) + } + fun whatsIsYourName(botPhoneNumber: String, pwdPhoneNumber: String) { + whatsappService.sendMessage(botPhoneNumber, pwdPhoneNumber, "Qual o seu nome?") } } diff --git a/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt b/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt new file mode 100644 index 0000000..5d709f9 --- /dev/null +++ b/src/main/kotlin/ufrpe/sbpc/botpcd/service/WhatsappService.kt @@ -0,0 +1,33 @@ +package ufrpe.sbpc.botpcd.service + +import com.whatsapp.api.domain.messages.Message +import com.whatsapp.api.domain.messages.TextMessage +import com.whatsapp.api.impl.WhatsappBusinessCloudApi +import org.springframework.stereotype.Service +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository + + +@Service +class WhatsappService( + private val cloudApi: WhatsappBusinessCloudApi, + private val messageExchangeRepository: MessageExchangeRepository +) { + fun sendMessage(botNumber: String, destinyNumberID: String, msg: String) { + val message = Message.MessageBuilder.builder() + .setTo(destinyNumberID) + .buildTextMessage(TextMessage().setBody(msg)) + cloudApi.sendMessage(botNumber, message) + messageExchangeRepository.save(MessageExchange(fromPhoneNumber = botNumber, toPhoneNumber = destinyNumberID, message = msg)) + } + + fun createOptions(options: List, header: String = ""): String { + var mensage = "${header}\n" + for (i in options.indices) { + mensage += "- Digite ${i + 1} para ${options[i]}\n" + } + return mensage + } +} + + diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/BotPcdApplicationTests.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/BotPcdApplicationTests.kt index 818e733..7e60b36 100644 --- a/src/test/kotlin/ufrpe/sbpc/botpcd/BotPcdApplicationTests.kt +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/BotPcdApplicationTests.kt @@ -1,13 +1,13 @@ package ufrpe.sbpc.botpcd -//import org.junit.jupiter.api.Test -//import org.springframework.boot.test.context.SpringBootTest -// -//@SpringBootTest -//class BotPcdApplicationTests { -// -// @Test -// fun contextLoads() { -// } -// -//} +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class BotPcdApplicationTests { + + @Test + fun contextLoads() { + } + +} diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/RunCucumberTest.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/RunCucumberTest.kt index 417c302..70b9812 100644 --- a/src/test/kotlin/ufrpe/sbpc/botpcd/RunCucumberTest.kt +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/RunCucumberTest.kt @@ -1,16 +1,16 @@ package ufrpe.sbpc.botpcd -//import io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME -//import io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME -//import org.junit.platform.suite.api.ConfigurationParameter -//import org.junit.platform.suite.api.IncludeEngines -//import org.junit.platform.suite.api.SelectPackages -//import org.junit.platform.suite.api.Suite -// -//@Suite -//@IncludeEngines("cucumber") -//@SelectPackages("ufrpe.sbpc.botpcd") -//@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") -//@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "ufrpe.sbpc.botpcd") -//class RunCucumberTest { -//} \ No newline at end of file +import io.cucumber.core.options.Constants.GLUE_PROPERTY_NAME +import io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME +import org.junit.platform.suite.api.ConfigurationParameter +import org.junit.platform.suite.api.IncludeEngines +import org.junit.platform.suite.api.SelectPackages +import org.junit.platform.suite.api.Suite + +@Suite +@IncludeEngines("cucumber") +@SelectPackages("ufrpe.sbpc.botpcd") +@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "ufrpe.sbpc.botpcd") +class RunCucumberTest { +} \ No newline at end of file diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt index 9caf291..116f7f9 100644 --- a/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/StepDefinitions.kt @@ -1,7 +1,119 @@ package ufrpe.sbpc.botpcd -//import io.cucumber.java.en.Given -// -//class StepDefinitions { -// -//} +import com.whatsapp.api.domain.webhook.Value +import com.whatsapp.api.domain.webhook.WebHook +import io.cucumber.java.pt.Dado +import io.cucumber.java.pt.Entao +import io.cucumber.java.pt.Quando +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import ufrpe.sbpc.botpcd.entity.Disability +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.entity.PWD +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository +import ufrpe.sbpc.botpcd.repository.PWDRepository +import java.io.File + + +class StepDefinitions( + private val mockMvc: MockMvc, + val pwdRepository: PWDRepository, + val messageExchangeRepository: MessageExchangeRepository, +) { + private val numberUserNotRegister: String = "558187654321" + private val botNumber: String = "15556557522" + @Dado("usuário recebeu mensagem {string}") + fun `usuário recebeu mensagem`(message: String) { + mockUserRecievedMessage(numberUserNotRegister, message) + } + @Dado("usuário recebeu mensagem") + fun `usuário recebeu mensagem docs string`(message: String) { + mockUserRecievedMessage(numberUserNotRegister, message) + } + @Dado("pcd está cadastrado completo") + fun pcdEstaCadastrado() { + // Implementar lógica para garantir que o PCD está cadastrado no sistema + } + @Dado("usuário não cadastrado") + fun `usuário não cadastrado`() { + assertNull(pwdRepository.findByPhoneNumber(numberUserNotRegister)) + } + @Dado("usuário possui deficiência cadastrada") + fun `usuário que possui deficiencia cadastrada`() { + pwdRepository.save(PWD(phoneNumber = numberUserNotRegister, disabilities = mutableSetOf(Disability.BLINDED))) + } + @Quando("usuário envia mensagem {string}") + fun `usuario envia mensagem`(mensagemEnviada: String) { + userSendMessage(mensagemEnviada, numberUserNotRegister) + } + @Entao("bot registrará o usuário com deficiencia {string}") + fun `bot registra deficiencia do usuário`(deficiencia: String) { + val pwd = pwdRepository.findByPhoneNumberWithDisabilities(numberUserNotRegister)!! + assertEquals(pwd.disabilities.first().textOption, deficiencia) + pwdRepository.delete(pwd) + } + @Entao("bot salvará o nome do usuário {string}") + fun `bot salva o nome do usuário`(nome: String) { + assertEquals(pwdRepository.findByPhoneNumber(numberUserNotRegister)!!.name, nome) + } + @Entao("usuário receberá mensagem {string}") + fun `usuario receberá mensagem`(message: String) { + testarMensagemRecebidaDoUsuario(message, numberUserNotRegister) + } + @Entao("usuário receberá mensagem") + fun `usuario receberá mensagem docs string`(message: String) { + testarMensagemRecebidaDoUsuario(message, numberUserNotRegister) + } + fun mockUserRecievedMessage(userNumber: String, message: String) { + messageExchangeRepository.save(MessageExchange(fromPhoneNumber = botNumber, toPhoneNumber = numberUserNotRegister, message = message)) + } + fun testarMensagemRecebidaDoUsuario(mensagemEsperada: String, userPhoneNumber: String) { + assertEquals(mensagemEsperada, messageExchangeRepository.lastExchangeMessage(toPhoneNumber = userPhoneNumber, fromPhoneNumber = botNumber)?.message) + } + private fun userSendMessage(mensagemEnviada: String, userPhoneNumber: String) { + val payload = loadPayload("src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-oi.json") + .changeUserNumber(userPhoneNumber) + .changeUserMessage(mensagemEnviada) + .changeBotNumber(botNumber) + mockMvc.perform( + post("/webhooks") + .content(payload) + .contentType("application/json") + ).andExpect(status().isOk) + } + + @Quando("O PCD com a deficiência de {string} envia mensagem {string}") + fun pcdComDeficienciaMandaMensagem(deficiencia: String, mensagem: String) { + // Implementar lógica para simular a mensagem enviada pelo PCD + } + + @Entao("O bot vai enviar uma lista de opções de acordo com a {string}") + fun botEnviaListaOpcoes(opcoes: String) { + // Implementar lógica para verificar se a resposta do bot corresponde às opções esperadas + } +} + +fun loadPayload(filePath: String): String { + return File(filePath).readText(Charsets.UTF_8) +} +fun String.changeUserNumber(newNumber: String): String { + return this.replace(Regex("(?<=\"wa_id\": \")\\d+(?=\")"), newNumber) +} +fun String.changeUserMessage(newMessage: String): String { + return this.replace(Regex("(?<=\\\"body\\\": \\\").*?(?=\\\")"), newMessage) +} +fun String.changeBotNumber(newNumber: String): String { + return this.replace(Regex("(?<=\\\"phone_number_id\\\": \\\").*?(?=\\\")"), newNumber) +} +fun String.getChange(): Value { + return WebHook.constructEvent(this).entry[0].changes[0].value +} +fun String.getBotNumber(): String { + return this.getChange().metadata.displayPhoneNumber +} +fun String.getMessageBody(): String { + return this.getChange().messages[0].text.body +} \ No newline at end of file diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappBusinessCloudApiMock.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappBusinessCloudApiMock.kt new file mode 100644 index 0000000..825bdc6 --- /dev/null +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/WhatsappBusinessCloudApiMock.kt @@ -0,0 +1,35 @@ +package ufrpe.sbpc.botpcd + +import com.whatsapp.api.domain.messages.Contact +import com.whatsapp.api.domain.messages.Message +import com.whatsapp.api.domain.messages.response.MessageResponse +import com.whatsapp.api.impl.WhatsappBusinessCloudApi +import org.springframework.beans.factory.annotation.Autowired +import ufrpe.sbpc.botpcd.entity.MessageExchange +import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository + +class WhatsappBusinessCloudApiMock(token: String?) : WhatsappBusinessCloudApi(token) { + var capturedPhoneNumberId: String? = null + private set + var capturedMessage: Message? = null + private set + @Autowired + private lateinit var messageExchangeRepository: MessageExchangeRepository + override fun sendMessage(phoneNumberId: String?, message: Message?): MessageResponse { + this.capturedPhoneNumberId = phoneNumberId + this.capturedMessage = message + // Retorna uma resposta simulada + messageExchangeRepository.save( + MessageExchange( + fromPhoneNumber = phoneNumberId!!, + toPhoneNumber = message!!.to, + message = message.textMessage.body + ) + ) + return MessageResponse( + "whatsapp", + listOf(null), + listOf(null) + ); + } +} \ No newline at end of file diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/config/CucumberSpringContextConfiguration.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/config/CucumberSpringContextConfiguration.kt new file mode 100644 index 0000000..2425e2c --- /dev/null +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/config/CucumberSpringContextConfiguration.kt @@ -0,0 +1,13 @@ +package ufrpe.sbpc.botpcd.config + +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.web.WebAppConfiguration +import io.cucumber.spring.CucumberContextConfiguration +import org.springframework.test.context.ActiveProfiles + +@CucumberContextConfiguration +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureMockMvc +@ActiveProfiles("test") +class CucumberSpringContextConfiguration \ No newline at end of file diff --git a/src/test/kotlin/ufrpe/sbpc/botpcd/config/MockConfig.kt b/src/test/kotlin/ufrpe/sbpc/botpcd/config/MockConfig.kt new file mode 100644 index 0000000..a0e218f --- /dev/null +++ b/src/test/kotlin/ufrpe/sbpc/botpcd/config/MockConfig.kt @@ -0,0 +1,17 @@ +package ufrpe.sbpc.botpcd.config + +import com.whatsapp.api.impl.WhatsappBusinessCloudApi +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary +import org.springframework.context.annotation.Profile +import ufrpe.sbpc.botpcd.WhatsappBusinessCloudApiMock + +@Configuration +@Profile("test") +class MockConfig { + @Bean + fun whatsappMock(): WhatsappBusinessCloudApi { + return WhatsappBusinessCloudApiMock("fakeToken") + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..f72463b --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,9 @@ +spring.application.name=BotPCD +spring.datasource.url=jdbc\:h2\:mem\:testdb\;DB_CLOSE_ON_EXIT\=FALSE\;DB_CLOSE_DELAY\=-1 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=root +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +whatsapp.api.token=teste +whatsapp.verify.token=teste \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/mocks/unknow-api-call.json b/src/test/resources/ufrpe/sbpc/botpcd/mocks/unknow-api-call.json new file mode 100644 index 0000000..a9a6b2f --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/mocks/unknow-api-call.json @@ -0,0 +1,40 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "1944143699658447", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15556557522", + "phone_number_id": "610886818782897" + }, + "statuses": [ + { + "id": "wamid.HBgMNTU4MTg4MTU3OTQ4FQIAERgSQUVCRkM2Qzk4NzJFMDQxN0ExAA==", + "status": "sent", + "timestamp": "1748703539", + "recipient_id": "558112345678", + "conversation": { + "id": "08ac58a1276c34530b921d13182bcb6e", + "expiration_timestamp": "1748735580", + "origin": { + "type": "service" + } + }, + "pricing": { + "billable": true, + "pricing_model": "CBP", + "category": "service" + } + } + ] + }, + "field": "messages" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/mocks/user-send-a-number.json b/src/test/resources/ufrpe/sbpc/botpcd/mocks/user-send-a-number.json new file mode 100644 index 0000000..1a25fff --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/mocks/user-send-a-number.json @@ -0,0 +1,39 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "1944143699658447", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15556557522", + "phone_number_id": "610886818782897" + }, + "contacts": [ + { + "profile": { + "name": "joazinho da terra" + }, + "wa_id": "558112345678" + } + ], + "messages": [ + { + "from": "558112345678", + "id": "wamid.jfiajdsijfiqbeufbausdf", + "timestamp": "1748715839", + "text": { + "body": "1" + }, + "type": "text" + } + ] + }, + "field": "messages" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-oi.json b/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-oi.json new file mode 100644 index 0000000..69ea2c0 --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-oi.json @@ -0,0 +1,39 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "1944143699658447", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15556557522", + "phone_number_id": "610886818782897" + }, + "contacts": [ + { + "profile": { + "name": "joazinho da terra" + }, + "wa_id": "558112345678" + } + ], + "messages": [ + { + "from": "558112345678", + "id": "wamid.Hjjhjbbiunub", + "timestamp": "1748701962", + "text": { + "body": "Oi" + }, + "type": "text" + } + ] + }, + "field": "messages" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-seu-nome.json b/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-seu-nome.json new file mode 100644 index 0000000..1abd245 --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/mocks/usuario-manda-seu-nome.json @@ -0,0 +1,39 @@ +{ + "object": "whatsapp_business_account", + "entry": [ + { + "id": "1944143699658447", + "changes": [ + { + "value": { + "messaging_product": "whatsapp", + "metadata": { + "display_phone_number": "15556557522", + "phone_number_id": "610886818782897" + }, + "contacts": [ + { + "profile": { + "name": "Joaozinho da terra" + }, + "wa_id": "558112345678" + } + ], + "messages": [ + { + "from": "558112345678", + "id": "wamidohutydrtctg", + "timestamp": "1748717220", + "text": { + "body": "joazinho da terra" + }, + "type": "text" + } + ] + }, + "field": "messages" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature b/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature new file mode 100644 index 0000000..531cd70 --- /dev/null +++ b/src/test/resources/ufrpe/sbpc/botpcd/registerPCD.feature @@ -0,0 +1,81 @@ +# language: pt +Funcionalidade: Cadastro do PCD + Como um PCD, quero poder me cadastrar para acessar os serviços da SBPC com mais facilidade. + + Cenario: Usuário não cadastrado manda qualquer mensagem + Dado usuário não cadastrado + Quando usuário envia mensagem "Oi" + Entao usuário receberá mensagem + """ +Olá, qual sua deficiência? +- Digite 1 para Deficiência visual +- Digite 2 para Deficiência auditiva/surdez +- Digite 3 para Surdocegueira +- Digite 4 para Transtorno do Espectro Autista/Neurodivergente +- Digite 5 para Deficiência física +- Digite 6 para Não tenho deficiência, mas tenho mobilidade reduzida +- Digite 7 para Não preciso de suporte. +""" + + Regra: Apenas Usuarios com deficiência podem ser cadastrados no sistema + Cenário de Fundo: Dado usuário não cadastrado + E usuário recebeu mensagem + """ +Olá, qual sua deficiência? +- Digite 1 para Deficiência visual +- Digite 2 para Deficiência auditiva/surdez +- Digite 3 para Surdocegueira +- Digite 4 para Transtorno do Espectro Autista/Neurodivergente +- Digite 5 para Deficiência física +- Digite 6 para Não tenho deficiência, mas tenho mobilidade reduzida +- Digite 7 para Não preciso de suporte. +""" + + Cenario: O usuário responde que não precisa de suporte + Quando usuário envia mensagem "7" + Então usuário receberá mensagem "Agradecemos o contato! Este canal é exclusivo para atendimento de pessoas com deficiência ou mobilidade reduzida que participarão do evento. Desejamos a você uma excelente participação na 77ª Reunião Anual da SBPC." + + Cenario: + Quando usuário envia mensagem "10" + Entao usuário receberá mensagem + """ +Olá, qual sua deficiência? +- Digite 1 para Deficiência visual +- Digite 2 para Deficiência auditiva/surdez +- Digite 3 para Surdocegueira +- Digite 4 para Transtorno do Espectro Autista/Neurodivergente +- Digite 5 para Deficiência física +- Digite 6 para Não tenho deficiência, mas tenho mobilidade reduzida +- Digite 7 para Não preciso de suporte. +""" + Esquema do Cenario: O usuário responde que tem deficiência + Dado usuário recebeu mensagem + """ +Olá, qual sua deficiência? +- Digite 1 para Deficiência visual +- Digite 2 para Deficiência auditiva/surdez +- Digite 3 para Surdocegueira +- Digite 4 para Transtorno do Espectro Autista/Neurodivergente +- Digite 5 para Deficiência física +- Digite 6 para Não tenho deficiência, mas tenho mobilidade reduzida +- Digite 7 para Não preciso de suporte. +""" + Quando usuário envia mensagem "" + Então bot registrará o usuário com deficiencia "" + E usuário receberá mensagem "Qual o seu nome?" + + Exemplos: + | numero_da_deficiencia | tipo_deficiencia | + | 1 | Deficiência visual | + | 2 | Deficiência auditiva/surdez | + | 3 | Surdocegueira | + | 4 | Transtorno do Espectro Autista/Neurodivergente | + | 5 | Deficiência física | + | 6 | Não tenho deficiência, mas tenho mobilidade reduzida | + + Cenario: O usuário responde com o seu nome + Dado usuário recebeu mensagem "Qual o seu nome?" + E usuário possui deficiência cadastrada + Quando usuário envia mensagem "João Victor" + Entao usuário receberá mensagem "Cadastro realizado." + E bot salvará o nome do usuário "João Victor"