Skip to content

Commit

Permalink
fix: send mail to opted in users for trial instance in ApiWorkflowSta…
Browse files Browse the repository at this point in the history
  • Loading branch information
ytvnr committed Mar 27, 2024
1 parent 35fac1e commit b4c480a
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import io.gravitee.rest.api.model.UserEntity;
import io.gravitee.rest.api.model.WorkflowReferenceType;
import io.gravitee.rest.api.model.WorkflowState;
import io.gravitee.rest.api.model.parameters.Key;
import io.gravitee.rest.api.model.parameters.ParameterReferenceType;
import io.gravitee.rest.api.model.permissions.ApiPermission;
import io.gravitee.rest.api.model.permissions.RolePermissionAction;
import io.gravitee.rest.api.model.permissions.RoleScope;
Expand All @@ -39,6 +41,7 @@
import io.gravitee.rest.api.service.EmailService;
import io.gravitee.rest.api.service.MembershipService;
import io.gravitee.rest.api.service.NotifierService;
import io.gravitee.rest.api.service.ParameterService;
import io.gravitee.rest.api.service.RoleService;
import io.gravitee.rest.api.service.UserService;
import io.gravitee.rest.api.service.WorkflowService;
Expand All @@ -55,6 +58,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
Expand All @@ -75,6 +79,7 @@ public class ApiWorkflowStateServiceImpl implements ApiWorkflowStateService {
private final MembershipService membershipService;
private final EmailService emailService;
private final ApiSearchService apiSearchService;
private final ParameterService parameterService;

public ApiWorkflowStateServiceImpl(
final AuditService auditService,
Expand All @@ -85,7 +90,8 @@ public ApiWorkflowStateServiceImpl(
final NotifierService notifierService,
final MembershipService membershipService,
final EmailService emailService,
final ApiSearchService apiSearchService
final ApiSearchService apiSearchService,
final ParameterService parameterService
) {
this.auditService = auditService;
this.apiMetadataService = apiMetadataService;
Expand All @@ -96,6 +102,7 @@ public ApiWorkflowStateServiceImpl(
this.membershipService = membershipService;
this.emailService = emailService;
this.apiSearchService = apiSearchService;
this.parameterService = parameterService;
}

@Override
Expand Down Expand Up @@ -164,14 +171,16 @@ private GenericApiEntity updateWorkflowReview(
// Find all reviewers of the API and send them a notification email
if (hook.equals(ApiHook.ASK_FOR_REVIEW)) {
List<String> reviewersEmail = findAllReviewersEmail(executionContext, genericApiEntity);
this.emailService.sendAsyncEmailNotification(
executionContext,
new EmailNotificationBuilder()
.params(new NotificationParamsBuilder().api(genericApiEntity).user(user).build())
.to(reviewersEmail.toArray(new String[reviewersEmail.size()]))
.template(EmailNotificationBuilder.EmailTemplate.API_ASK_FOR_REVIEW)
.build()
);
if (reviewersEmail.size() > 0) {
this.emailService.sendAsyncEmailNotification(
executionContext,
new EmailNotificationBuilder()
.params(new NotificationParamsBuilder().api(genericApiEntity).user(user).build())
.to(reviewersEmail.toArray(new String[reviewersEmail.size()]))
.template(EmailNotificationBuilder.EmailTemplate.API_ASK_FOR_REVIEW)
.build()
);
}
}

Map<Audit.AuditProperties, String> properties = new HashMap<>();
Expand All @@ -197,6 +206,8 @@ private GenericApiEntity updateWorkflowReview(

private List<String> findAllReviewersEmail(ExecutionContext executionContext, GenericApiEntity genericApiEntity) {
final RolePermissionAction[] acls = { RolePermissionAction.UPDATE };
final boolean isTrialInstance = parameterService.findAsBoolean(executionContext, Key.TRIAL_INSTANCE, ParameterReferenceType.SYSTEM);
final Predicate<UserEntity> excludeIfTrialAndNotOptedIn = userEntity -> !isTrialInstance || userEntity.optedIn();

// find direct members of the API
Set<String> reviewerEmails = roleService
Expand All @@ -211,6 +222,7 @@ private List<String> findAllReviewersEmail(ExecutionContext executionContext, Ge
.map(MembershipEntity::getMemberId)
.distinct()
.map(id -> this.userService.findById(executionContext, id))
.filter(excludeIfTrialAndNotOptedIn)
.map(UserEntity::getEmail)
.filter(Objects::nonNull)
.collect(toSet());
Expand All @@ -232,6 +244,7 @@ private List<String> findAllReviewersEmail(ExecutionContext executionContext, Ge
.map(MembershipEntity::getMemberId)
.distinct()
.map(id -> this.userService.findById(executionContext, id))
.filter(excludeIfTrialAndNotOptedIn)
.map(UserEntity::getEmail)
.filter(Objects::nonNull)
.collect(toSet())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,53 @@
package io.gravitee.rest.api.service.v4.impl;

import static io.gravitee.rest.api.model.WorkflowType.REVIEW;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.gravitee.repository.management.model.Workflow;
import io.gravitee.rest.api.model.MembershipEntity;
import io.gravitee.rest.api.model.MembershipMemberType;
import io.gravitee.rest.api.model.MembershipReferenceType;
import io.gravitee.rest.api.model.ReviewEntity;
import io.gravitee.rest.api.model.RoleEntity;
import io.gravitee.rest.api.model.UserEntity;
import io.gravitee.rest.api.model.WorkflowReferenceType;
import io.gravitee.rest.api.model.WorkflowState;
import io.gravitee.rest.api.model.api.ApiEntity;
import io.gravitee.rest.api.model.parameters.Key;
import io.gravitee.rest.api.model.parameters.ParameterReferenceType;
import io.gravitee.rest.api.model.permissions.ApiPermission;
import io.gravitee.rest.api.model.v4.api.GenericApiEntity;
import io.gravitee.rest.api.service.ApiMetadataService;
import io.gravitee.rest.api.service.AuditService;
import io.gravitee.rest.api.service.EmailNotification;
import io.gravitee.rest.api.service.EmailService;
import io.gravitee.rest.api.service.MembershipService;
import io.gravitee.rest.api.service.NotifierService;
import io.gravitee.rest.api.service.ParameterService;
import io.gravitee.rest.api.service.RoleService;
import io.gravitee.rest.api.service.UserService;
import io.gravitee.rest.api.service.WorkflowService;
import io.gravitee.rest.api.service.common.GraviteeContext;
import io.gravitee.rest.api.service.v4.ApiNotificationService;
import io.gravitee.rest.api.service.v4.ApiSearchService;
import io.gravitee.rest.api.service.v4.ApiWorkflowStateService;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.assertj.core.api.Assertions;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.core.Authentication;
Expand Down Expand Up @@ -92,6 +108,9 @@ public class ApiWorkflowStateServiceImplTest {
@Mock
private EmailService emailService;

@Mock
private ParameterService parameterService;

private ApiWorkflowStateService apiWorkflowStateService;

@AfterClass
Expand Down Expand Up @@ -122,7 +141,8 @@ public void setUp() {
notifierService,
membershipService,
emailService,
apiSearchService
apiSearchService,
parameterService
);
}

Expand Down Expand Up @@ -156,6 +176,122 @@ public void shouldAskForReview() {
verify(apiNotificationService, times(0)).triggerUpdateNotification(eq(GraviteeContext.getExecutionContext()), any(ApiEntity.class));
}

@Test
public void shouldAskForReviewAndSendMail() {
final GenericApiEntity genericApiEntity = new io.gravitee.rest.api.model.v4.api.ApiEntity();
genericApiEntity.setId(API_ID);

final ReviewEntity reviewEntity = new ReviewEntity();
reviewEntity.setMessage("Test Review msg");

when(workflowService.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage()))
.thenReturn(null);
when(apiSearchService.findGenericById(GraviteeContext.getExecutionContext(), API_ID)).thenReturn(genericApiEntity);
when(userService.findById(eq(GraviteeContext.getExecutionContext()), any()))
.thenReturn(UserEntity.builder().email("test@gio.gio").status("ACTIVE").password("password").build());
when(roleService.findByScope(any(), any())).thenReturn(List.of(new RoleEntity()));
when(roleService.hasPermission(any(), eq(ApiPermission.REVIEWS), any())).thenReturn(true);
when(membershipService.getMembershipsByReferenceAndRole(eq(MembershipReferenceType.API), eq(API_ID), any()))
.thenReturn(Set.of(MembershipEntity.builder().id(USER_ID).memberType(MembershipMemberType.USER).build()));
when(parameterService.findAsBoolean(any(), eq(Key.TRIAL_INSTANCE), eq(ParameterReferenceType.SYSTEM))).thenReturn(false);
apiWorkflowStateService.askForReview(GraviteeContext.getExecutionContext(), API_ID, USER_ID, reviewEntity);

verify(workflowService)
.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage());
verify(auditService)
.createApiAuditLog(
eq(GraviteeContext.getExecutionContext()),
argThat(apiId -> apiId.equals(API_ID)),
anyMap(),
argThat(evt -> Workflow.AuditEvent.API_REVIEW_ASKED.equals(evt)),
any(),
any(),
any()
);
verify(apiNotificationService, times(0)).triggerUpdateNotification(eq(GraviteeContext.getExecutionContext()), any(ApiEntity.class));
final ArgumentCaptor<EmailNotification> emailNotificationArgumentCaptor = ArgumentCaptor.forClass(EmailNotification.class);
verify(emailService)
.sendAsyncEmailNotification(eq(GraviteeContext.getExecutionContext()), emailNotificationArgumentCaptor.capture());
assertThat(emailNotificationArgumentCaptor.getValue())
.satisfies(emailNotification -> assertThat(emailNotification.getTo()).containsExactly("test@gio.gio"));
}

@Test
public void shouldAskForReviewAndSendMailForOptedInUserInTrialInstance() {
final GenericApiEntity genericApiEntity = new io.gravitee.rest.api.model.v4.api.ApiEntity();
genericApiEntity.setId(API_ID);

final ReviewEntity reviewEntity = new ReviewEntity();
reviewEntity.setMessage("Test Review msg");

when(workflowService.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage()))
.thenReturn(null);
when(apiSearchService.findGenericById(GraviteeContext.getExecutionContext(), API_ID)).thenReturn(genericApiEntity);
when(userService.findById(eq(GraviteeContext.getExecutionContext()), any()))
.thenReturn(UserEntity.builder().email("test@gio.gio").status("ACTIVE").password("password").build());
when(roleService.findByScope(any(), any())).thenReturn(List.of(new RoleEntity()));
when(roleService.hasPermission(any(), eq(ApiPermission.REVIEWS), any())).thenReturn(true);
when(membershipService.getMembershipsByReferenceAndRole(eq(MembershipReferenceType.API), eq(API_ID), any()))
.thenReturn(Set.of(MembershipEntity.builder().id(USER_ID).memberType(MembershipMemberType.USER).build()));
when(parameterService.findAsBoolean(any(), eq(Key.TRIAL_INSTANCE), eq(ParameterReferenceType.SYSTEM))).thenReturn(true);
apiWorkflowStateService.askForReview(GraviteeContext.getExecutionContext(), API_ID, USER_ID, reviewEntity);

verify(workflowService)
.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage());
verify(auditService)
.createApiAuditLog(
eq(GraviteeContext.getExecutionContext()),
argThat(apiId -> apiId.equals(API_ID)),
anyMap(),
argThat(evt -> Workflow.AuditEvent.API_REVIEW_ASKED.equals(evt)),
any(),
any(),
any()
);
verify(apiNotificationService, times(0)).triggerUpdateNotification(eq(GraviteeContext.getExecutionContext()), any(ApiEntity.class));
final ArgumentCaptor<EmailNotification> emailNotificationArgumentCaptor = ArgumentCaptor.forClass(EmailNotification.class);
verify(emailService)
.sendAsyncEmailNotification(eq(GraviteeContext.getExecutionContext()), emailNotificationArgumentCaptor.capture());
assertThat(emailNotificationArgumentCaptor.getValue())
.satisfies(emailNotification -> assertThat(emailNotification.getTo()).containsExactly("test@gio.gio"));
}

@Test
public void shouldAskForReviewAndNotSendMailForNonOptedInUserInTrialInstance() {
final GenericApiEntity genericApiEntity = new io.gravitee.rest.api.model.v4.api.ApiEntity();
genericApiEntity.setId(API_ID);

final ReviewEntity reviewEntity = new ReviewEntity();
reviewEntity.setMessage("Test Review msg");

when(workflowService.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage()))
.thenReturn(null);
when(apiSearchService.findGenericById(GraviteeContext.getExecutionContext(), API_ID)).thenReturn(genericApiEntity);
when(userService.findById(eq(GraviteeContext.getExecutionContext()), any()))
.thenReturn(UserEntity.builder().email("test@gio.gio").status("PENDING").password("password").build());
when(roleService.findByScope(any(), any())).thenReturn(List.of(new RoleEntity()));
when(roleService.hasPermission(any(), eq(ApiPermission.REVIEWS), any())).thenReturn(true);
when(membershipService.getMembershipsByReferenceAndRole(eq(MembershipReferenceType.API), eq(API_ID), any()))
.thenReturn(Set.of(MembershipEntity.builder().id(USER_ID).memberType(MembershipMemberType.USER).build()));
when(parameterService.findAsBoolean(any(), eq(Key.TRIAL_INSTANCE), eq(ParameterReferenceType.SYSTEM))).thenReturn(true);
apiWorkflowStateService.askForReview(GraviteeContext.getExecutionContext(), API_ID, USER_ID, reviewEntity);

verify(workflowService)
.create(WorkflowReferenceType.API, API_ID, REVIEW, USER_ID, WorkflowState.IN_REVIEW, reviewEntity.getMessage());
verify(auditService)
.createApiAuditLog(
eq(GraviteeContext.getExecutionContext()),
argThat(apiId -> apiId.equals(API_ID)),
anyMap(),
argThat(evt -> Workflow.AuditEvent.API_REVIEW_ASKED.equals(evt)),
any(),
any(),
any()
);
verify(apiNotificationService, times(0)).triggerUpdateNotification(eq(GraviteeContext.getExecutionContext()), any(ApiEntity.class));
verify(emailService, never()).sendAsyncEmailNotification(eq(GraviteeContext.getExecutionContext()), any());
}

@Test
public void shouldAskForReviewWithNoMessage() {
final GenericApiEntity genericApiEntity = new io.gravitee.rest.api.model.v4.api.ApiEntity();
Expand Down

0 comments on commit b4c480a

Please sign in to comment.