package com.hithomelabs.cftunnels.Services; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.hithomelabs.cftunnels.Config.CloudflareConfig; import com.hithomelabs.cftunnels.Entity.Tunnel; import com.hithomelabs.cftunnels.Exceptions.ExternalServiceException; import com.hithomelabs.cftunnels.Headers.AuthKeyEmailHeader; import com.hithomelabs.cftunnels.Models.Config; import com.hithomelabs.cftunnels.Models.Ingress; import com.hithomelabs.cftunnels.Models.TunnelResponse; import com.hithomelabs.cftunnels.Models.TunnelResult; import com.hithomelabs.cftunnels.Models.TunnelsResponse; import com.hithomelabs.cftunnels.Repositories.TunnelRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; import static com.hithomelabs.cftunnels.TestUtils.Util.getClassPathDataResource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CloudflareAPIServiceTest { @InjectMocks private CloudflareAPIService cloudflareAPIService; @Mock AuthKeyEmailHeader authKeyEmailHeader; @Mock private RestTemplate restTemplate; @Mock CloudflareConfig cloudflareConfig; @Mock TunnelRepository tunnelRepository; private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json"; private static final String bigTunnelResponse; static { try { bigTunnelResponse = getClassPathDataResource(tunnelResponseLargeIngressFile); } catch (IOException e) { throw new RuntimeException(e); } } @Test void testGetCloudflareTunnels() { when(cloudflareConfig.getAccountId()).thenReturn("account-123"); when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); List tunnelResults = List.of(new TunnelResult("t1", "test-tunnel")); TunnelsResponse mockBody = new TunnelsResponse(tunnelResults, null, null, true); ResponseEntity mockResponse = new ResponseEntity<>(mockBody, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelsResponse.class) )).thenReturn(mockResponse); ResponseEntity response = cloudflareAPIService.getCloudflareTunnels(); assertEquals(HttpStatus.OK, response.getStatusCode()); } @Test void getCloudflareTunnelConfigurations() throws JsonProcessingException { when(cloudflareConfig.getAccountId()).thenReturn("account-123"); when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); TunnelResponse tunnelResponse = new ObjectMapper().readValue(bigTunnelResponse, TunnelResponse.class); ResponseEntity tunnelResponseResponseEntity = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelResponse.class) )).thenReturn(tunnelResponseResponseEntity); ResponseEntity response = cloudflareAPIService.getCloudflareTunnelConfigurations("sampleTunnelID", restTemplate, TunnelResponse.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(response.getBody().getResult().getConfig().getIngress().get(0).getHostname(), "giteabkp.hithomelabs.com"); } @Test void putCloudflareTunnelConfigurations() throws JsonProcessingException { when(cloudflareConfig.getAccountId()).thenReturn("account-123"); when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); TunnelResponse tunnelResponse = new ObjectMapper().readValue(bigTunnelResponse, TunnelResponse.class); ResponseEntity tunnelResponseResponseEntity = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); Config config = tunnelResponse.getResult().getConfig(); when(restTemplate.exchange( any(String.class), eq(HttpMethod.PUT), any(HttpEntity.class), eq(TunnelResponse.class) )).thenReturn(tunnelResponseResponseEntity); ResponseEntity response = cloudflareAPIService.putCloudflareTunnelConfigurations("sampleTunnelID", restTemplate, TunnelResponse.class, config); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(response.getBody().getResult().getConfig().getIngress().get(2).getHostname(), "random.hithomelabs.com"); } @Test void createOrUpdateTunnel_Success() { String tunnelId = "50df9101-f625-4618-b7c5-100338a57124"; String environment = "dev"; List tunnelResults = List.of(new TunnelResult(tunnelId, "devtunnel")); TunnelsResponse mockBody = new TunnelsResponse(tunnelResults, null, null, true); ResponseEntity mockResponse = new ResponseEntity<>(mockBody, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelsResponse.class) )).thenReturn(mockResponse); when(tunnelRepository.findById(UUID.fromString(tunnelId))).thenReturn(Optional.empty()); Tunnel result = cloudflareAPIService.createOrUpdateTunnel(tunnelId, environment); assertEquals(UUID.fromString(tunnelId), result.getId()); assertEquals("devtunnel", result.getName()); assertEquals(environment, result.getEnvironment()); verify(tunnelRepository).save(any(Tunnel.class)); } @Test void createOrUpdateTunnel_UpdatesExistingTunnel() { String tunnelId = "50df9101-f625-4618-b7c5-100338a57124"; String environment = "prod"; List tunnelResults = List.of(new TunnelResult(tunnelId, "devtunnel")); TunnelsResponse mockBody = new TunnelsResponse(tunnelResults, null, null, true); ResponseEntity mockResponse = new ResponseEntity<>(mockBody, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelsResponse.class) )).thenReturn(mockResponse); Tunnel existingTunnel = new Tunnel(UUID.fromString(tunnelId), "dev", "oldname"); when(tunnelRepository.findById(UUID.fromString(tunnelId))).thenReturn(Optional.of(existingTunnel)); cloudflareAPIService.createOrUpdateTunnel(tunnelId, environment); verify(tunnelRepository).deleteById(UUID.fromString(tunnelId)); verify(tunnelRepository).save(any(Tunnel.class)); } @Test void createOrUpdateTunnel_ThrowsExternalServiceExceptionOnApiError() { String tunnelId = "50df9101-f625-4618-b7c5-100338a57124"; ResponseEntity mockResponse = new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelsResponse.class) )).thenReturn(mockResponse); assertThrows(ExternalServiceException.class, () -> cloudflareAPIService.createOrUpdateTunnel(tunnelId, "dev") ); } @Test void createOrUpdateTunnel_ThrowsNoSuchElementExceptionWhenTunnelNotFound() { String tunnelId = "50df9101-f625-4618-b7c5-100338a57124"; List tunnelResults = List.of(new TunnelResult("other-tunnel-id", "othertunnel")); TunnelsResponse mockBody = new TunnelsResponse(tunnelResults, null, null, true); ResponseEntity mockResponse = new ResponseEntity<>(mockBody, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelsResponse.class) )).thenReturn(mockResponse); assertThrows(NoSuchElementException.class, () -> cloudflareAPIService.createOrUpdateTunnel(tunnelId, "dev") ); } @Test void getAllConfiguredTunnels_ReturnsAllTunnels() { List tunnels = List.of( new Tunnel(UUID.fromString("50df9101-f625-4618-b7c5-100338a57124"), "dev", "devtunnel"), new Tunnel(UUID.fromString("60df9101-f625-4618-b7c5-100338a57125"), "prod", "prodtunnel") ); when(tunnelRepository.findAll()).thenReturn(tunnels); List result = cloudflareAPIService.getAllConfiguredTunnels(); assertEquals(2, result.size()); assertEquals("devtunnel", result.get(0).getName()); assertEquals("prodtunnel", result.get(1).getName()); } @Test void addTunnelIngress_Success() throws JsonProcessingException { String tunnelId = "50df9101-f625-4618-b7c5-100338a57124"; Ingress ingress = new Ingress("http://192.168.0.100:8080", "test.hithomelabs.com", null, null); when(cloudflareConfig.getAccountId()).thenReturn("account-123"); when(authKeyEmailHeader.getHttpHeaders()).thenReturn(new HttpHeaders()); TunnelResponse tunnelResponse = new ObjectMapper().readValue(bigTunnelResponse, TunnelResponse.class); ResponseEntity getResponse = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(TunnelResponse.class) )).thenReturn(getResponse); ResponseEntity putResponse = new ResponseEntity<>(tunnelResponse, HttpStatus.OK); when(restTemplate.exchange( any(String.class), eq(HttpMethod.PUT), any(HttpEntity.class), eq(TunnelResponse.class) )).thenReturn(putResponse); ResponseEntity result = cloudflareAPIService.addTunnelIngress(tunnelId, ingress); assertEquals(HttpStatus.OK, result.getStatusCode()); } }