package com.dalab.policyengine.controller; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.time.LocalDateTime; import java.util.*; import org.junit.jupiter.api.BeforeEach; 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.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import com.dalab.policyengine.dto.*; import com.dalab.policyengine.model.Policy; import com.dalab.policyengine.model.PolicyDraft; import com.dalab.policyengine.model.PolicyStatus; import com.dalab.policyengine.service.IPolicyService; import com.dalab.policyengine.service.IPolicyEvaluationService; import com.dalab.policyengine.service.IPolicyDraftService; import com.dalab.policyengine.service.IEventSubscriptionService; import com.fasterxml.jackson.databind.ObjectMapper; @WebMvcTest(PolicyController.class) @ExtendWith(MockitoExtension.class) @ActiveProfiles("test") class PolicyControllerTest { @Autowired private MockMvc mockMvc; @MockBean private IPolicyService policyService; @MockBean private IPolicyEvaluationService policyEvaluationService; @MockBean private IPolicyDraftService policyDraftService; @MockBean private IEventSubscriptionService eventSubscriptionService; @Autowired private ObjectMapper objectMapper; private Policy testPolicy; private PolicyDTO testPolicyDTO; private PolicyDraft testPolicyDraft; private PolicyDraftDTO testPolicyDraftDTO; private PolicyEvaluationRequestDTO testEvaluationRequest; private PolicyImpactRequestDTO testImpactRequest; private PolicyImpactResponseDTO testImpactResponse; @BeforeEach void setUp() { // Create test policy testPolicy = new Policy(); testPolicy.setId(UUID.randomUUID()); testPolicy.setName("Test Policy"); testPolicy.setDescription("Test policy for compliance"); testPolicy.setRulesContent("when asset.type == 'PII' then tag('SENSITIVE')"); testPolicy.setStatus(PolicyStatus.ENABLED); testPolicy.setCreatedAt(LocalDateTime.now()); testPolicy.setUpdatedAt(LocalDateTime.now()); // Create test policy DTO testPolicyDTO = PolicyDTO.builder() .id(testPolicy.getId()) .name("Test Policy") .description("Test policy for compliance") .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") .status("ENABLED") .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); // Create test policy draft testPolicyDraft = new PolicyDraft(); testPolicyDraft.setId(UUID.randomUUID()); testPolicyDraft.setName("Test Draft Policy"); testPolicyDraft.setDescription("Draft policy for testing"); testPolicyDraft.setRulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')"); testPolicyDraft.setStatus("DRAFT"); testPolicyDraft.setCreatedAt(LocalDateTime.now()); // Create test policy draft DTO testPolicyDraftDTO = PolicyDraftDTO.builder() .id(testPolicyDraft.getId()) .name("Test Draft Policy") .description("Draft policy for testing") .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") .status("DRAFT") .createdAt(LocalDateTime.now()) .build(); // Create test evaluation request testEvaluationRequest = PolicyEvaluationRequestDTO.builder() .policyId(testPolicy.getId()) .assetId(UUID.randomUUID()) .evaluationContext(Map.of("asset.type", "PII")) .build(); // Create test impact request testImpactRequest = PolicyImpactRequestDTO.builder() .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") .analysisType("FULL") .includePerformanceEstimate(true) .includeCostImpact(true) .includeComplianceImpact(true) .build(); // Create test impact response testImpactResponse = PolicyImpactResponseDTO.builder() .analysisId("impact-12345678") .analysisType("FULL") .build(); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void createPolicy_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() .name("New Test Policy") .description("New test policy for compliance") .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") .build(); when(policyService.createPolicy(any(PolicyCreateRequestDTO.class))).thenReturn(testPolicyDTO); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) .andExpect(jsonPath("$.name").value("Test Policy")) .andExpect(jsonPath("$.status").value("ENABLED")); } @Test @WithMockUser(authorities = "ROLE_USER") void createPolicy_AsUser_ShouldBeForbidden() throws Exception { // Given PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() .name("New Test Policy") .description("New test policy for compliance") .rulesContent("when asset.type == 'PII' then tag('SENSITIVE')") .build(); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isForbidden()); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void getAllPolicies_AsDataEngineer_ShouldSucceed() throws Exception { // Given List policyList = Arrays.asList(testPolicyDTO); Page policyPage = new PageImpl<>(policyList, PageRequest.of(0, 20), 1); when(policyService.getAllPolicies(any(Pageable.class))).thenReturn(policyPage); // When & Then mockMvc.perform(get("/api/v1/policyengine/policies") .param("page", "0") .param("size", "20")) .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].id").value(testPolicy.getId().toString())) .andExpect(jsonPath("$.content[0].name").value("Test Policy")) .andExpect(jsonPath("$.totalElements").value(1)); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void getPolicyById_AsDataEngineer_ShouldSucceed() throws Exception { // Given when(policyService.getPolicyById(testPolicy.getId())).thenReturn(Optional.of(testPolicyDTO)); // When & Then mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", testPolicy.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) .andExpect(jsonPath("$.name").value("Test Policy")) .andExpect(jsonPath("$.status").value("ENABLED")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void getPolicyById_PolicyNotFound_ShouldReturnNotFound() throws Exception { // Given UUID nonExistentId = UUID.randomUUID(); when(policyService.getPolicyById(nonExistentId)).thenReturn(Optional.empty()); // When & Then mockMvc.perform(get("/api/v1/policyengine/policies/{policyId}", nonExistentId)) .andExpect(status().isNotFound()); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void updatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyUpdateRequestDTO request = PolicyUpdateRequestDTO.builder() .name("Updated Test Policy") .description("Updated test policy for compliance") .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") .status("ENABLED") .build(); PolicyDTO updatedPolicy = PolicyDTO.builder() .id(testPolicy.getId()) .name("Updated Test Policy") .description("Updated test policy for compliance") .rulesContent("when asset.type == 'PII' then tag('SENSITIVE') and notify('admin')") .status("ENABLED") .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); when(policyService.updatePolicy(eq(testPolicy.getId()), any(PolicyUpdateRequestDTO.class))) .thenReturn(updatedPolicy); // When & Then mockMvc.perform(put("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(testPolicy.getId().toString())) .andExpect(jsonPath("$.name").value("Updated Test Policy")) .andExpect(jsonPath("$.status").value("ENABLED")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void deletePolicy_AsDataEngineer_ShouldSucceed() throws Exception { // Given doNothing().when(policyService).deletePolicy(testPolicy.getId()); // When & Then mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) .with(csrf())) .andExpect(status().isNoContent()); } @Test @WithMockUser(authorities = "ROLE_USER") void deletePolicy_AsUser_ShouldBeForbidden() throws Exception { // When & Then mockMvc.perform(delete("/api/v1/policyengine/policies/{policyId}", testPolicy.getId()) .with(csrf())) .andExpect(status().isForbidden()); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void evaluatePolicy_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyEvaluationResponseDTO evaluationResponse = PolicyEvaluationResponseDTO.builder() .policyId(testPolicy.getId()) .assetId(testEvaluationRequest.getAssetId()) .evaluationResult("PASS") .actions(Arrays.asList("tag('SENSITIVE')")) .evaluationTimestamp(LocalDateTime.now()) .build(); when(policyEvaluationService.evaluatePolicy(any(PolicyEvaluationRequestDTO.class))) .thenReturn(evaluationResponse); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testEvaluationRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.policyId").value(testPolicy.getId().toString())) .andExpect(jsonPath("$.evaluationResult").value("PASS")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void analyzePolicyImpact_AsDataEngineer_ShouldSucceed() throws Exception { // Given when(policyService.analyzePolicy(any(PolicyImpactRequestDTO.class))) .thenReturn(testImpactResponse); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(testImpactRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.analysisId").value("impact-12345678")) .andExpect(jsonPath("$.analysisType").value("FULL")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void createPolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyDraftCreateRequestDTO request = PolicyDraftCreateRequestDTO.builder() .name("New Draft Policy") .description("New draft policy for testing") .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") .build(); when(policyDraftService.createPolicyDraft(any(PolicyDraftCreateRequestDTO.class))) .thenReturn(testPolicyDraftDTO); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/drafts") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) .andExpect(jsonPath("$.name").value("Test Draft Policy")) .andExpect(jsonPath("$.status").value("DRAFT")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void getAllPolicyDrafts_AsDataEngineer_ShouldSucceed() throws Exception { // Given List draftList = Arrays.asList(testPolicyDraftDTO); Page draftPage = new PageImpl<>(draftList, PageRequest.of(0, 20), 1); when(policyDraftService.getAllPolicyDrafts(any(Pageable.class))).thenReturn(draftPage); // When & Then mockMvc.perform(get("/api/v1/policyengine/policies/drafts") .param("page", "0") .param("size", "20")) .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].id").value(testPolicyDraft.getId().toString())) .andExpect(jsonPath("$.content[0].name").value("Test Draft Policy")) .andExpect(jsonPath("$.totalElements").value(1)); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void getPolicyDraftById_AsDataEngineer_ShouldSucceed() throws Exception { // Given when(policyDraftService.getPolicyDraftById(testPolicyDraft.getId())) .thenReturn(Optional.of(testPolicyDraftDTO)); // When & Then mockMvc.perform(get("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) .andExpect(jsonPath("$.name").value("Test Draft Policy")) .andExpect(jsonPath("$.status").value("DRAFT")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void updatePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyDraftUpdateRequestDTO request = PolicyDraftUpdateRequestDTO.builder() .name("Updated Draft Policy") .description("Updated draft policy for testing") .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") .build(); PolicyDraftDTO updatedDraft = PolicyDraftDTO.builder() .id(testPolicyDraft.getId()) .name("Updated Draft Policy") .description("Updated draft policy for testing") .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE') and encrypt()") .status("DRAFT") .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); when(policyDraftService.updatePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftUpdateRequestDTO.class))) .thenReturn(updatedDraft); // When & Then mockMvc.perform(put("/api/v1/policyengine/policies/drafts/{draftId}", testPolicyDraft.getId()) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(testPolicyDraft.getId().toString())) .andExpect(jsonPath("$.name").value("Updated Draft Policy")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void approvePolicyDraft_AsDataEngineer_ShouldSucceed() throws Exception { // Given PolicyDraftApprovalRequestDTO request = PolicyDraftApprovalRequestDTO.builder() .approved(true) .comments("Approved for production use") .build(); PolicyDTO approvedPolicy = PolicyDTO.builder() .id(UUID.randomUUID()) .name("Approved Policy") .description("Policy approved from draft") .rulesContent("when asset.type == 'PHI' then tag('HEALTHCARE')") .status("ENABLED") .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); when(policyDraftService.approvePolicyDraft(eq(testPolicyDraft.getId()), any(PolicyDraftApprovalRequestDTO.class))) .thenReturn(approvedPolicy); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/drafts/{draftId}/approve", testPolicyDraft.getId()) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Approved Policy")) .andExpect(jsonPath("$.status").value("ENABLED")); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void createPolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { // Given PolicyCreateRequestDTO request = PolicyCreateRequestDTO.builder() .name("") // Invalid: empty name .description("Test policy") .rulesContent("invalid rule syntax") .build(); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void evaluatePolicy_InvalidRequest_ShouldReturnBadRequest() throws Exception { // Given PolicyEvaluationRequestDTO request = PolicyEvaluationRequestDTO.builder() .policyId(null) // Invalid: null policy ID .assetId(UUID.randomUUID()) .evaluationContext(Map.of("asset.type", "PII")) .build(); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/evaluate") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } @Test @WithMockUser(authorities = "ROLE_DATA_ENGINEER") void analyzePolicyImpact_InvalidRequest_ShouldReturnBadRequest() throws Exception { // Given PolicyImpactRequestDTO request = PolicyImpactRequestDTO.builder() .rulesContent("") // Invalid: empty rules content .analysisType("INVALID_TYPE") // Invalid: unknown analysis type .build(); // When & Then mockMvc.perform(post("/api/v1/policyengine/policies/{policyId}/impact-preview", testPolicy.getId()) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()); } }