Refactor report generation and enhance integration tests
- Updated ReportService to streamline Excel report merging logic by introducing a new method for conditional merging. - Enhanced integration tests to include fiscal period handling and ensure correct artifact generation, particularly for scenarios with empty forecast sections. - Improved test assertions to validate the presence of expected artifacts and their properties in the generated reports.
This commit is contained in:
parent
661d78a225
commit
a01aa46f3e
|
|
@ -201,11 +201,11 @@ public class ReportService {
|
||||||
Row titleRow = sheet.createRow(0);
|
Row titleRow = sheet.createRow(0);
|
||||||
titleRow.setHeightInPoints(24);
|
titleRow.setHeightInPoints(24);
|
||||||
createCell(titleRow, 0, sheetName + " Detail", styles.get("title"));
|
createCell(titleRow, 0, sheetName + " Detail", styles.get("title"));
|
||||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, Math.max(0, columnCount - 1)));
|
mergeIfNeeded(sheet, 0, columnCount);
|
||||||
|
|
||||||
Row subtitleRow = sheet.createRow(1);
|
Row subtitleRow = sheet.createRow(1);
|
||||||
createCell(subtitleRow, 0, "Fiscal period " + fiscalPeriod + " | Rows " + rows.size(), styles.get("subtitle"));
|
createCell(subtitleRow, 0, "Fiscal period " + fiscalPeriod + " | Rows " + rows.size(), styles.get("subtitle"));
|
||||||
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, Math.max(0, columnCount - 1)));
|
mergeIfNeeded(sheet, 1, columnCount);
|
||||||
|
|
||||||
if (rows.isEmpty()) {
|
if (rows.isEmpty()) {
|
||||||
Row emptyRow = sheet.createRow(3);
|
Row emptyRow = sheet.createRow(3);
|
||||||
|
|
@ -347,6 +347,12 @@ public class ReportService {
|
||||||
cell.setCellStyle(style);
|
cell.setCellStyle(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mergeIfNeeded(Sheet sheet, int rowIndex, int columnCount) {
|
||||||
|
if (columnCount > 1) {
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(rowIndex, rowIndex, 0, columnCount - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CellStyle styleForKey(Map<String, CellStyle> styles, String key) {
|
private CellStyle styleForKey(Map<String, CellStyle> styles, String key) {
|
||||||
return key.toLowerCase().contains("ratio") ? styles.get("ratio") : styles.get("number");
|
return key.toLowerCase().contains("ratio") ? styles.get("ratio") : styles.get("number");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.hanwha.nexacrodemo.upload.TestWorkbookFactory;
|
import com.hanwha.nexacrodemo.upload.TestWorkbookFactory;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -20,12 +21,14 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockHttpSession;
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
class ConsolidationIntegrationTest {
|
class ConsolidationIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -39,24 +42,27 @@ class ConsolidationIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validUploadsCanBeConsolidatedAndReported() throws Exception {
|
void validUploadsCanBeConsolidatedAndReported() throws Exception {
|
||||||
|
String fiscalPeriod = "2026-04";
|
||||||
MockHttpSession session = login("operator", "demo1234");
|
MockHttpSession session = login("operator", "demo1234");
|
||||||
|
|
||||||
upload(session, "trial-balance", "tb-valid.xlsx", TestWorkbookFactory.trialBalanceValid());
|
upload(session, "trial-balance", fiscalPeriod, "tb-valid.xlsx", withFiscalPeriod(TestWorkbookFactory.trialBalanceValid(), fiscalPeriod));
|
||||||
upload(session, "forecast", "forecast-valid.xlsx", TestWorkbookFactory.forecastValid());
|
upload(session, "forecast", fiscalPeriod, "forecast-valid.xlsx", withFiscalPeriod(TestWorkbookFactory.forecastValid(), fiscalPeriod));
|
||||||
|
|
||||||
MvcResult runResult = mockMvc.perform(post("/api/consolidations/runs")
|
MvcResult runResult = mockMvc.perform(post("/api/consolidations/runs")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.session(session)
|
.session(session)
|
||||||
.content("""
|
.content("""
|
||||||
{
|
{
|
||||||
"fiscalPeriod": "2026-03",
|
"fiscalPeriod": "%s",
|
||||||
"reportCurrency": "KRW"
|
"reportCurrency": "KRW"
|
||||||
}
|
}
|
||||||
"""))
|
""".formatted(fiscalPeriod)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.statusCode").value("REQUESTED"))
|
.andExpect(jsonPath("$.statusCode").value("REQUESTED"))
|
||||||
.andReturn();
|
.andReturn();
|
||||||
|
|
||||||
|
long runId = objectMapper.readTree(runResult.getResponse().getContentAsByteArray()).path("id").asLong();
|
||||||
|
|
||||||
consolidationService.processPendingRuns("test-worker");
|
consolidationService.processPendingRuns("test-worker");
|
||||||
|
|
||||||
mockMvc.perform(get("/api/tx/consolidations/overview").session(session))
|
mockMvc.perform(get("/api/tx/consolidations/overview").session(session))
|
||||||
|
|
@ -65,20 +71,24 @@ class ConsolidationIntegrationTest {
|
||||||
|
|
||||||
MvcResult overviewResult = mockMvc.perform(get("/api/tx/reports/overview").session(session))
|
MvcResult overviewResult = mockMvc.perform(get("/api/tx/reports/overview").session(session))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.datasets.artifacts.length()").value(2))
|
|
||||||
.andExpect(jsonPath("$.datasets.jobLogs[0].logLevel").exists())
|
.andExpect(jsonPath("$.datasets.jobLogs[0].logLevel").exists())
|
||||||
.andReturn();
|
.andReturn();
|
||||||
|
|
||||||
JsonNode artifacts = objectMapper.readTree(overviewResult.getResponse().getContentAsByteArray())
|
JsonNode artifacts = objectMapper.readTree(overviewResult.getResponse().getContentAsByteArray())
|
||||||
.path("datasets")
|
.path("datasets")
|
||||||
.path("artifacts");
|
.path("artifacts");
|
||||||
|
int artifactCountForRun = 0;
|
||||||
long excelArtifactId = -1L;
|
long excelArtifactId = -1L;
|
||||||
for (JsonNode artifact : artifacts) {
|
for (JsonNode artifact : artifacts) {
|
||||||
|
if (artifact.path("runId").asLong() != runId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
artifactCountForRun++;
|
||||||
if ("EXCEL".equals(artifact.path("artifactType").asText())) {
|
if ("EXCEL".equals(artifact.path("artifactType").asText())) {
|
||||||
excelArtifactId = artifact.path("id").asLong();
|
excelArtifactId = artifact.path("id").asLong();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Assertions.assertEquals(2, artifactCountForRun);
|
||||||
Assertions.assertTrue(excelArtifactId > 0, "EXCEL artifact should exist");
|
Assertions.assertTrue(excelArtifactId > 0, "EXCEL artifact should exist");
|
||||||
|
|
||||||
MvcResult excelDownload = mockMvc.perform(get("/api/reports/{artifactId}/download", excelArtifactId).session(session))
|
MvcResult excelDownload = mockMvc.perform(get("/api/reports/{artifactId}/download", excelArtifactId).session(session))
|
||||||
|
|
@ -92,7 +102,61 @@ class ConsolidationIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upload(MockHttpSession session, String templateCode, String fileName, byte[] content) throws Exception {
|
@Test
|
||||||
|
void consolidationStillSucceedsWhenForecastSectionIsEmpty() throws Exception {
|
||||||
|
String fiscalPeriod = "2026-03";
|
||||||
|
MockHttpSession session = login("operator", "demo1234");
|
||||||
|
|
||||||
|
upload(session, "trial-balance", fiscalPeriod, "tb-valid.xlsx", TestWorkbookFactory.trialBalanceValid());
|
||||||
|
|
||||||
|
MvcResult runResult = mockMvc.perform(post("/api/consolidations/runs")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.session(session)
|
||||||
|
.content("""
|
||||||
|
{
|
||||||
|
"fiscalPeriod": "%s",
|
||||||
|
"reportCurrency": "KRW"
|
||||||
|
}
|
||||||
|
""".formatted(fiscalPeriod)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.statusCode").value("REQUESTED"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
long runId = objectMapper.readTree(runResult.getResponse().getContentAsByteArray()).path("id").asLong();
|
||||||
|
|
||||||
|
consolidationService.processPendingRuns("test-worker");
|
||||||
|
|
||||||
|
MvcResult overviewResult = mockMvc.perform(get("/api/tx/reports/overview").session(session))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
JsonNode artifacts = objectMapper.readTree(overviewResult.getResponse().getContentAsByteArray())
|
||||||
|
.path("datasets")
|
||||||
|
.path("artifacts");
|
||||||
|
int artifactCountForRun = 0;
|
||||||
|
long excelArtifactId = -1L;
|
||||||
|
for (JsonNode artifact : artifacts) {
|
||||||
|
if (artifact.path("runId").asLong() != runId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
artifactCountForRun++;
|
||||||
|
if ("EXCEL".equals(artifact.path("artifactType").asText())) {
|
||||||
|
excelArtifactId = artifact.path("id").asLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assertions.assertEquals(2, artifactCountForRun);
|
||||||
|
Assertions.assertTrue(excelArtifactId > 0, "EXCEL artifact should exist");
|
||||||
|
|
||||||
|
MvcResult excelDownload = mockMvc.perform(get("/api/reports/{artifactId}/download", excelArtifactId).session(session))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(excelDownload.getResponse().getContentAsByteArray()))) {
|
||||||
|
Assertions.assertEquals("No data available", workbook.getSheet("Forecast").getRow(3).getCell(0).getStringCellValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upload(MockHttpSession session, String templateCode, String fiscalPeriod, String fileName, byte[] content) throws Exception {
|
||||||
MockMultipartFile file = new MockMultipartFile(
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
"file",
|
"file",
|
||||||
fileName,
|
fileName,
|
||||||
|
|
@ -103,12 +167,27 @@ class ConsolidationIntegrationTest {
|
||||||
mockMvc.perform(multipart("/api/uploads")
|
mockMvc.perform(multipart("/api/uploads")
|
||||||
.file(file)
|
.file(file)
|
||||||
.param("templateCode", templateCode)
|
.param("templateCode", templateCode)
|
||||||
.param("fiscalPeriod", "2026-03")
|
.param("fiscalPeriod", fiscalPeriod)
|
||||||
.session(session))
|
.session(session))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.statusCode").value("ACCEPTED"));
|
.andExpect(jsonPath("$.statusCode").value("ACCEPTED"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] withFiscalPeriod(byte[] content, String fiscalPeriod) throws Exception {
|
||||||
|
try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(content))) {
|
||||||
|
var dataSheet = workbook.getSheet("DATA");
|
||||||
|
for (int rowIndex = 1; rowIndex <= dataSheet.getLastRowNum(); rowIndex++) {
|
||||||
|
var row = dataSheet.getRow(rowIndex);
|
||||||
|
if (row != null && row.getCell(0) != null) {
|
||||||
|
row.getCell(0).setCellValue(fiscalPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
workbook.write(outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MockHttpSession login(String username, String password) throws Exception {
|
private MockHttpSession login(String username, String password) throws Exception {
|
||||||
MvcResult result = mockMvc.perform(post("/api/auth/login")
|
MvcResult result = mockMvc.perform(post("/api/auth/login")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue