Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -41,14 +42,17 @@
* Service responsible for handling all email communications in the Visitor Management System.
*/
@Service
@Profile({"prod", "default", "sqlite"})
@Profile({"prod", "default", "sqlite", "test"})
public class GraphEmailService implements EmailService {

private static final Logger logger = LoggerFactory.getLogger(GraphEmailService.class);

private final RestClient restClient;
private final OAuth2AuthorizedClientManager authorizedClientManager;

@Value("${graph.api.base-url}")
private String graphApiBaseUrl;

@Autowired
public GraphEmailService(OAuth2AuthorizedClientManager authorizedClientManager,
RestClient restClient) {
Expand All @@ -72,7 +76,7 @@ public String getAccessToken() {
@Override
public boolean sendEmail(Email email) {
String accessToken = getAccessToken();
String endpointUsers = String.format("https://graph.microsoft.com/v1.0/users/%s/sendMail", email.from());
String endpointUsers = String.format("%s/users/%s/sendMail", graphApiBaseUrl, email.from());

Map<String, Object> emailData = new HashMap<>();
Map<String, Object> message = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ public class NotificationService {

private final EmailService emailService;
private final TemplateEngine templateEngine;
private final String systemFrom;

// Use the env-backed VMS_SYSTEM_EMAIL (fallback to vms.system-email or default noreply)
@Value("${VMS_SYSTEM_EMAIL:${vms.system-email:noreply@company.com}}")
private String systemFrom;

public NotificationService(EmailService emailService, TemplateEngine templateEngine) {
public NotificationService(
EmailService emailService,
TemplateEngine templateEngine,
@Value("${VMS_SYSTEM_EMAIL:${vms.system-email:noreply@company.com}}") String systemFrom
) {
this.emailService = emailService;
this.templateEngine = templateEngine;
this.systemFrom = systemFrom;
}

public void sendVisitorConfirmationEmail(Visitor visitor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public OtpService(OtpRepository otpRepository, EmailService emailService,
this.systemFrom = systemFrom;
}

private static final int OTP_EXPIRATION_MINUTES = 10;
private static final int OTP_EXPIRATION_MINUTES = 3;
private static final int MAX_OTP_ATTEMPTS = 2;
private static final int MAX_RESEND_COUNT = 2;
private static final int RESEND_COOLDOWN_MINUTES = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,23 @@
import org.springframework.stereotype.Component;

@Component
@Profile("prod")
@Profile({"prod","test"})
public class ScheduledTasks {

private final ExcelService excelService;
private final GraphDirectoryService graphDirectoryService;

private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);


public ScheduledTasks(ExcelService excelService, GraphDirectoryService graphDirectoryService) {
this.excelService = excelService;
this.graphDirectoryService = graphDirectoryService;
}

@Scheduled(
fixedRateString = "${vms.scheduled.report.rate:43200000}",
initialDelayString = "${vms.scheduled.report.initialDelay:PT2H}"
)
@Scheduled(fixedRateString = "${vms.scheduled.report.rate:43200000}", initialDelayString = "PT2H") // Runs every 12 hours by default
public void sendVisitorReport() {
excelService.sendVisitorReport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,6 @@ public VisitService(OtpService otpService, VisitorRepository visitorRepository,
this.visitRepository = visitRepository;
}


// Send OTP
// notificationService.sendOtp(visitor.getEmail(), visitor.getOtp());
// }
//
// @Transactional
// public Visitor saveVisitor(Visitor visitor) {
// Visitor savedVisitor = visitorRepository.save(visitor);
// emailService.sendVisitorEmail(savedVisitor);
// otpService.sendOtp(savedVisitor.getEmail());
// return savedVisitor;
// }


// return otpService.sendOtp(visit.getVisitor().getEmail(), visit);
// }

/**
* Registers a new visit and generates an OTP for visitor verification.
* The OTP is associated with the visit and sent to the visitor's email.
Expand Down

This file was deleted.

9 changes: 4 additions & 5 deletions web-backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ vms:
sync:
onstartup:
enabled: false
wiremock:
server:
port: 8081
mail:
url: http://localhost:${wiremock.server.port}/v1.0/users/%s/sendMail

graph:
api:
base-url: https://graph.microsoft.com/v1.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;

@Import(TestcontainersConfiguration.class)
@SpringBootTest
@ActiveProfiles("test")
class VmsApplicationTests {

@Test
void contextLoads() {
}

}
18 changes: 18 additions & 0 deletions web-backend/src/test/java/com/statusneo/vms/config/TestConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.statusneo.vms.config;
import com.statusneo.vms.service.EmailService;
import com.statusneo.vms.service.GraphDirectoryService;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import static org.mockito.Mockito.mock;

@TestConfiguration
public class TestConfig {
@Bean
public GraphDirectoryService graphDirectoryService() {
return mock(GraphDirectoryService.class);
}
@Bean
public EmailService emailService() {
return mock(EmailService.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.statusneo.vms.service;

import com.statusneo.vms.TestcontainersConfiguration;
import com.statusneo.vms.model.Visitor;
import com.statusneo.vms.repository.VisitorRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
Expand All @@ -19,9 +18,11 @@
@SpringBootTest
@Disabled("Enable this test with real credentials and configuration for full integration testing.")
@ActiveProfiles("test")
@Import(TestcontainersConfiguration.class)
class ExcelServiceITest {

@MockitoBean
private EmailService emailService;

@Autowired
private ExcelService excelService;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.statusneo.vms.service;

import com.statusneo.vms.TestcontainersConfiguration;
import com.statusneo.vms.model.Email;
import com.statusneo.vms.model.Attachment;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;

import java.util.List;
Expand All @@ -16,7 +14,6 @@

@SpringBootTest
@ActiveProfiles("test")
@Import(TestcontainersConfiguration.class)
class GraphEmailServiceIntegrationTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestClient;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import com.statusneo.vms.model.Email;

import java.time.Instant;
import java.util.Map;


@ActiveProfiles("test")
class GraphEmailServiceTest {

@Mock
Expand All @@ -33,9 +42,23 @@ class GraphEmailServiceTest {
@InjectMocks
private GraphEmailService graphEmailService;

@Mock
private OAuth2AuthorizedClientManager authorizedClientManager;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
"dummy-token",
Instant.now(),
Instant.now().plusSeconds(3600)
);

OAuth2AuthorizedClient authorizedClient = mock(OAuth2AuthorizedClient.class);
when(authorizedClient.getAccessToken()).thenReturn(accessToken);
when(authorizedClientManager.authorize(any())).thenReturn(authorizedClient);
ReflectionTestUtils.setField(graphEmailService, "graphApiBaseUrl", "https://graph.microsoft.com/v1.0");
}

@Test
Expand All @@ -47,7 +70,6 @@ void testSendEmail_Success() {
String body = "Test Body";

ResponseEntity<Void> responseEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);

mockRestClientResponse(responseEntity);

// Act
Expand All @@ -68,7 +90,6 @@ void testSendEmail_Failure() {
String body = "Test Body";

ResponseEntity<Void> responseEntity = new ResponseEntity<>(HttpStatus.BAD_REQUEST);

mockRestClientResponse(responseEntity);

// Act
Expand All @@ -84,8 +105,24 @@ private void mockRestClientResponse(ResponseEntity<Void> responseEntity) {
when(restClient.post()).thenReturn(requestBodyUriSpec);
when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodySpec);
when(requestBodySpec.headers(any())).thenReturn(requestBodySpec);
when(requestBodySpec.body(any())).thenReturn(requestBodySpec);
when(requestBodySpec.body(any(Map.class))).thenReturn(requestBodySpec);
when(requestBodySpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.toBodilessEntity()).thenReturn(responseEntity);
}

@Test
void testSendEmail_AccessTokenFailure() {
when(authorizedClientManager.authorize(any())).thenReturn(null);

String fromEmail = "system-user@example.com";
String toEmail = "recipient@example.com";
String subject = "Test Subject";
String body = "Test Body";

RuntimeException exception = assertThrows(RuntimeException.class, () ->
graphEmailService.sendEmail(Email.of(fromEmail, toEmail, subject, body))
);

assertEquals("Failed to obtain access token", exception.getMessage());
}
}
Loading
Loading