diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java index 6813284..b2381e0 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java @@ -42,6 +42,7 @@ public class BusinessBlockGenerationServiceTest assertTrue(containsPath(files, "src/views/pages/cart_page/index.vue")); assertTrue(containsPath(files, "src/views/pages/cart_page/blocks/cart_001/CartBlock.vue")); assertTrue(containsPath(files, "src/api/business-blocks/cart_001.js")); + assertFalse(containsPath(files, "src/utils/chartRuntime.js")); } @Test diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java index 5b48fa8..355ce4d 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java @@ -13,6 +13,8 @@ import com.ruoyi.generator.domain.TemplateFile; import com.ruoyi.generator.domain.GeneratedBusinessBlockFile; import com.ruoyi.generator.domain.front.FrontProjectPageDesign; import com.ruoyi.generator.domain.front.dto.BusinessActionDesign; +import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign; +import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign; import com.ruoyi.generator.domain.front.dto.BusinessActionUiBindingDesign; import com.ruoyi.generator.mapper.GenProjectMapper; import com.ruoyi.generator.mapper.GenTableMapper; @@ -37,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +49,7 @@ import java.util.zip.ZipInputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.mockito.Mockito.never; @@ -142,6 +146,40 @@ public class GenProjectServiceImplTest { assertFalse(preview.containsKey("my-controller.java.vm")); } + @Test + public void previewCodeImportsDateTypesForRunnableBackendEntityAuditColumns() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + GenTableColumn createTime = new GenTableColumn(); + createTime.setTableId(11L); + createTime.setColumnName("create_time"); + createTime.setColumnType("datetime"); + createTime.setJavaType("Date"); + createTime.setJavaField("createTime"); + createTime.setIsPk("0"); + table.setColumns(Arrays.asList(pkColumn(11L), createTime)); + + TemplateFile entityTemplate = templateFile("entity.java.vm", + "#foreach ($import in $importList)\nimport ${import};\n#end\n" + + "@JsonFormat(pattern = \"yyyy-MM-dd\")\nprivate Date createTime;"); + entityTemplate.setTemplateId(9201L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")) + .thenReturn(projectTemplate(9201L)); + when(templateFileService.selectTemplateFilesByTemplateId(9201L)).thenReturn(Arrays.asList(entityTemplate)); + + Map preview = service.previewCode(1L, 11L, "entity.java.vm", "backend"); + + String content = preview.get("entity.java.vm"); + assertTrue(content.contains("import java.util.Date;")); + assertTrue(content.contains("import com.fasterxml.jackson.annotation.JsonFormat;")); + } + @Test public void previewCodeAppliesOnlyMatchingActiveSnippetToTemplateInsertPoint() { GenProject project = project(); @@ -166,6 +204,36 @@ public class GenProjectServiceImplTest { assertFalse(content.contains("public void wrong() {}")); } + @Test + public void previewCodeNormalizesResourceUrlProtocolChecks() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList(projectModule(1L))); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate(100L)); + when(templateFileService.selectTemplateFilesByTemplateId(100L)).thenReturn(Arrays.asList( + templateFile("index.vue.vm", + "if (/^(https?:)?\\/\\//.test(url) || url.indexOf(\"data:\") === 0 || url.indexOf(\"blob:\") === 0) {\n" + + " return url\n" + + "}\n" + + "if (/^(https?:)?///.test(url) || url.indexOf(\"data:\") === 0 || url.indexOf(\"blob:\") === 0) {\n" + + " return url\n" + + "}") + )); + + Map preview = service.previewCode(1L, 11L, "index.vue.vm", "backend"); + + String content = preview.get("index.vue.vm"); + assertFalse(content.contains("/^(https?:)?\\/\\//")); + assertFalse(content.contains("/^(https?:)?///")); + assertTrue(content.contains("url.indexOf(\"http://\") === 0")); + assertTrue(content.contains("url.indexOf(\"//\") === 0")); + } + @Test public void previewCodeExposesBusinessActionsForCurrentTableOnly() { GenProject project = project(); @@ -257,6 +325,95 @@ public class GenProjectServiceImplTest { assertFalse(content.contains("sync_role")); } + @Test + public void previewCodeEnrichesPageDesignedBusinessActionsWithRenderMetadata() { + GenProject project = project(); + BusinessActionDesign action = businessAction("borrow_book", "sys_user"); + action.setRequestFields(Arrays.asList("id", "user_id", "due_date")); + BusinessActionEffectDesign effect = new BusinessActionEffectDesign(); + effect.setType("INSERT_ROW"); + effect.setTargetTable("sys_user"); + Map values = new LinkedHashMap(); + values.put("user_id", "${current_user.id}"); + effect.setValues(values); + action.setEffects(Arrays.asList(effect)); + project.setBusinessActions(Arrays.asList(action)); + project.setPageDesigns(Arrays.asList(pageDesign("user_page", "sys_user", + "{\"canvas\":\"frontend-list-v1\",\"regions\":[" + + "{\"id\":\"toolbar\",\"type\":\"toolbar\",\"actions\":[\"business:borrow_book\"]}," + + "{\"id\":\"table\",\"type\":\"table\",\"fields\":[\"id\"],\"rowActions\":[]}" + + "]}"))); + GenTable table = table(11L, "sys_user", "User"); + GenTableColumn dueDate = column(11L, "due_date", "dueDate"); + dueDate.setColumnComment("Due date"); + dueDate.setHtmlType("datetime"); + table.setColumns(Arrays.asList(pkColumn(11L), column(11L, "user_id", "userId"), dueDate)); + project.setTables(Arrays.asList(table)); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(100L)); + when(templateFileService.selectTemplateFilesByTemplateId(100L)).thenReturn(Arrays.asList( + templateFile("list.vue.vm", + "#foreach($action in $pageDesignBusinessActions)" + + "trusted:#foreach($field in $action.currentUserFields)${field};#end" + + "#foreach($field in $action.inputFields)" + + "input:${field.field}:${field.label}:${field.htmlType};#end" + + "#end") + )); + + Map preview = service.previewCode(1L, 11L, "list.vue.vm", "frontend"); + + String content = preview.get("list.vue.vm"); + assertTrue(content.contains("trusted:user_id;")); + assertTrue(content.contains("input:due_date:Due date:datetime;")); + } + + @Test + public void previewCodeAppliesPageDesignBusinessButtonPresentation() { + GenProject project = project(); + project.setBusinessActions(Arrays.asList( + businessAction("approve_book", "sys_user") + )); + project.setPageDesigns(Arrays.asList(pageDesign("user_page", "sys_user", + "{\"canvas\":\"frontend-list-v1\"," + + "\"businessButtons\":{\"approve_book\":{" + + "\"label\":\"审核通过\"," + + "\"type\":\"success\"," + + "\"icon\":\"el-icon-check\"," + + "\"confirmMessage\":\"确认审核通过?\"," + + "\"successMessage\":\"已审核\"," + + "\"refreshTarget\":\"list\"" + + "}}," + + "\"regions\":[" + + "{\"id\":\"toolbar\",\"type\":\"toolbar\",\"actions\":[\"business:approve_book\"]}," + + "{\"id\":\"table\",\"type\":\"table\",\"fields\":[\"id\"],\"rowActions\":[]}" + + "]}"))); + GenTable table = table(11L, "sys_user", "User"); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList(projectModule(1L))); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(100L)); + when(templateFileService.selectTemplateFilesByTemplateId(100L)).thenReturn(Arrays.asList( + templateFile("list.vue.vm", + "#foreach($action in $pageDesignToolbarBusinessActions)" + + "${action.code}:${action.buttonLabel}:${action.buttonType}:${action.buttonIcon}:" + + "${action.confirmMessage}:${action.successMessage}:${action.refreshTarget};" + + "#end") + )); + + Map preview = service.previewCode(1L, 11L, "list.vue.vm", "frontend"); + + String content = preview.get("list.vue.vm"); + assertTrue(content.contains("approve_book:审核通过:success:el-icon-check:确认审核通过?:已审核:list;")); + } + @Test public void previewCodeUsesPageDesignedBusinessActionsFromOtherOwnerTables() { GenProject project = project(); @@ -316,16 +473,151 @@ public class GenProjectServiceImplTest { assertTrue(content.contains("portal-page")); assertTrue(content.contains("content-board")); assertTrue(content.contains("businessActionMap")); + assertTrue(content.contains("routeActionMap")); + assertTrue(content.contains("handleConfiguredRoute")); + } + + @Test + public void previewCodeUsesBundledFrontendIndexWhenDefaultRunnableTemplateLacksBusinessButtonConfig() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + TemplateFile staleTemplate = templateFile("frontend-index.vue.vm", + "#set($designed = $hasCurrentFrontendPageDesign)\n" + + "#set($renderForm = true)\n" + + "\n" + + ""); + staleTemplate.setTemplateId(9203L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(9203L)); + when(templateFileService.selectTemplateFilesByTemplateId(9203L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "frontend-index.vue.vm", "frontend"); + + String content = preview.get("frontend-index.vue.vm"); + assertFalse(content.contains("old business button template")); + assertTrue(content.contains("executeBusinessAction")); + assertTrue(content.contains("refreshBusinessActionTarget")); + assertTrue(content.contains("businessActionDialogVisible")); + assertTrue(content.contains("submitBusinessAction")); + assertTrue(content.contains("currentUserFields")); + assertFalse(content.contains("this.$prompt")); + } + + @Test + public void previewCodeUsesBundledFrontendIndexWhenDefaultRunnableTemplateHasSqlFragileRouteRegex() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + TemplateFile staleTemplate = templateFile("frontend-index.vue.vm", + "#set($designed = $hasCurrentFrontendPageDesign)\n" + + "#set($renderForm = true)\n" + + "\n" + + ""); + staleTemplate.setTemplateId(9203L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(9203L)); + when(templateFileService.selectTemplateFilesByTemplateId(9203L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "frontend-index.vue.vm", "frontend"); + + String content = preview.get("frontend-index.vue.vm"); + assertFalse(content.contains("old route regex template")); + assertFalse(content.contains("return /(^|\\/):[^/]+/.test(path) ? \"\" : path")); + assertTrue(content.contains("hasUnresolvedRouteParam(path)")); + } + + @Test + public void previewCodeUsesBundledFrontendIndexWhenDefaultRunnableTemplateCannotResolveUploadedResourceUrls() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + TemplateFile staleTemplate = templateFile("frontend-index.vue.vm", + "#set($designed = $hasCurrentFrontendPageDesign)\n" + + "#set($renderForm = true)\n" + + "\n" + + ""); + staleTemplate.setTemplateId(9203L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(9203L)); + when(templateFileService.selectTemplateFilesByTemplateId(9203L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "frontend-index.vue.vm", "frontend"); + + String content = preview.get("frontend-index.vue.vm"); + assertFalse(content.contains("")); + assertTrue(content.contains("resolveResourceUrl(url)")); + } + + @Test + public void previewCodeUsesBundledFrontendAppWhenDefaultRunnableTemplateHasBrokenNavBinding() { + GenProject project = project(); + GenTable table = table(11L, "sys_user", "User"); + FrontProjectPageDesign home = new FrontProjectPageDesign(); + home.setPageCode("home"); + home.setPageName("Home"); + home.setRoutePath("/home"); + home.setPageScope("frontend"); + home.setPageType("list"); + home.setTableName("sys_user"); + home.setLayoutJson("{\"navigation\":{\"showInMenu\":true,\"menuLevel\":\"primary\",\"menuCode\":\"home\",\"menuName\":\"Home\",\"menuOrder\":1},\"regions\":[]}"); + project.setPageDesigns(Arrays.asList(home)); + TemplateFile staleTemplate = templateFile("frontend-App.vue.vm", + ""); + staleTemplate.setTemplateId(9203L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "frontend")).thenReturn(projectTemplate(9203L)); + when(templateFileService.selectTemplateFilesByTemplateId(9203L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "frontend-App.vue.vm", "frontend"); + + String content = preview.get("frontend-App.vue.vm"); + assertTrue(content.contains("portal-shell")); + assertFalse(content.contains(":class=\"{ active: isNavGroupActive(")); } @Test public void previewCodeUsesBundledControllerWhenDefaultRunnableTemplateIsStale() { GenProject project = project(); + BusinessActionDesign action = businessAction("borrow_book", "sys_user"); + action.setRequestFields(Arrays.asList("user_id")); + BusinessActionEffectDesign effect = new BusinessActionEffectDesign(); + effect.setType("INSERT_ROW"); + effect.setTargetTable("sys_user"); + Map values = new LinkedHashMap(); + values.put("user_id", "${current_user.id}"); + effect.setValues(values); + action.setEffects(Arrays.asList(effect)); + project.setBusinessActions(Arrays.asList(action)); GenTable table = table(11L, "sys_user", "User"); table.setColumns(Arrays.asList(pkColumn(11L), column(11L, "user_id", "userId"))); table.setPkColumn(table.getColumns().get(0)); TemplateFile staleTemplate = templateFile("controller.java.vm", - "package ${packageName}.controller; public class ${ClassName}Controller {}"); + "package ${packageName}.controller; " + + "// __currentUserOnly prepareBusinessActionParams\n" + + "public class ${ClassName}Controller {}"); staleTemplate.setTemplateId(9201L); when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); @@ -340,8 +632,99 @@ public class GenProjectServiceImplTest { String content = preview.get("controller.java.vm"); assertFalse(content.contains("public class UserController {}")); - assertTrue(content.contains("@GetMapping(\"/page\")")); + assertTrue(content.contains("@GetMapping(\"/system/user/page\")")); assertTrue(content.contains("__currentUserOnly")); + assertTrue(content.contains("new String[] {\"user_id\"}")); + assertTrue(content.contains("actionParams.put(field, currentUser.getUserId())")); + } + + @Test + public void previewCodeUsesBundledBusinessServiceWhenDefaultRunnableTemplateIsStale() { + GenProject project = project(); + project.setBusinessActions(Arrays.asList(businessAction("borrow_book", "sys_user"))); + GenTable table = table(11L, "sys_user", "User"); + table.setColumns(Arrays.asList(pkColumn(11L), column(11L, "user_id", "userId"))); + table.setPkColumn(table.getColumns().get(0)); + TemplateFile staleTemplate = templateFile("service.java.vm", + "package ${packageName}.service; public interface I${ClassName}Service {}"); + staleTemplate.setTemplateId(9201L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate(9201L)); + when(templateFileService.selectTemplateFilesByTemplateId(9201L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "service.java.vm", "backend"); + + String content = preview.get("service.java.vm"); + assertTrue(content.contains("int borrow_book(Map params)")); + } + + @Test + public void previewCodeUsesBundledBusinessServiceImplWhenDefaultRunnableTemplateIsStale() { + GenProject project = project(); + project.setBusinessActions(Arrays.asList(businessAction("borrow_book", "sys_user"))); + GenTable table = table(11L, "sys_user", "User"); + table.setColumns(Arrays.asList(pkColumn(11L), column(11L, "user_id", "userId"))); + table.setPkColumn(table.getColumns().get(0)); + TemplateFile staleTemplate = templateFile("serviceImpl.java.vm", + "package ${packageName}.service.impl; " + + "// @Transactional resolveBusinessValue\n" + + "public class ${ClassName}ServiceImpl {}"); + staleTemplate.setTemplateId(9201L); + + when(genProjectMapper.selectGenProjectById(1L)).thenReturn(project); + when(genTableMapper.selectGenTablesByProjectId(1L)).thenReturn(Arrays.asList(table)); + when(genTableMapper.selectGenTableById(11L)).thenReturn(table); + when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList()); + when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList()); + when(sysProjectTemplateService.selectSysProjectTemplateByProjectAndType(1L, "backend")).thenReturn(projectTemplate(9201L)); + when(templateFileService.selectTemplateFilesByTemplateId(9201L)).thenReturn(Arrays.asList(staleTemplate)); + + Map preview = service.previewCode(1L, 11L, "serviceImpl.java.vm", "backend"); + + String content = preview.get("serviceImpl.java.vm"); + assertTrue(content.contains("@Transactional(rollbackFor = Exception.class)")); + assertTrue(content.contains("resolveBusinessValue")); + assertTrue(content.contains("STRICT_TARGET_TABLE_CONDITIONS")); + } + + @Test + public void previewCodeRejectsSavedBusinessActionConditionOutsideTargetTable() { + GenProject project = project(); + GenTable bookTable = table(21L, "book_info", "Book"); + bookTable.setColumns(Arrays.asList( + pkColumn(21L), + column(21L, "available_quantity", "availableQuantity"))); + bookTable.setPkColumn(bookTable.getColumns().get(0)); + GenTable borrowTable = table(22L, "borrow_info", "Borrow"); + borrowTable.setColumns(Arrays.asList( + pkColumn(22L), + column(22L, "book_id", "bookId"))); + borrowTable.setPkColumn(borrowTable.getColumns().get(0)); + project.setTables(Arrays.asList(bookTable, borrowTable)); + + BusinessActionDesign action = businessAction("borrow_book", "book_info"); + action.setRequestFields(Arrays.asList("book_id")); + BusinessActionRuleDesign rule = new BusinessActionRuleDesign(); + rule.setType("NUMBER_GTE"); + rule.setTargetTable("book_info"); + rule.setTargetField("available_quantity"); + rule.setCompareValue("1"); + rule.setConditionFields(Arrays.asList("book_id")); + action.setRuleChecks(Arrays.asList(rule)); + project.setBusinessActions(Arrays.asList(action)); + + try { + service.previewCode(project, 21L, "serviceImpl.java.vm", "backend"); + fail("Expected invalid saved business action to be rejected"); + } catch (ServiceException e) { + assertEquals("Business rule condition field does not exist on target table book_info: book_id", + e.getMessage()); + } } @Test @@ -737,6 +1120,53 @@ public class GenProjectServiceImplTest { assertTrue(paths.contains("demo-app/src/api/business-blocks/cart_001.js")); } + @Test + public void getProjectStructureAddsAdminBusinessBlockPageDesignFiles() { + GenProject project = project(); + project.setFrontDraft(true); + FrontProjectPageDesign home = pageDesign("admin_home", "", + "{\"canvas\":\"business-blocks-v1\",\"blocks\":[],\"navigation\":{\"showInMenu\":true,\"menuOrder\":1}}"); + home.setPageScope("admin"); + home.setPageName("Home"); + home.setRoutePath("/admin/home"); + home.setPageType("business_block"); + project.setPageDesigns(Arrays.asList(home)); + + when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(template(9202L, 0L))); + when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList( + structure(1L, 0L, "{projectName}-admin", "folder", null, null), + structure(2L, 1L, "src", "folder", null, null) + )); + + List> structure = service.getProjectStructure(project, "admin_frontend"); + + Set paths = structurePaths(structure); + assertTrue(paths.contains("demo-app-admin/src/views/pages/admin_home/index.vue")); + } + + @Test + public void adminChartFilesAppearInPreviewAndDownloadStructure() { + GenProject project = chartBusinessBlockProject(); + project.setFrontDraft(true); + + when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList( + template(9202L, 0L), + template(9201L, 0L))); + when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))) + .thenReturn(Arrays.asList( + structure(1L, 0L, "{projectName}-admin", "folder", null, null), + structure(2L, 1L, "src", "folder", null, null))); + + Set frontendPaths = structurePaths(service.getProjectStructure(project, "admin_frontend")); + Set backendPaths = structurePaths(service.getProjectStructure(project, "backend")); + + assertTrue(frontendPaths.contains("demo-app-admin/src/utils/chartRuntime.js")); + assertTrue(frontendPaths.contains( + "demo-app-admin/src/views/pages/admin_home/blocks/admin_line_chart_001/AdminLineChart001Block.vue")); + assertTrue(backendPaths.contains( + "demo-app-admin/src/main/resources/mapper/block/AdminLineChart001BlockMapper.xml")); + } + @Test public void getProjectStructureAddsNormalFrontendPageDesignFiles() { GenProject project = project(); @@ -795,6 +1225,29 @@ public class GenProjectServiceImplTest { assertTrue(entries.contains("demo-app/src/api/business-blocks/cart_001.js")); } + @Test + public void downloadStructureSkipsDuplicateDirectoryEntriesFromEquivalentPaths() throws Exception { + GenProject project = project(); + project.setFrontDraft(true); + project.setTables(Arrays.asList()); + + when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(template(9201L, 0L))); + when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList( + structure(1L, 0L, "demo", "folder", null, null), + structure(2L, 1L, "src", "folder", null, null), + structure(3L, 2L, "com/example", "folder", null, null), + structure(4L, 3L, "controller", "folder", null, null), + structure(5L, 2L, "com", "folder", null, null), + structure(6L, 5L, "example", "folder", null, null), + structure(7L, 6L, "controller", "folder", null, null) + )); + + byte[] data = service.downloadStructure(project, "backend"); + + Set entries = zipEntries(data); + assertTrue(entries.contains("demo/src/com/example/controller/")); + } + @SuppressWarnings("unchecked") private Map child(Map node, int index) { return children(node).get(index); @@ -980,6 +1433,37 @@ public class GenProjectServiceImplTest { return project; } + private GenProject chartBusinessBlockProject() { + GenProject project = project(); + GenTable orders = table(203L, "shop_order", "ShopOrder"); + orders.setColumns(Arrays.asList( + pkColumn(203L), + column(203L, "amount", "amount"), + column(203L, "status", "status"), + column(203L, "create_time", "createTime"))); + orders.getColumns().get(1).setJavaType("BigDecimal"); + orders.getColumns().get(1).setColumnType("decimal(10,2)"); + orders.getColumns().get(3).setJavaType("Date"); + orders.getColumns().get(3).setColumnType("datetime"); + orders.setPkColumn(orders.getColumns().get(0)); + project.setTables(Arrays.asList(orders)); + + FrontProjectPageDesign design = pageDesign("admin_home", "", + "{\"canvas\":\"business-blocks-v1\",\"blocks\":[" + + "{\"id\":\"admin_line_chart_001\",\"blockCode\":\"admin_line_chart\",\"span\":6," + + "\"dataset\":{\"schema\":\"single-table-aggregate-v1\",\"table\":\"shop_order\"," + + "\"dimension\":{\"field\":\"createTime\",\"timeUnit\":\"day\"}," + + "\"metrics\":[{\"field\":\"amount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\",\"label\":\"Sales\"}]," + + "\"filters\":[{\"field\":\"status\",\"operator\":\"eq\",\"value\":\"PAID\"}]," + + "\"sort\":{\"by\":\"dimension\",\"order\":\"asc\"},\"limit\":100}," + + "\"display\":{\"title\":\"Sales Trend\"}}]}"); + design.setPageScope("admin"); + design.setPageType("business_block"); + design.setRoutePath("/admin/home"); + project.setPageDesigns(Arrays.asList(design)); + return project; + } + private FrontProjectPageDesign businessBlockPageDesign() { FrontProjectPageDesign design = pageDesign("cart_page", "shop_cart", "{\"canvas\":\"business-blocks-v1\",\"blocks\":[{\"id\":\"cart_001\",\"blockCode\":\"cart\",\"config\":{"