Files
daily_publish/src/test/java/com/reportdist/CompleteApiFlowIntegrationTest.java
T
panda afcd18c54f fix: evaluation report P0/P1/P2 fixes, remove Docker, add upload UI
Backend:
- Add NotFoundException + BusinessException, return correct HTTP status (404/400)
- Add @Index on reports.project_id and reports.upload_time
- Add fileSize column to reports, populate on upload, return in DTO
- Cascade delete: deleting project now removes all reports (DB + files + PDFs)
- Delete report: also clean up pre-rendered PDF
- File upload MIME validation (extension + Content-Type)
- Remove duplicate @ExceptionHandler from ReportController
- Switch from System.err to SLF4J logger
- Handle MethodArgumentNotValid, MissingServletRequestPart, etc.

Frontend:
- Remove all Docker files (project uses 宝塔 panel deployment)
- Upgrade axios 1.6.8 -> 1.7.7 (CVE-2024-39338)
- Remove unused @vue-office/pptx + vue-demi (see CHANGELOG for rationale)
- Fix vite proxy port 37821 -> 30081
- Remove mock data fallback in production
- Add upload report UI (button + modal in ProjectDetail)
- Add create project UI (button + modal in ProjectList)
- Add filename search box in ProjectDetail
- New useApi methods: createProject, uploadReport, deleteProject, deleteReport
- FilePreview/ReportCard: show fileSize (was undefined before)

Docs:
- Add README.md (overview, quick start, structure)
- Add CHANGELOG.md (full change log + pptx removal rationale)
- Include EVALUATION_REPORT.md and blog-vibe-coding.md

Tests:
- All 73 backend tests pass
- All 43 frontend tests pass
- Updated test fixtures for new API contract
2026-06-01 21:35:13 +08:00

251 lines
10 KiB
Java

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<Map> 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<Map> 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 = "<html><body><h1>Integration Test Report</h1><p>Content here</p></body></html>";
MultiValueMap<String, Object> 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<MultiValueMap<String, Object>> reportRequest = new HttpEntity<>(reportParts, reportHeaders);
ResponseEntity<Map> 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<List> 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<Map> 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<Void> 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<Map> 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<Void> deleteProjectResponse = restTemplate.exchange(
baseUrl + "/api/projects/" + projectId,
HttpMethod.DELETE,
null,
Void.class
);
assertEquals(HttpStatus.NO_CONTENT, deleteProjectResponse.getStatusCode());
// Verify project is gone
ResponseEntity<Map> 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<Map> 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<String, Object> 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<MultiValueMap<String, Object>> request = new HttpEntity<>(parts, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(
baseUrl + "/api/reports",
request,
Map.class
);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
}
// Verify reports exist
ResponseEntity<List> reportsResponse = restTemplate.getForEntity(
baseUrl + "/api/reports?projectId=" + projectId,
List.class
);
assertEquals(2, reportsResponse.getBody().size());
// Delete project
ResponseEntity<Void> deleteResponse = restTemplate.exchange(
baseUrl + "/api/projects/" + projectId,
HttpMethod.DELETE,
null,
Void.class
);
assertEquals(HttpStatus.NO_CONTENT, deleteResponse.getStatusCode());
// Project should be gone
ResponseEntity<Map> getProject = restTemplate.getForEntity(
baseUrl + "/api/projects/" + projectId,
Map.class
);
assertEquals(HttpStatus.NOT_FOUND, getProject.getStatusCode());
// Reports are cascade-deleted along with the project
ResponseEntity<List> 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.");
}
}