Skip to content
Open
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
@@ -1,8 +1,11 @@
package com.kenyajug.regression.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kenyajug.regression.entities.AppLog;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/dummy")
Expand All @@ -13,5 +16,6 @@ public String hello() {
return "Hello from DummyRestController!";

}

}

Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.List;
@Controller
Expand Down Expand Up @@ -135,4 +134,8 @@ public String logsDetailed(@PathVariable("id") String logId, Model model){
model.addAttribute("metadata",metadata);
return "logs-detailed";
}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.kenyajug.regression.controllers;

import com.kenyajug.regression.entities.AppLog;
import com.kenyajug.regression.services.IngestionService;
import com.kenyajug.regression.services.RetrievalService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class LogsIngestionController {
private final RetrievalService retrievalService;
private final IngestionService ingestionService;

public LogsIngestionController(RetrievalService retrievalService, IngestionService ingestionService) {
this.retrievalService = retrievalService;
this.ingestionService = ingestionService;
}


@PostMapping("/ingest")
@PreAuthorize("hasAnyRole('ROLE_ADMIN','datasource')")
public ResponseEntity<?> ingestLogs(@Valid @RequestBody AppLog appLog) {
var optionalAppLog = retrievalService.findAppLogById(appLog.uuid());
if (optionalAppLog.isPresent()) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body("Log with id " + appLog.uuid() + " already exists");
} else {
ingestionService.saveAppLog(appLog);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/kenyajug/regression/entities/AppLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,23 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;
public record AppLog(
String uuid,
@NotNull(message = "timestamp cannot be null")
LocalDateTime timestamp,
@NotNull(message = "severity cannot be null")
@NotBlank(message = "severity cannot be blank")
String severity,
@NotNull(message = "applicationId cannot be null")
@NotBlank(message = "applicationId cannot be blank")
String applicationId,
String logSource,
@NotBlank(message = "message cannot be null")
@NotNull(message = "message cannot be blank")
Comment on lines 30 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This are awesome 🔥

String message
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
Expand All @@ -39,13 +40,19 @@ public SecurityConfig(SecurityService securityService) {
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(request -> request.anyRequest().authenticated())
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
httpSecurity
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(request -> request.anyRequest().authenticated())
.formLogin(form -> form.permitAll())
.httpBasic(basic -> {})
.userDetailsService(securityService);
return httpSecurity.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,7 @@ public interface IIngestionService {
* @return an {@link Optional} containing the extracted {@link LogsMetadata} if a match is found; otherwise, an empty {@link Optional}
*/
Optional<LogsMetadata> extractMetadataByRegex(AppLog appLog, InstantTraceGroup traceGroup, Constants.Tuple regex);

void saveAppLog(AppLog appLog) ;
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import com.kenyajug.regression.entities.AppLog;
import com.kenyajug.regression.entities.LogsMetadata;
import com.kenyajug.regression.resources.ApplicationResource;
import com.kenyajug.regression.resources.DatasourceResource;
Expand Down Expand Up @@ -90,4 +91,7 @@ public interface IRetrievalService {
* @return an {@code List} containing the {@code LogsMetadata} list if found, or empty if no log matches the given ID
*/
List<LogsMetadata> findMetadataByLogId(String logId);


Optional<AppLog> findAppLogById(String logId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,9 @@ public Optional<LogsMetadata> extractMetadataByRegex(AppLog appLog, InstantTrace
);
return Optional.of(metadata);
}

@Override
public void saveAppLog(AppLog appLog) {
appLogRepository.save(appLog);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,12 @@ public Optional<LogResource> findLogsById(String logId) {
public List<LogsMetadata> findMetadataByLogId(String logId) {
return metadataRepository.findByRootLogId(logId);
}

@Override
public Optional<AppLog> findAppLogById(String logId) {
var appLog = logRepository.findById(logId);
if (appLog.isEmpty()) return Optional.empty();
return appLog;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public class ApplicationBasicSecurityTest {
public void shouldThrow3xxWhenMissingCredentialsTest() throws Exception {
mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/login"));
.andExpect(status().is4xxClientError());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class ApplicationUITest {
public void shouldThrowUnauthorizedAccessErrorApplicationFormTest() throws Exception {
mockMvc.perform(get("/add/application"))
.andDo(print())
.andExpect(status().is3xxRedirection());
.andExpect(status().is4xxClientError());
}
@Test
@DisplayName("Should launch source application form successfully")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class DashboardPageTests {
public void shouldLoadHomePage_UnauthorizedTest() throws Exception{
mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().is3xxRedirection());
.andExpect(status().is4xxClientError());
}
@Test
@DisplayName("Should load home page successfully")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.kenyajug.regression.web_mvc_tests;


import com.kenyajug.regression.controllers.LogsIngestionController;
import com.kenyajug.regression.entities.AppLog;
import com.kenyajug.regression.resources.ApplicationResource;
import com.kenyajug.regression.resources.DatasourceResource;
import com.kenyajug.regression.resources.LogResource;
import com.kenyajug.regression.services.IngestionService;
import com.kenyajug.regression.services.RetrievalService;
import com.kenyajug.regression.utils.DateTimeUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;
import java.util.Optional;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class LogsIngestionControllerTest {

@Autowired
MockMvc mockMvc;

@MockitoBean
RetrievalService retrievalService;

@MockitoBean
IngestionService ingestionService;

@MockitoBean
private AppLog appLog;

@BeforeEach
void setUp() {
appLog = new AppLog(
"test-uuid",
DateTimeUtils.convertZonedUTCTimeStringToLocalDateTime("2025-08-11 11:09:22 UTC") ,
"ERROR",
"app-123",
"",
"Test message"
// add other fields as needed
);
}

@Test
void shouldReturnConflictIfLogExists() throws Exception {
var datetime = DateTimeUtils.convertZonedUTCTimeStringToLocalDateTime("2025-08-11 11:09:22 UTC");
AppLog applog = new AppLog(
"test-uuid",
datetime,
"ERROR",
"app-123",
"",
"Test message"
);
when(retrievalService.findAppLogById("test-uuid")).thenReturn(Optional.of(applog));
mockMvc.perform(post("/ingest")
.with(httpBasic("admin@regression.com", "admin123")) // Assuming basic auth is used
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"uuid": "test-uuid",
"timestamp": "%s",
"severity": "ERROR",
"applicationId": "app-123",
"message": "Test message"
}
""".formatted(datetime)))
.andExpect(status().isConflict())
.andExpect(content().string("Log with id test-uuid already exists"));

verify(ingestionService, never()).saveAppLog(any());
}

@Test
void shouldCreateLogIfNotExists() throws Exception {

mockMvc.perform(post("/ingest")
.with(httpBasic("admin@regression.com", "admin123"))
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"uuid": "test-uuid",
"timestamp": "2025-05-08T12:00:00",
"severity": "ERROR",
"applicationId": "app-123",
"message": "Test message"
}
"""))
.andExpect(status().isCreated());

verify(ingestionService, times(1)).saveAppLog(any());
}


@Test
void shouldReturnUnauthorizedIfNoCredentials() throws Exception {
mockMvc.perform(post("/ingest")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"uuid": "test-uuid",
"timestamp": "2025-05-08T12:00:00",
"severity": "ERROR",
"applicationId": "app-123",
"message": "Test message"
}
"""))
.andExpect(status().isUnauthorized());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void shouldLaunchPasswordResetPageTest() throws Exception {
public void shouldThrow302_OnPasswordResetPageTest() throws Exception {
mockMvc.perform(get("/user/form"))
.andDo(print())
.andExpect(status().is3xxRedirection());
.andExpect(status().is4xxClientError());
}
@Test
@DisplayName("Should throw SecurityException under security violation conditions")
Expand Down