From 3fc6d65cce682b3a2cc4e04c44531a1b3b73a272 Mon Sep 17 00:00:00 2001 From: benzuzu Date: Mon, 24 Jul 2023 21:36:44 -0400 Subject: [PATCH] Add Integration Tests --- .../oncokb/config/SlackConfiguration.java | 14 + .../oncokb/domain/enumeration/MailType.java | 3 +- .../cbio/oncokb/service/SlackService.java | 15 +- .../cbio/oncokb/web/rest/SlackController.java | 7 +- .../cbio/oncokb/service/SlackServiceIT.java | 130 +++++++++ .../oncokb/web/rest/SlackControllerIT.java | 257 ++++++++++++++++++ .../templates/mail/approvalEmailString.txt | 1 + 7 files changed, 412 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/mskcc/cbio/oncokb/config/SlackConfiguration.java create mode 100644 src/test/java/org/mskcc/cbio/oncokb/service/SlackServiceIT.java create mode 100644 src/test/java/org/mskcc/cbio/oncokb/web/rest/SlackControllerIT.java create mode 100644 src/test/resources/templates/mail/approvalEmailString.txt diff --git a/src/main/java/org/mskcc/cbio/oncokb/config/SlackConfiguration.java b/src/main/java/org/mskcc/cbio/oncokb/config/SlackConfiguration.java new file mode 100644 index 000000000..e75c04789 --- /dev/null +++ b/src/main/java/org/mskcc/cbio/oncokb/config/SlackConfiguration.java @@ -0,0 +1,14 @@ +package org.mskcc.cbio.oncokb.config; + +import com.slack.api.Slack; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SlackConfiguration { + + @Bean + public Slack slack() { + return Slack.getInstance(); + } +} diff --git a/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/MailType.java b/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/MailType.java index ccd94e38e..4e4ce823f 100644 --- a/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/MailType.java +++ b/src/main/java/org/mskcc/cbio/oncokb/domain/enumeration/MailType.java @@ -17,7 +17,8 @@ public enum MailType { , APPROVAL(new MailTypeBuilder() .templateName("approvalEmail") .description("User Approval") - .titleKey("email.approval.title")) + .titleKey("email.approval.title") + .stringTemplateName("approvalEmailString.txt")) , APPROVAL_ALIGN_LICENSE_WITH_COMPANY(new MailTypeBuilder() .templateName("approvalAlignLicenseWithCompanyEmail") .description("Autocorrect user license to company license") diff --git a/src/main/java/org/mskcc/cbio/oncokb/service/SlackService.java b/src/main/java/org/mskcc/cbio/oncokb/service/SlackService.java index a62af47c1..a492a739c 100644 --- a/src/main/java/org/mskcc/cbio/oncokb/service/SlackService.java +++ b/src/main/java/org/mskcc/cbio/oncokb/service/SlackService.java @@ -33,7 +33,6 @@ import org.mskcc.cbio.oncokb.web.rest.slack.DropdownEmailOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.thymeleaf.context.Context; @@ -70,15 +69,16 @@ public class SlackService { private final MailService mailService; private final EmailService emailService; private final UserMailsService userMailsService; + private final UserMapper userMapper; + private final Slack slack; - @Autowired - private UserMapper userMapper; - - public SlackService(ApplicationProperties applicationProperties, MailService mailService, EmailService emailService, UserMailsService userMailsService) { + public SlackService(ApplicationProperties applicationProperties, MailService mailService, EmailService emailService, UserMailsService userMailsService, UserMapper userMapper, Slack slack) { this.applicationProperties = applicationProperties; this.mailService = mailService; this.emailService = emailService; this.userMailsService = userMailsService; + this.userMapper = userMapper; + this.slack = slack; } @Async @@ -119,7 +119,6 @@ public void sendApprovedConfirmation(UserDTO userDTO, Company company) { .text(text) .build(); - Slack slack = Slack.getInstance(); try { // This is an automatic message when user from whitelist is registered. WebhookResponse response = slack.send(this.applicationProperties.getSlack().getUserRegistrationWebhook(), payload); @@ -135,7 +134,6 @@ public void sendConfirmationOnUserAcceptsTrialAgreement(UserDTO userDTO, Instant .text(userDTO.getEmail() + " has read and agreed to the trial license agreement. The account has been activated, the trial period ends on " + expirationDate) .build(); - Slack slack = Slack.getInstance(); try { WebhookResponse response = slack.send(this.applicationProperties.getSlack().getUserRegistrationWebhook(), payload); @@ -158,7 +156,6 @@ public void sendConfirmationOnUserAcceptsTrialAgreement(UserDTO userDTO, Instant public List getAllUnapprovedUserRequestsSentAfter(int daysAgo) { List userList = new ArrayList<>(); - Slack slack = Slack.getInstance(); final String REQUEST_MESSAGE_TEXT = "This content can't be displayed."; try { @@ -228,7 +225,6 @@ private void sendBlocks(String url, List layoutBlocks) { .blocks(layoutBlocks) .build(); - Slack slack = Slack.getInstance(); try { WebhookResponse response = slack.send(url, payload); log.info("Send the latest user blocks to slack with response code " + response.getCode()); @@ -677,7 +673,6 @@ private void sendModal(String triggerId, View view) { .view(view) .build(); - Slack slack = Slack.getInstance(); try { ViewsOpenResponse response = slack.methods().viewsOpen(request); if (!response.isOk()) { diff --git a/src/main/java/org/mskcc/cbio/oncokb/web/rest/SlackController.java b/src/main/java/org/mskcc/cbio/oncokb/web/rest/SlackController.java index 8cac08c93..ecfef90cf 100644 --- a/src/main/java/org/mskcc/cbio/oncokb/web/rest/SlackController.java +++ b/src/main/java/org/mskcc/cbio/oncokb/web/rest/SlackController.java @@ -20,7 +20,6 @@ import org.mskcc.cbio.oncokb.web.rest.slack.DropdownEmailOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -40,18 +39,18 @@ public class SlackController { private final UserService userService; - @Autowired - private UserMapper userMapper; + private final UserMapper userMapper; private final UserRepository userRepository; private final MailService mailService; - public SlackController(UserService userService, UserRepository userRepository, MailService mailService, SlackService slackService) { + public SlackController(UserService userService, UserRepository userRepository, MailService mailService, SlackService slackService, UserMapper userMapper) { this.userService = userService; this.userRepository = userRepository; this.mailService = mailService; this.slackService = slackService; + this.userMapper = userMapper; } // We do not put any auth protection for the slack call diff --git a/src/test/java/org/mskcc/cbio/oncokb/service/SlackServiceIT.java b/src/test/java/org/mskcc/cbio/oncokb/service/SlackServiceIT.java new file mode 100644 index 000000000..2af32f6ea --- /dev/null +++ b/src/test/java/org/mskcc/cbio/oncokb/service/SlackServiceIT.java @@ -0,0 +1,130 @@ +package org.mskcc.cbio.oncokb.service; + +import com.slack.api.Slack; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.block.SectionBlock; +import com.slack.api.model.block.composition.TextObject; +import com.slack.api.webhook.Payload; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mskcc.cbio.oncokb.OncokbPublicApp; +import org.mskcc.cbio.oncokb.config.application.ApplicationProperties; +import org.mskcc.cbio.oncokb.config.application.SlackProperties; +import org.mskcc.cbio.oncokb.domain.Company; +import org.mskcc.cbio.oncokb.domain.enumeration.LicenseStatus; +import org.mskcc.cbio.oncokb.domain.enumeration.LicenseType; +import org.mskcc.cbio.oncokb.service.dto.UserDTO; +import org.mskcc.cbio.oncokb.service.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SpringBootTest(classes = OncokbPublicApp.class) +class SlackServiceIT { + + // Testing Slack url for applicationProperties + private static final String USER_REGISTRATION_WEBHOOK = "https://hooks.slack.com/example"; + + @Autowired + private ApplicationProperties applicationProperties; + + @Autowired + private MailService mailService; + + @Autowired + private EmailService emailService; + + @Autowired + private UserMailsService userMailsService; + + @Autowired + private UserMapper userMapper; + + @Spy + private Slack slack; + + @Captor + private ArgumentCaptor urlCaptor; + + @Captor + private ArgumentCaptor payloadCaptor; + + private SlackService slackService; + + @BeforeEach + public void setup() throws IOException { + MockitoAnnotations.initMocks(this); + doReturn(null).when(slack).send(any(String.class), any(Payload.class)); + + // Specify webhook address for testing + applicationProperties.setSlack(new SlackProperties()); + applicationProperties.getSlack().setUserRegistrationWebhook(USER_REGISTRATION_WEBHOOK); + + slackService = new SlackService(applicationProperties, mailService, emailService, userMailsService, userMapper, slack); + } + + @Test + void testSendUserRegistrationToChannel() throws IOException { + // Create mock user + UserDTO user = new UserDTO(); + user.setId(0L); + user.setEmail("john.doe@example.com"); + user.setFirstName("john"); + user.setLastName("doe"); + user.setJobTitle("job title"); + user.setCompanyName("company name"); + user.setCity("city"); + user.setCountry("country"); + user.setLicenseType(LicenseType.COMMERCIAL); + Company company = new Company(); + company.setName("company name"); + company.setLicenseType(LicenseType.COMMERCIAL); + company.setLicenseStatus(LicenseStatus.UNKNOWN); + + // Mock send Slack blocks + slackService.sendUserRegistrationToChannel(user, false, company); + verify(slack).send(urlCaptor.capture(), payloadCaptor.capture()); + String url = urlCaptor.getValue(); + Payload payload = payloadCaptor.getValue(); + + // Ensure url is correct + assertThat(url).isEqualTo(USER_REGISTRATION_WEBHOOK); + + // Ensure all crucial information is in payload + Set expectedValues = new HashSet<>(); + expectedValues.add(LicenseType.COMMERCIAL.getName()); + expectedValues.add("Not Activated"); + expectedValues.add("REGULAR"); + expectedValues.add("john.doe@example.com"); + expectedValues.add("john doe"); + expectedValues.add("job title"); + expectedValues.add("company name"); + expectedValues.add("city"); + expectedValues.add("country"); + for (LayoutBlock block : payload.getBlocks()) { + if (block instanceof SectionBlock) { + SectionBlock sectionBlock = (SectionBlock) block; + if (sectionBlock.getText() != null) { + expectedValues.removeIf(sectionBlock.getText().getText()::contains); + } + if (sectionBlock.getFields() != null) { + for (TextObject textObject : sectionBlock.getFields()) { + expectedValues.removeIf(textObject.getText()::contains); + } + } + } + } + assertThat(expectedValues).isEmpty(); + } +} diff --git a/src/test/java/org/mskcc/cbio/oncokb/web/rest/SlackControllerIT.java b/src/test/java/org/mskcc/cbio/oncokb/web/rest/SlackControllerIT.java new file mode 100644 index 000000000..8e2312d12 --- /dev/null +++ b/src/test/java/org/mskcc/cbio/oncokb/web/rest/SlackControllerIT.java @@ -0,0 +1,257 @@ +package org.mskcc.cbio.oncokb.web.rest; + +import com.google.gson.Gson; +import com.slack.api.Slack; +import com.slack.api.app_backend.interactive_components.payload.BlockActionPayload; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.block.SectionBlock; +import com.slack.api.model.block.composition.TextObject; +import com.slack.api.util.json.GsonFactory; +import com.slack.api.webhook.Payload; +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.security.RandomUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mskcc.cbio.oncokb.OncokbPublicApp; +import org.mskcc.cbio.oncokb.config.application.ApplicationProperties; +import org.mskcc.cbio.oncokb.config.application.EmailAddresses; +import org.mskcc.cbio.oncokb.config.application.SlackProperties; +import org.mskcc.cbio.oncokb.domain.Token; +import org.mskcc.cbio.oncokb.domain.User; +import org.mskcc.cbio.oncokb.domain.UserDetails; +import org.mskcc.cbio.oncokb.domain.enumeration.LicenseType; +import org.mskcc.cbio.oncokb.domain.enumeration.MailType; +import org.mskcc.cbio.oncokb.repository.UserDetailsRepository; +import org.mskcc.cbio.oncokb.repository.UserRepository; +import org.mskcc.cbio.oncokb.service.*; +import org.mskcc.cbio.oncokb.service.impl.TokenServiceImpl; +import org.mskcc.cbio.oncokb.service.mapper.UserMapper; +import org.mskcc.cbio.oncokb.web.rest.slack.ActionId; +import org.mskcc.cbio.oncokb.web.rest.slack.BlockId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.MessageSource; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.thymeleaf.spring5.SpringTemplateEngine; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.*; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mskcc.cbio.oncokb.config.Constants.DEFAULT_TOKEN_EXPIRATION_IN_SECONDS; + +@SpringBootTest(classes = OncokbPublicApp.class) +public class SlackControllerIT { + + // Testing Slack url and email addresses for applicationProperties + private static final String USER_REGISTRATION_WEBHOOK = "https://hooks.slack.com/example"; + private static final String LICENSE_ADDR = "license@example.com"; + private static final String REGISTRATION_ADDR = "registration@example.com"; + private static final String CONTACT_ADDR = "contact@example.com"; + private static final String TECH_DEV_ADDR = "dev@example.com"; + private static final String DEFAULT_ADDR = "default@example.com"; + + @Autowired + private ApplicationProperties applicationProperties; + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + private MailService mailService; + @Autowired + private JHipsterProperties jHipsterProperties; + @Autowired + private MessageSource messageSource; + @Autowired + private SpringTemplateEngine templateEngine; + @Autowired + private UserMailsService userMailsService; + + private SlackService slackService; + @Autowired + private EmailService emailService; + + @Autowired + private UserMapper userMapper; + + @Spy + private Slack slack; + + @Captor + private ArgumentCaptor urlCaptor; + + @Captor + private ArgumentCaptor payloadCaptor; + + @Spy + private JavaMailSenderImpl javaMailSender; + + @Captor + private ArgumentCaptor messageCaptor; + + @Autowired + private TokenServiceImpl tokenService; + + @Autowired + private UserDetailsRepository userDetailsRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + private SlackController slackController; + + @BeforeEach + void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + doReturn(null).when(slack).send(urlCaptor.capture(), payloadCaptor.capture()); + doNothing().when(javaMailSender).send(messageCaptor.capture()); + + // Specify webhook address for testing + applicationProperties.setSlack(new SlackProperties()); + applicationProperties.getSlack().setUserRegistrationWebhook(USER_REGISTRATION_WEBHOOK); + + // specify application emails for testing + applicationProperties.setEmailAddresses(new EmailAddresses()); + applicationProperties.getEmailAddresses().setLicenseAddress(LICENSE_ADDR); + applicationProperties.getEmailAddresses().setContactAddress(CONTACT_ADDR); + applicationProperties.getEmailAddresses().setRegistrationAddress(REGISTRATION_ADDR); + applicationProperties.getEmailAddresses().setTechDevAddress(TECH_DEV_ADDR); + jHipsterProperties.getMail().setFrom(DEFAULT_ADDR); + + mailService = new MailService(jHipsterProperties, javaMailSender, messageSource, templateEngine, userMailsService, applicationProperties); + slackService = new SlackService(applicationProperties, mailService, emailService, userMailsService, userMapper, slack); + slackController = new SlackController(userService, userRepository, mailService, slackService, userMapper); + } + + @Test + void testApproveUser() throws IOException, MessagingException { + // Create mock user + User mockUser = new User(); + mockUser.setLogin("john.doe@example.com"); + mockUser.setEmail("john.doe@example.com"); + mockUser.setPassword(passwordEncoder.encode(RandomUtil.generatePassword())); + mockUser.setLangKey("en"); + mockUser.setActivated(false); + userRepository.save(mockUser); + + // Add mock user details + UserDetails mockUserDetails = new UserDetails().user(mockUser); + mockUserDetails.setCompanyName("company name"); + mockUserDetails.setLicenseType(LicenseType.COMMERCIAL); + userDetailsRepository.save(mockUserDetails); + + // Mock payload + BlockActionPayload actionJSON = new BlockActionPayload(); + List actions = new ArrayList<>(); + BlockActionPayload.Action approveAction = new BlockActionPayload.Action(); + approveAction.setActionId(ActionId.APPROVE_USER.getId()); + approveAction.setValue("john.doe@example.com"); + actions.add(approveAction); + actionJSON.setActions(actions); + actionJSON.setResponseUrl(USER_REGISTRATION_WEBHOOK); + actionJSON.setToken("token"); + + // Mock approve user + Gson snakeCase = GsonFactory.createSnakeCase(); + slackController.approveUser(snakeCase.toJson(actionJSON)); + MimeMessage message = messageCaptor.getValue(); + String url = urlCaptor.getValue(); + Payload payload = payloadCaptor.getValue(); + + // Check mail integration + assertThat(message.getSubject()).isEqualTo(messageSource.getMessage(MailType.APPROVAL.getTitleKey(), new Object[]{}, Locale.forLanguageTag(mockUser.getLangKey()))); + assertThat(message.getAllRecipients()[0].toString()).isEqualTo("john.doe@example.com"); + assertThat(message.getFrom()[0].toString()).isEqualTo(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(String.class); + assertThat(message.getContent().toString()).contains(getStringFromResourceTemplateMailTextFile(MailType.APPROVAL.getStringTemplateName().get()).trim()); + + // Check Slack integration + assertThat(url).isEqualTo(USER_REGISTRATION_WEBHOOK); + Set expectedValues = new HashSet<>(); + expectedValues.add(LicenseType.COMMERCIAL.getName()); + expectedValues.add("john.doe@example.com"); + expectedValues.add("company name"); + expectedValues.add(LicenseType.COMMERCIAL.getName()); + expectedValues.add(BlockId.COLLAPSED.getId()); + for (LayoutBlock block : payload.getBlocks()) { + if (block instanceof SectionBlock) { + SectionBlock sectionBlock = (SectionBlock) block; + if (sectionBlock.getText() != null) { + expectedValues.removeIf(sectionBlock.getText().getText()::contains); + } + if (sectionBlock.getFields() != null) { + for (TextObject textObject : sectionBlock.getFields()) { + expectedValues.removeIf(textObject.getText()::contains); + } + } + if (sectionBlock.getBlockId() != null) { + expectedValues.removeIf(sectionBlock.getBlockId()::contains); + } + } + } + assertThat(expectedValues).isEmpty(); + + // Check user approval + mockUser = userRepository.findOneWithAuthoritiesByLogin("john.doe@example.com").orElse(null); + assertThat(mockUser).isNotNull(); + assertThat(mockUser.getActivated()).isTrue(); + List mockTokens = tokenService.findByUser(mockUser); + assertThat(mockTokens).isNotEmpty(); + Token mockToken = mockTokens.get(0); + assertThat(mockToken.getExpiration()).isBefore(Instant.now().plusSeconds(DEFAULT_TOKEN_EXPIRATION_IN_SECONDS + 1)); + assertThat(mockToken.isRenewable()).isTrue(); + assertThat(userMailsService.findUserMailsByUserAndMailTypeAndSentDateAfter(mockUser, MailType.APPROVAL, null)).isNotEmpty(); + } + + @Test + void testGiveTrialAccess() throws IOException { + + } + + @Test + void testChangeLicenseType() throws IOException { + + } + + @Test + void testConvertToRegularAccount() throws IOException { + + } + + @Test + void testDropdownEmailOptions() throws IOException { + + } + + private String getStringFromResourceTemplateMailTextFile(String fileName) throws IOException { + StringBuilder sb = new StringBuilder(); + + URL targetFileUrl = getClass().getClassLoader().getResource("templates/mail/" + fileName); + if (targetFileUrl != null) { + try (Stream stream = Files.lines(Paths.get(targetFileUrl.getPath()), StandardCharsets.UTF_8)) { + stream.forEach(s -> sb.append(s).append("\n")); + } + } + + return sb.toString(); + } +} diff --git a/src/test/resources/templates/mail/approvalEmailString.txt b/src/test/resources/templates/mail/approvalEmailString.txt new file mode 100644 index 000000000..87765798b --- /dev/null +++ b/src/test/resources/templates/mail/approvalEmailString.txt @@ -0,0 +1 @@ +Your OncoKB account has been approved. Please click on the URL below to log in: