package com.reportdist; import com.reportdist.dto.ProjectRequest; import com.reportdist.dto.ReportResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.*; import org.junit.jupiter.api.DisplayName; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; /** * Complete API flow integration test. * Tests: Create Project → Upload Report → Query Report → Delete Report → Delete Project (cascade) */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class CompleteApiFlowIntegrationTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; private String baseUrl; @BeforeEach void setUp() throws Exception { baseUrl = "http://localhost:" + port; // Ensure upload directory exists Path uploadPath = Paths.get(System.getProperty("java.io.tmpdir"), "report-dist-test-uploads"); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } } @AfterEach void tearDown() { // Cleanup is handled by H2 create-drop and file cleanup in each test } @Test @DisplayName("Complete API flow: Create Project → Upload Report → Query Report → Delete Report → Delete Project") void completeApiFlow_shouldSucceed() { // Step 1: Create a project ProjectRequest projectRequest = new ProjectRequest("Integration Test Project", "Testing complete flow"); ResponseEntity createResponse = restTemplate.postForEntity( baseUrl + "/api/projects", projectRequest, Map.class ); assertEquals(HttpStatus.CREATED, createResponse.getStatusCode()); assertNotNull(createResponse.getBody()); Long projectId = ((Number) createResponse.getBody().get("id")).longValue(); assertNotNull(projectId); assertEquals("Integration Test Project", createResponse.getBody().get("name")); System.out.println("[Step 1] Created project with ID: " + projectId); // Step 2: Verify project exists ResponseEntity getProjectResponse = restTemplate.getForEntity( baseUrl + "/api/projects/" + projectId, Map.class ); assertEquals(HttpStatus.OK, getProjectResponse.getStatusCode()); assertEquals("Integration Test Project", getProjectResponse.getBody().get("name")); System.out.println("[Step 2] Verified project exists"); // Step 3: Upload a report to the project String htmlContent = "

Integration Test Report

Content here

"; MultiValueMap reportParts = new LinkedMultiValueMap<>(); reportParts.add("file", new ByteArrayResource(htmlContent.getBytes()) { @Override public String getFilename() { return "test-report.html"; } }); reportParts.add("projectId", projectId.toString()); reportParts.add("fileType", "HTML"); HttpHeaders reportHeaders = new HttpHeaders(); reportHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity> reportRequest = new HttpEntity<>(reportParts, reportHeaders); ResponseEntity uploadResponse = restTemplate.postForEntity( baseUrl + "/api/reports", reportRequest, Map.class ); assertEquals(HttpStatus.CREATED, uploadResponse.getStatusCode()); assertNotNull(uploadResponse.getBody()); Long reportId = ((Number) uploadResponse.getBody().get("id")).longValue(); assertNotNull(reportId); assertEquals("test-report.html", uploadResponse.getBody().get("fileName")); assertEquals("HTML", uploadResponse.getBody().get("fileType")); String filePath = (String) uploadResponse.getBody().get("filePath"); System.out.println("[Step 3] Uploaded report with ID: " + reportId + ", path: " + filePath); // Verify file was actually saved Path savedFile = Paths.get(filePath); assertTrue(Files.exists(savedFile), "File should exist on disk"); // Step 4: Query reports by projectId ResponseEntity reportsResponse = restTemplate.getForEntity( baseUrl + "/api/reports?projectId=" + projectId, List.class ); assertEquals(HttpStatus.OK, reportsResponse.getStatusCode()); assertNotNull(reportsResponse.getBody()); assertEquals(1, reportsResponse.getBody().size()); Map reportInList = (Map) reportsResponse.getBody().get(0); assertEquals(reportId, ((Number) reportInList.get("id")).longValue()); System.out.println("[Step 4] Queried reports, found: " + reportsResponse.getBody().size()); // Step 5: Get report by ID with content ResponseEntity getReportResponse = restTemplate.getForEntity( baseUrl + "/api/reports/" + reportId, Map.class ); assertEquals(HttpStatus.OK, getReportResponse.getStatusCode()); assertEquals("test-report.html", getReportResponse.getBody().get("fileName")); assertEquals(htmlContent, getReportResponse.getBody().get("fileContent")); System.out.println("[Step 5] Retrieved report with content"); // Step 6: Delete the report ResponseEntity deleteReportResponse = restTemplate.exchange( baseUrl + "/api/reports/" + reportId, HttpMethod.DELETE, null, Void.class ); assertEquals(HttpStatus.NO_CONTENT, deleteReportResponse.getStatusCode()); // Verify report file is deleted assertFalse(Files.exists(savedFile), "File should be deleted from disk"); // Verify report is gone ResponseEntity getDeletedReport = restTemplate.getForEntity( baseUrl + "/api/reports/" + reportId, Map.class ); assertEquals(HttpStatus.NOT_FOUND, getDeletedReport.getStatusCode()); System.out.println("[Step 6] Deleted report"); // Step 7: Delete the project ResponseEntity deleteProjectResponse = restTemplate.exchange( baseUrl + "/api/projects/" + projectId, HttpMethod.DELETE, null, Void.class ); assertEquals(HttpStatus.NO_CONTENT, deleteProjectResponse.getStatusCode()); // Verify project is gone ResponseEntity getDeletedProject = restTemplate.getForEntity( baseUrl + "/api/projects/" + projectId, Map.class ); assertEquals(HttpStatus.NOT_FOUND, getDeletedProject.getStatusCode()); System.out.println("[Step 7] Deleted project"); System.out.println("[SUCCESS] Complete API flow test passed!"); } @Test @DisplayName("Cascade delete: Deleting project should not delete reports (manual cleanup)") void deleteProject_shouldNotAutoDeleteReports() { // Create project ProjectRequest projectRequest = new ProjectRequest("Cascade Test Project", "Testing cascade"); ResponseEntity createResponse = restTemplate.postForEntity( baseUrl + "/api/projects", projectRequest, Map.class ); assertEquals(HttpStatus.CREATED, createResponse.getStatusCode()); Long projectId = ((Number) createResponse.getBody().get("id")).longValue(); // Upload two reports for (int i = 1; i <= 2; i++) { final int reportIndex = i; String content = "Report " + i; MultiValueMap parts = new LinkedMultiValueMap<>(); parts.add("file", new ByteArrayResource(content.getBytes()) { @Override public String getFilename() { return "report" + reportIndex + ".html"; } }); parts.add("projectId", projectId.toString()); parts.add("fileType", "HTML"); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity> request = new HttpEntity<>(parts, headers); ResponseEntity response = restTemplate.postForEntity( baseUrl + "/api/reports", request, Map.class ); assertEquals(HttpStatus.CREATED, response.getStatusCode()); } // Verify reports exist ResponseEntity reportsResponse = restTemplate.getForEntity( baseUrl + "/api/reports?projectId=" + projectId, List.class ); assertEquals(2, reportsResponse.getBody().size()); // Delete project ResponseEntity deleteResponse = restTemplate.exchange( baseUrl + "/api/projects/" + projectId, HttpMethod.DELETE, null, Void.class ); assertEquals(HttpStatus.NO_CONTENT, deleteResponse.getStatusCode()); // Project should be gone ResponseEntity getProject = restTemplate.getForEntity( baseUrl + "/api/projects/" + projectId, Map.class ); assertEquals(HttpStatus.NOT_FOUND, getProject.getStatusCode()); // Reports are cascade-deleted along with the project ResponseEntity remainingReports = restTemplate.getForEntity( baseUrl + "/api/reports?projectId=" + projectId, List.class ); assertEquals(0, remainingReports.getBody().size()); System.out.println("[INFO] Project deleted. All reports cascade-deleted."); } }