feat: ai test one-click runner, result selection, audit docs
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,432 @@
|
||||
# WorkUtils 插件 UI 与代码可维护性审查
|
||||
|
||||
审查日期:2026-07-30
|
||||
|
||||
## 1. 结论
|
||||
|
||||
当前产品已经形成统一的左侧导航、颜色和基础按钮风格,但各插件仍是“每个 Window 自己搭 UI、自己开线程、自己管理 loading、自己处理错误”的实现方式。视觉统一停留在颜色层,交互状态和代码结构没有统一。
|
||||
|
||||
主要风险:
|
||||
|
||||
1. `Window` 类过大:8 个窗口平均约 824 行,`MobileUiAutomationWindow` 达 1702 行。
|
||||
2. 异步状态没有统一模型:存在 `new Thread`、`Task`、`CompletableFuture`、布尔字段和手动 `Platform.runLater` 等多套实现。
|
||||
3. loading 行为不一致:有的只禁用按钮,有的改文字,有的显示旋转图标,有的同时显示停止按钮;异常分支容易漏恢复状态。
|
||||
4. UI 直接暴露敏感配置:Git 远程 URL 显示账号,数据库生成器显示完整 JDBC URL 和用户名。
|
||||
5. 操作层级不清:刷新、执行、交换、终止、删除等按钮经常处于同一视觉层级。
|
||||
6. 空状态普遍只是大片空白,用户无法判断是“尚未执行”“没有数据”还是“执行失败”。
|
||||
7. UI、业务编排、文件操作、历史记录和线程控制混在同一个类里,AI 修改局部功能时需要理解整个窗口,误改概率高。
|
||||
|
||||
建议先建设公共 UI/异步基础设施,再逐插件迁移。不要同时重写所有插件。
|
||||
|
||||
## 2. 统一设计与代码基线
|
||||
|
||||
### 2.1 页面结构
|
||||
|
||||
所有插件统一为:
|
||||
|
||||
1. `PageHeader`:标题、简短说明、帮助入口。
|
||||
2. `ConfigCard`:输入与选项。
|
||||
3. `ActionBar`:一个主操作、零到两个次操作、一个条件显示的取消操作。
|
||||
4. `OperationStatus`:阶段、进度、耗时、当前处理对象。
|
||||
5. `ResultArea`:结果、空状态、失败状态。
|
||||
6. `HistoryTable`:历史记录放在独立 Tab 或折叠区,不抢占主任务空间。
|
||||
|
||||
### 2.2 按钮状态
|
||||
|
||||
按钮不应由业务代码直接反复调用 `setText`、`setDisable`、`setGraphic`。统一使用状态:
|
||||
|
||||
```java
|
||||
public enum OperationState {
|
||||
IDLE, VALIDATING, RUNNING, CANCELLING, SUCCEEDED, FAILED
|
||||
}
|
||||
```
|
||||
|
||||
统一按钮规范:
|
||||
|
||||
| 状态 | 主按钮 | 取消按钮 | 输入区 |
|
||||
|---|---|---|---|
|
||||
| IDLE | 可用,显示动作名称 | 隐藏 | 可编辑 |
|
||||
| VALIDATING | spinner +“校验中” | 隐藏 | 禁用 |
|
||||
| RUNNING | spinner + 阶段名称 | 显示 | 禁用 |
|
||||
| CANCELLING | “正在停止…” | 禁用 | 禁用 |
|
||||
| SUCCEEDED | 恢复动作名称 | 隐藏 | 可编辑 |
|
||||
| FAILED | 恢复动作名称 | 隐藏 | 可编辑 |
|
||||
|
||||
建议增加公共组件:
|
||||
|
||||
```java
|
||||
public final class AsyncAction {
|
||||
private final ReadOnlyObjectWrapper<OperationState> state =
|
||||
new ReadOnlyObjectWrapper<>(OperationState.IDLE);
|
||||
private final StringProperty message = new SimpleStringProperty("");
|
||||
private final DoubleProperty progress = new SimpleDoubleProperty(-1);
|
||||
|
||||
public <T> void run(
|
||||
Supplier<T> work,
|
||||
Consumer<T> onSuccess,
|
||||
Consumer<Throwable> onFailure) {
|
||||
if (state.get() == OperationState.RUNNING) return;
|
||||
state.set(OperationState.RUNNING);
|
||||
|
||||
Task<T> task = new Task<>() {
|
||||
@Override protected T call() { return work.get(); }
|
||||
};
|
||||
task.setOnSucceeded(e -> finish(() -> onSuccess.accept(task.getValue())));
|
||||
task.setOnFailed(e -> finish(() -> onFailure.accept(task.getException())));
|
||||
AppExecutors.io().execute(task);
|
||||
}
|
||||
|
||||
private void finish(Runnable callback) {
|
||||
try {
|
||||
callback.run();
|
||||
} finally {
|
||||
state.set(OperationState.IDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`LoadingButtonBinder` 只负责展示:
|
||||
|
||||
```java
|
||||
LoadingButtonBinder.bind(
|
||||
exportButton,
|
||||
exportAction.stateProperty(),
|
||||
"开始导出",
|
||||
"导出中…"
|
||||
);
|
||||
```
|
||||
|
||||
这样异常、提前返回和取消都不会遗漏恢复按钮。
|
||||
|
||||
### 2.3 推荐的插件目录边界
|
||||
|
||||
```text
|
||||
biz/<plugin>/
|
||||
<Plugin>.java # 插件元数据和装配
|
||||
application/
|
||||
<UseCase>.java # 一次用户任务的编排
|
||||
<Request>.java
|
||||
<Result>.java
|
||||
domain/
|
||||
... # 纯业务模型和规则
|
||||
infrastructure/
|
||||
... # Git、DB、文件、进程等外部访问
|
||||
ui/
|
||||
<Plugin>Pane.java # 只组装区域
|
||||
<Plugin>ViewModel.java # 可观察状态与命令
|
||||
components/
|
||||
...
|
||||
```
|
||||
|
||||
限制规则:
|
||||
|
||||
- `Pane` 不直接创建线程、访问文件、调用 Git/DB 或拼接业务结果。
|
||||
- `ViewModel` 不创建 JavaFX 节点。
|
||||
- `UseCase` 不依赖 JavaFX。
|
||||
- 表格 cell 不直接执行文件操作,调用明确的 command。
|
||||
- 单文件建议不超过 350 行;超过 500 行必须拆分。
|
||||
- 每个异步操作只有一个拥有者,禁止窗口和 Service 同时管理线程生命周期。
|
||||
|
||||
## 3. 主框架
|
||||
|
||||
证据:`screenshots/00-home.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 欢迎页信息价值低,大片空白;没有最近任务、常用插件或配置健康状态。
|
||||
- 顶部只有重复的插件图标和名称,无法承载状态、帮助或全局通知。
|
||||
- 导航图标来源不统一,部分图标显示风格和宽度不一致。
|
||||
- 插件说明只存在元数据中,导航和页面内没有有效呈现。
|
||||
|
||||
### 重构
|
||||
|
||||
- 欢迎页改为“最近使用 + 配置状态 + 快捷入口”,不要重复罗列功能名称。
|
||||
- `MainWindow` 只负责路由与壳层;拆出 `NavigationPane`、`WorkspaceHeader`、`WelcomePane`。
|
||||
- 增加统一 `NotificationCenter`,插件错误使用页内 banner 或 toast,不要各自弹 Alert。
|
||||
- 用图标库统一图标,不再把 Emoji 当 UI 图标。
|
||||
|
||||
## 4. Git 仓库同步
|
||||
|
||||
证据:`screenshots/01-git-sync.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 项目表直接展示含账号的远程 URL,既敏感又占据大量宽度。
|
||||
- “Git …”列标题被截断,列宽策略失效。
|
||||
- 69 个项目没有搜索、状态筛选和批量选择反馈。
|
||||
- “同步执行”和“损坏仓库处理”以 Tab 分割,但用户无法看到同步任务与项目选择的关系。
|
||||
- loading 主要靠禁用“同步”和启用“停止”,缺少总进度、当前项目、成功/失败计数。
|
||||
|
||||
### 设计
|
||||
|
||||
- 项目列表顶部:搜索、状态筛选、`已选择 N 项`。
|
||||
- URL 默认显示 `host/group/repo`,账号信息掩码;完整值仅通过“复制地址”获得。
|
||||
- 同步操作采用固定底部 ActionBar:`同步选中项目` + 条件显示 `停止`。
|
||||
- 运行时展示:总进度、当前仓库、成功/跳过/失败计数;失败项留在结果列表。
|
||||
- “损坏仓库处理”作为失败结果的后续动作,而不是并列一级 Tab。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- `GitSyncWindow` 拆为 `ProjectTablePane`、`SyncProgressPane`、`DamagedRepoPane`。
|
||||
- 从 `GitSyncPlugin` 中迁出 Git 命令、进程输出和 URL 解析,形成 `GitRepositoryGateway`。
|
||||
- `SyncRepositoriesUseCase` 返回结构化事件,不向 UI 传原始日志字符串:
|
||||
|
||||
```java
|
||||
sealed interface SyncEvent {
|
||||
record Started(int total) implements SyncEvent {}
|
||||
record RepositoryStarted(String repository, int index) implements SyncEvent {}
|
||||
record RepositoryFinished(String repository, SyncOutcome outcome) implements SyncEvent {}
|
||||
record Completed(SyncSummary summary) implements SyncEvent {}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Git 分支评审
|
||||
|
||||
证据:`screenshots/02-git-review.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 页面信息密度过高,“粘贴识别”“手动字段”“IDEA 命令”“执行日志”同时出现。
|
||||
- 主按钮“准备评审”含义不明确,实际上会拉取、切分支并打开 IDEA,副作用较大。
|
||||
- 来源/目标依靠 A、B 解释,用户仍容易反向操作。
|
||||
- 运行时 `runButton` 和 `historyOpenButton` 被同一 `running` 状态控制,两个无关动作耦合。
|
||||
- 日志区默认空白,缺少阶段式反馈。
|
||||
|
||||
### 设计
|
||||
|
||||
- 改为三步:①识别评审信息;②确认仓库和分支方向;③执行并查看结果。
|
||||
- 主按钮文案明确为“切换到目标分支并打开评审”。
|
||||
- 在执行前展示摘要:`来源 A → 目标 B`、将执行的 Git 操作、工作区是否干净。
|
||||
- 每个操作拥有独立状态:识别、刷新、执行、打开历史互不锁死。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- `ReviewTextParser` 独立且纯函数化。
|
||||
- `GitReviewViewModel` 持有表单和 `reviewAction`。
|
||||
- `GitReviewService` 拆成 `GitStatusReader`、`BranchPreparationService`、`IdeaLauncher`。
|
||||
- 用 `ReviewPlan` 表达执行前计划,使 UI 可预览,也便于单测。
|
||||
|
||||
## 6. 文本对比
|
||||
|
||||
证据:`screenshots/03-diff.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 空状态完全空白,没有示例或输入提示。
|
||||
- 结果区在未比较时仍占据大半页面。
|
||||
- “开始对比”对于本地 CPU 操作没有 loading;大文本可能冻结 JavaFX 线程。
|
||||
- 上一处/下一处在无结果时只是灰色,未解释差异数量。
|
||||
- 原文/对比文缺少文件导入、编码和换行符信息。
|
||||
|
||||
### 设计
|
||||
|
||||
- 输入区增加拖入文件/粘贴入口和字符、行数统计。
|
||||
- 首次使用显示简短示例;比较后将结果区扩展。
|
||||
- ActionBar 显示 `发现 N 处差异`,导航按钮显示当前位置 `3/18`。
|
||||
- 大文本比较进入后台任务,按钮显示“比较中…”,并支持取消。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- 当前 `DiffWindow` 同时包含 LCS、字符分段、HTML 渲染和 UI,应拆为:
|
||||
- `DiffEngine`
|
||||
- `DiffResult`
|
||||
- `DiffHtmlRenderer`
|
||||
- `DiffViewModel`
|
||||
- `DiffPane`
|
||||
- `DiffResult` 保留结构化行和字符片段,UI 不再解析 HTML 来导航。
|
||||
|
||||
## 7. MyBatis SQL 导出
|
||||
|
||||
证据:`screenshots/04-mybatis-export.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- “执行日志”在未执行时是空白大框;历史表反而成为页面视觉重点。
|
||||
- “导出/终止”始终并列,终止按钮即使禁用也持续占位。
|
||||
- 缺少项目范围、目标目录和预估数量摘要。
|
||||
- 历史表列标题被截断,文件名过长,核心信息难扫描。
|
||||
- loading 有进度文本,但没有进度条和错误计数。
|
||||
|
||||
### 设计
|
||||
|
||||
- 配置卡显示过滤条件、项目范围、输出目录。
|
||||
- 主按钮为“导出 SQL”;运行后才出现“停止”。
|
||||
- 状态区显示 `12/69`、当前项目、已导出 SQL 数、错误数。
|
||||
- 历史记录放第二 Tab,文件名单行省略,Tooltip 展示完整路径。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- `MyBatisExportWindow` 和 `ScannerWindow` 的历史表、打开/目录菜单高度重复,提取 `ExportHistoryPane<T>`。
|
||||
- 用 `ExportRequest` 替代多个散落参数。
|
||||
- `MyBatisExportService` 通过 `ProgressSink<ExportProgress>` 上报结构化进度。
|
||||
|
||||
## 8. MyBatis 逆向工程
|
||||
|
||||
证据:`screenshots/05-mybatis-generator.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 顶部展示完整 JDBC URL 和用户名,存在泄露风险。
|
||||
- 左侧项目树与右侧表查询、输出目录并排,用户任务顺序不清。
|
||||
- 三个输出路径通常相关,却要求逐项浏览/填充。
|
||||
- “取消”实际是关闭窗口,在嵌入模式下语义不成立。
|
||||
- 查询表和生成操作没有统一 loading,也缺少连接失败后的就地恢复。
|
||||
|
||||
### 设计
|
||||
|
||||
- 分成 ①数据源 ②选择表 ③生成配置 ④确认生成 四个区域或步骤。
|
||||
- 数据源仅显示别名、数据库名、主机掩码和连接状态,完整连接串只在设置页维护。
|
||||
- 输出目录选择一个项目根目录,再实时预览三个派生目录。
|
||||
- “生成”前显示将覆盖/创建的文件摘要;完成后展示文件列表和打开目录。
|
||||
- 嵌入模式去掉“取消”,改为“重置配置”。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- `GeneratorFormState` 统一持有表选择与目录规则。
|
||||
- `DatasourceSummary` 是脱敏 DTO,UI 禁止接触密码和原始 URL。
|
||||
- `QueryTablesUseCase` 与 `GenerateArtifactsUseCase` 使用两个独立 `AsyncAction`。
|
||||
- 路径推导放入 `OutputLayoutPolicy`,避免三个按钮各自维护逻辑。
|
||||
|
||||
## 9. 扫描功能
|
||||
|
||||
证据:`screenshots/06-scanner.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 页面写“扫描类型:关键词扫描”,但描述称支持关键词/Redis/Dubbo,入口和能力不一致。
|
||||
- 正则匹配只是一个 Checkbox,缺少风险提示和表达式校验。
|
||||
- 当前状态只显示“就绪”,位置和视觉权重过低。
|
||||
- 历史表抢占主页面,空白结果/当前任务没有清晰区分。
|
||||
- 开始扫描只禁用按钮,不展示 spinner、进度或取消能力。
|
||||
|
||||
### 设计
|
||||
|
||||
- 扫描类型使用单选卡或下拉,并根据类型切换专属字段。
|
||||
- 正则模式提供即时校验和示例。
|
||||
- 执行区域展示扫描目录、项目数、匹配数和当前项目。
|
||||
- 当前结果与历史记录分 Tab。
|
||||
- 主按钮 loading 文案为“扫描中 12/69”,提供取消。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- 将 `ScanConfig` 升级为按类型区分的 sealed request,避免大量可空字段。
|
||||
- `ScannerService` 不使用默认 `CompletableFuture` 线程池,统一注入受控 Executor。
|
||||
- `ScannerWindow` 与历史文件操作解耦,复用公共历史组件。
|
||||
|
||||
## 10. API 接口文档导出
|
||||
|
||||
证据:`screenshots/07-api-doc.png`
|
||||
|
||||
### UI 问题
|
||||
|
||||
- 三张模式卡占据大量空间,但下方关键输入没有标签,依赖 placeholder。
|
||||
- “全限定名”“批量导出”的说明文字对非作者用户不够明确。
|
||||
- 主按钮横跨整个页面,视觉重量过大。
|
||||
- 导出配置、导出历史、基类配置同属一级 Tab,但基类配置是低频系统设置。
|
||||
- 文件/目录选择也使用 loading,操作通常瞬时完成,产生假的忙碌感。
|
||||
- loading 通过节点属性保存旧文字,且部分代码再次直接覆盖文字,状态来源不唯一。
|
||||
|
||||
### 设计
|
||||
|
||||
- 模式切换改为紧凑 Segmented Control。
|
||||
- 每种模式只显示相关字段,并有明确 Label、示例和校验错误。
|
||||
- 主按钮右对齐,显示导出范围摘要。
|
||||
- 基类配置移入系统设置或高级设置。
|
||||
- 只有解析/生成过程进入 loading;文件选择不显示 spinner。
|
||||
- 结果页显示生成文件、接口数、方法数、耗时和“打开/复制路径”。
|
||||
|
||||
### 代码重构
|
||||
|
||||
- `ApiDocWindow` 拆成 `ExportConfigPane`、`ExportHistoryPane`、`BaseClassConfigPane`。
|
||||
- `ExportMode` 各自实现 request 构造,避免 `startExport` 中按模式分支。
|
||||
- `ApiDocExportUseCase` 负责 parse/generate/history,UI 只提交 `ApiDocExportRequest`。
|
||||
- 删除窗口内 `setBtnLoading`,接入统一 `AsyncAction`。
|
||||
- HTML 调试代理作为单独能力开关,不在生成 HTML 时隐式启动。
|
||||
|
||||
## 11. 手机 UI 自动化
|
||||
|
||||
运行时当前禁用,结论基于源码;不能作为截图可验证的 UI 结论。
|
||||
|
||||
### 主要问题
|
||||
|
||||
- `MobileUiAutomationWindow` 1702 行、67 个方法、30 个 action、36 处线程/回调相关代码,是最高风险文件。
|
||||
- 环境安装、设备管理、用例运行、录制、截图轮询、步骤编辑、报告历史全部集中在一个类。
|
||||
- 使用 `isRunning`、按钮文字和内联 style 共同表达状态,容易不一致。
|
||||
- 截图轮询拥有独立线程,窗口生命周期与线程停止逻辑难验证。
|
||||
- “运行/停止”和“开始录制/停止并生成用例”通过改文字复用按钮,状态机不明确。
|
||||
|
||||
### 设计
|
||||
|
||||
- 一级拆为“运行用例”“录制用例”“环境与设备”三个页面。
|
||||
- 顶部设备条只显示当前设备和健康状态;安装工具放入环境页。
|
||||
- 运行页显示用例、参数、实时步骤和报告;录制页聚焦截图与步骤列表。
|
||||
- 运行和录制使用显式状态机,停止按钮独立出现。
|
||||
|
||||
### 代码重构
|
||||
|
||||
至少拆成:
|
||||
|
||||
```text
|
||||
mobileuiauto/ui/
|
||||
MobileAutomationPane.java
|
||||
DeviceBar.java
|
||||
run/RunCasePane.java
|
||||
run/RunCaseViewModel.java
|
||||
record/RecordCasePane.java
|
||||
record/RecordCaseViewModel.java
|
||||
environment/EnvironmentPane.java
|
||||
environment/EnvironmentViewModel.java
|
||||
```
|
||||
|
||||
新增两个状态机:
|
||||
|
||||
```java
|
||||
enum RunState { IDLE, STARTING, RUNNING, STOPPING, SUCCEEDED, FAILED }
|
||||
enum RecordState { IDLE, STARTING, RECORDING, GENERATING, FAILED }
|
||||
```
|
||||
|
||||
截图轮询封装为 `ScreenshotStream extends AutoCloseable`,由录制 ViewModel 持有并在页面离开时关闭。
|
||||
|
||||
## 12. 系统设置
|
||||
|
||||
### UI 问题
|
||||
|
||||
- Tab 数量多但缺少分类,配置文件编辑依赖外部编辑器。
|
||||
- “重新加载”的反馈不明确。
|
||||
- 数据源、项目路径、插件管理本质是结构化配置,不应主要依赖文本文件。
|
||||
|
||||
### 重构
|
||||
|
||||
- 分类为“常规 / 项目与数据源 / 插件 / 外观”。
|
||||
- 配置项使用表单并提供校验、保存成功状态和恢复默认。
|
||||
- 所有敏感字段支持显示/隐藏;列表页面只使用脱敏 DTO。
|
||||
|
||||
## 13. 建议实施顺序
|
||||
|
||||
1. 建立 `OperationState`、`AsyncAction`、`LoadingButtonBinder`、`OperationStatusPane`。
|
||||
2. 提取 `ExportHistoryPane`、`EmptyStatePane`、`NotificationCenter`。
|
||||
3. 先迁移 Scanner 和 MyBatis SQL 导出,验证公共组件。
|
||||
4. 迁移 API 文档导出和 MyBatis 逆向工程。
|
||||
5. 拆分 Git 同步和 Git 评审。
|
||||
6. 抽离 DiffEngine。
|
||||
7. 最后单独重构手机 UI 自动化,不与其他插件并行大改。
|
||||
|
||||
## 14. AI 友好编码约束
|
||||
|
||||
- 每个需求先定位到一个 UseCase 和一个 ViewModel,避免直接修改 Window。
|
||||
- 不允许新增裸 `new Thread`。
|
||||
- 不允许在业务代码直接调用 `Button#setText/setDisable/setGraphic`。
|
||||
- 不允许使用 `Node#getProperties()` 保存业务状态。
|
||||
- 不允许在 Window 中出现 Git、SQL、HTTP、进程命令或文件遍历。
|
||||
- 所有跨线程结果必须是不可变 DTO。
|
||||
- 所有状态使用 enum,不使用多个 boolean 拼状态。
|
||||
- 每个 ViewModel 为每个 action 提供独立状态,禁止一个 `running` 锁住无关按钮。
|
||||
- 为 Request 校验、状态迁移、UseCase 成功/失败/取消路径编写单元测试。
|
||||
- UI 测试只验证绑定和可见性,不重复测试业务逻辑。
|
||||
|
||||
## 15. 证据范围
|
||||
|
||||
已实际捕获并复核:主界面、Git 同步、Git 分支评审、文本对比、MyBatis SQL 导出、MyBatis 逆向工程、扫描、API 文档导出。
|
||||
|
||||
未实际触发具有副作用的同步、切分支、数据库查询、生成、导出和删除操作,因此 loading、取消、错误恢复的结论同时参考了源码。手机 UI 自动化因当前配置禁用,仅完成源码审查。截图不能证明完整键盘访问、焦点顺序、屏幕阅读器语义或颜色对比合规,这些需要专项测试。
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 70 KiB |
@@ -1,6 +1,7 @@
|
||||
package com.example.workutils.biz.aitest;
|
||||
|
||||
import com.example.workutils.biz.aitest.application.AiTestProgress;
|
||||
import com.example.workutils.biz.aitest.application.AiTestOneClickRunner;
|
||||
import com.example.workutils.biz.aitest.application.AiTestHistoryService;
|
||||
import com.example.workutils.biz.aitest.application.AiTestRunSummary;
|
||||
import com.example.workutils.biz.aitest.application.AiTestRequest;
|
||||
@@ -18,6 +19,7 @@ import com.example.workutils.biz.aitest.project.MavenProjectAnalyzer;
|
||||
import com.example.workutils.biz.aitest.project.MavenProjectModel;
|
||||
import com.example.workutils.biz.aitest.project.ModuleLoadPresentation;
|
||||
import com.example.workutils.biz.aitest.ui.DirectoryPathSelector;
|
||||
import com.example.workutils.biz.aitest.ui.AiTestResultSelection;
|
||||
import com.example.workutils.biz.aitest.ui.TaskProgressState;
|
||||
import com.example.workutils.system.support.AppExecutors;
|
||||
import javafx.application.Platform;
|
||||
@@ -43,6 +45,7 @@ public class AiTestPane extends BorderPane {
|
||||
private final AiTestModulePreferenceService modulePreferences;
|
||||
private final AiTestHistoryService historyService;
|
||||
private final ToolchainValidator toolchainValidator;
|
||||
private final AiTestOneClickRunner oneClickRunner;
|
||||
|
||||
private final ComboBox<AiTestProject> projectCombo = new ComboBox<>();
|
||||
private final ComboBox<MavenModule> moduleCombo = new ComboBox<>();
|
||||
@@ -58,8 +61,8 @@ public class AiTestPane extends BorderPane {
|
||||
private final Spinner<Integer> repairRounds = new Spinner<>(1, 10, 3);
|
||||
private final TextArea defaultSpecification = new TextArea();
|
||||
private final TextArea projectSpecification = new TextArea();
|
||||
private final Button analyzeButton = new Button("分析增量");
|
||||
private final Button generateButton = new Button("生成并验证");
|
||||
private final Button analyzeButton = new Button("仅分析增量");
|
||||
private final Button generateButton = new Button("生成单测并分析覆盖率");
|
||||
private final Button cancelButton = new Button("取消");
|
||||
private final Button integrateButton = new Button("合并到本地分支");
|
||||
private final ProgressBar progressBar = new ProgressBar(0);
|
||||
@@ -67,6 +70,10 @@ public class AiTestPane extends BorderPane {
|
||||
private final Label moduleStatusLabel = new Label("请选择项目");
|
||||
private final Label toolchainStatus = new Label("工具链未检查");
|
||||
private final TabPane resultTabs = new TabPane();
|
||||
private final TabPane mainTabs = new TabPane();
|
||||
private Tab resultsMainTab;
|
||||
private Tab coverageResultTab;
|
||||
private Tab logResultTab;
|
||||
private final ListView<String> generatedTests = new ListView<>();
|
||||
private final TextArea coverageArea = readOnlyArea();
|
||||
private final TextArea logArea = readOnlyArea();
|
||||
@@ -94,6 +101,7 @@ public class AiTestPane extends BorderPane {
|
||||
this.modulePreferences = modulePreferences;
|
||||
this.historyService = historyService;
|
||||
this.toolchainValidator = toolchainValidator;
|
||||
this.oneClickRunner = new AiTestOneClickRunner(workflow);
|
||||
buildUi();
|
||||
bindActions();
|
||||
loadInitialData();
|
||||
@@ -103,13 +111,17 @@ public class AiTestPane extends BorderPane {
|
||||
setPadding(new Insets(20));
|
||||
setCenter(buildContent());
|
||||
setBottom(buildStatus());
|
||||
generateButton.setDisable(true);
|
||||
generateButton.setDisable(false);
|
||||
integrateButton.setDisable(true);
|
||||
cancelButton.setDisable(true);
|
||||
projectCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
moduleCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
baselineField.setId("baseline-ref");
|
||||
targetRefField.setId("target-ref");
|
||||
analyzeButton.setId("analyze-only-action");
|
||||
generateButton.setId("generate-and-cover-action");
|
||||
mainTabs.setId("ai-test-main-tabs");
|
||||
resultTabs.setId("ai-test-result-tabs");
|
||||
projectCombo.setCellFactory(ignored -> projectCell());
|
||||
projectCombo.setButtonCell(projectCell());
|
||||
}
|
||||
@@ -120,7 +132,9 @@ public class AiTestPane extends BorderPane {
|
||||
addRow(selection, 1, "基线分支/Commit", baselineField, "目标分支/Commit", targetRefField);
|
||||
selection.add(includeWorkingTree, 1, 2);
|
||||
selection.add(moduleStatusLabel, 3, 2, 2, 1);
|
||||
VBox projectPage = page(selection);
|
||||
HBox projectActions = new HBox(10, generateButton, analyzeButton, cancelButton);
|
||||
projectActions.setAlignment(Pos.CENTER_LEFT);
|
||||
VBox projectPage = page(selection, projectActions);
|
||||
|
||||
GridPane toolchain = grid();
|
||||
Button validateToolchain = new Button("检查并保存工具链");
|
||||
@@ -141,22 +155,23 @@ public class AiTestPane extends BorderPane {
|
||||
VBox specificationPage = page(specifications, saveSpecifications);
|
||||
VBox.setVgrow(specifications, Priority.ALWAYS);
|
||||
|
||||
resultTabs.getTabs().addAll(tab("生成文件", generatedTests), tab("覆盖率", coverageArea),
|
||||
tab("运行日志", logArea), tab("历史摘要", historyArea));
|
||||
coverageResultTab = tab("覆盖率", coverageArea);
|
||||
logResultTab = tab("运行日志", logArea);
|
||||
resultTabs.getTabs().addAll(tab("生成文件", generatedTests), coverageResultTab,
|
||||
logResultTab, tab("历史摘要", historyArea));
|
||||
resultTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
|
||||
VBox.setVgrow(resultTabs, Priority.ALWAYS);
|
||||
|
||||
HBox actions = new HBox(10, analyzeButton, generateButton, cancelButton);
|
||||
actions.setAlignment(Pos.CENTER_LEFT);
|
||||
GridPane integration = grid();
|
||||
addRow(integration, 0, "目标本地分支", targetBranch, "提交信息", commitMessage);
|
||||
integration.add(reviewApproved, 0, 1, 2, 1);
|
||||
integration.add(integrateButton, 4, 1);
|
||||
VBox resultPage = page(actions, resultTabs, titled("审核并合并", integration));
|
||||
VBox resultPage = page(resultTabs, titled("审核并合并", integration));
|
||||
VBox.setVgrow(resultTabs, Priority.ALWAYS);
|
||||
|
||||
TabPane mainTabs = new TabPane(tab("项目与增量", projectPage), tab("工具链", toolchainPage),
|
||||
tab("质量规范", specificationPage), tab("结果与合并", resultPage));
|
||||
resultsMainTab = tab("结果与合并", resultPage);
|
||||
mainTabs.getTabs().addAll(tab("项目与增量", projectPage), tab("工具链", toolchainPage),
|
||||
tab("质量规范", specificationPage), resultsMainTab);
|
||||
mainTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
|
||||
return mainTabs;
|
||||
}
|
||||
@@ -184,7 +199,7 @@ public class AiTestPane extends BorderPane {
|
||||
}
|
||||
});
|
||||
analyzeButton.setOnAction(event -> analyze());
|
||||
generateButton.setOnAction(event -> generate());
|
||||
generateButton.setOnAction(event -> generateAndCover());
|
||||
cancelButton.setOnAction(event -> workflow.cancel());
|
||||
integrateButton.setOnAction(event -> integrate());
|
||||
reviewApproved.selectedProperty().addListener((ignored, oldValue, approved) -> updateButtons());
|
||||
@@ -271,28 +286,46 @@ public class AiTestPane extends BorderPane {
|
||||
}
|
||||
|
||||
private void analyze() {
|
||||
AiTestProject project = projectCombo.getValue();
|
||||
MavenModule module = moduleCombo.getValue();
|
||||
if (project == null || module == null || baselineField.getText().isBlank()
|
||||
|| targetRefField.getText().isBlank()) {
|
||||
statusLabel.setText("请选择项目、模块并填写基线和目标引用");
|
||||
AiTestRequest request = requestFromForm();
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
AiTestRequest request = new AiTestRequest(project.path(), module.artifactId(),
|
||||
baselineField.getText().trim(), targetRefField.getText().trim(),
|
||||
includeWorkingTree.isSelected(), settingsFromForm());
|
||||
runAsync(() -> workflow.analyze(request, this::onProgress), run -> {
|
||||
currentRun = run;
|
||||
renderRun(run);
|
||||
});
|
||||
}
|
||||
|
||||
private void generate() {
|
||||
AiTestRun analyzed = currentRun;
|
||||
if (analyzed == null || analyzed.state() != AiTestRunState.ANALYZED) {
|
||||
private AiTestRequest requestFromForm() {
|
||||
AiTestProject project = projectCombo.getValue();
|
||||
MavenModule module = moduleCombo.getValue();
|
||||
if (project == null || module == null || baselineField.getText().isBlank()
|
||||
|| targetRefField.getText().isBlank()) {
|
||||
statusLabel.setText("请选择项目、模块并填写基线和目标引用");
|
||||
return null;
|
||||
}
|
||||
return new AiTestRequest(project.path(), module.artifactId(),
|
||||
baselineField.getText().trim(), targetRefField.getText().trim(),
|
||||
includeWorkingTree.isSelected(), settingsFromForm());
|
||||
}
|
||||
|
||||
private void generateAndCover() {
|
||||
AiTestRequest request = requestFromForm();
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
runAsync(() -> workflow.generateAndVerify(analyzed, this::onProgress), run -> {
|
||||
runAsync(() -> {
|
||||
ToolchainValidation validation = toolchainValidator.validate(request.settings(), ignored -> { });
|
||||
if (!validation.ready()) {
|
||||
String failures = validation.checks().stream()
|
||||
.filter(check -> !check.successful())
|
||||
.map(check -> check.name() + ": " + check.detail())
|
||||
.reduce("", (left, right) -> left + right + "\n");
|
||||
throw new IllegalStateException("工具链检查失败,请在‘工具链’中修正配置:\n"
|
||||
+ failures.trim());
|
||||
}
|
||||
return oneClickRunner.run(request, this::onProgress);
|
||||
}, run -> {
|
||||
currentRun = run;
|
||||
renderRun(run);
|
||||
});
|
||||
@@ -323,6 +356,14 @@ public class AiTestPane extends BorderPane {
|
||||
historyArea.appendText(run.id() + " | " + run.state() + " | " + run.message() + "\n");
|
||||
progressBar.setProgress(run.state() == AiTestRunState.READY_FOR_REVIEW
|
||||
|| run.state() == AiTestRunState.SUCCEEDED ? 1 : 0);
|
||||
if (run.state() == AiTestRunState.READY_FOR_REVIEW || run.state() == AiTestRunState.SUCCEEDED
|
||||
|| run.state() == AiTestRunState.FAILED || run.state() == AiTestRunState.CANCELLED) {
|
||||
mainTabs.getSelectionModel().select(resultsMainTab);
|
||||
AiTestResultSelection selection = AiTestResultSelection.forState(run.state());
|
||||
resultTabs.getSelectionModel().select(selection == AiTestResultSelection.COVERAGE
|
||||
? coverageResultTab : selection == AiTestResultSelection.LOG
|
||||
? logResultTab : resultTabs.getTabs().get(0));
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
@@ -353,6 +394,8 @@ public class AiTestPane extends BorderPane {
|
||||
statusLabel.setText(failure.getMessage() == null
|
||||
? failure.getClass().getSimpleName() : failure.getMessage());
|
||||
appendLog("失败: " + statusLabel.getText());
|
||||
mainTabs.getSelectionModel().select(resultsMainTab);
|
||||
resultTabs.getSelectionModel().select(logResultTab);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -390,8 +433,7 @@ public class AiTestPane extends BorderPane {
|
||||
|
||||
private void updateButtons() {
|
||||
analyzeButton.setDisable(busy);
|
||||
generateButton.setDisable(busy || currentRun == null
|
||||
|| currentRun.state() != AiTestRunState.ANALYZED);
|
||||
generateButton.setDisable(busy);
|
||||
integrateButton.setDisable(busy || !reviewApproved.isSelected() || currentRun == null
|
||||
|| currentRun.state() != AiTestRunState.READY_FOR_REVIEW);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.workutils.biz.aitest.application;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AiTestOneClickRunner {
|
||||
private final AiTestWorkflow workflow;
|
||||
|
||||
public AiTestOneClickRunner(AiTestWorkflow workflow) {
|
||||
this.workflow = Objects.requireNonNull(workflow, "workflow");
|
||||
}
|
||||
|
||||
public AiTestRun run(AiTestRequest request, Consumer<AiTestProgress> progress) {
|
||||
AiTestRun analyzed = workflow.analyze(request, progress);
|
||||
return analyzed.state() == AiTestRunState.ANALYZED
|
||||
? workflow.generateAndVerify(analyzed, progress)
|
||||
: analyzed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.workutils.biz.aitest.ui;
|
||||
|
||||
import com.example.workutils.biz.aitest.application.AiTestRunState;
|
||||
|
||||
public enum AiTestResultSelection {
|
||||
GENERATED_FILES,
|
||||
COVERAGE,
|
||||
LOG;
|
||||
|
||||
public static AiTestResultSelection forState(AiTestRunState state) {
|
||||
return switch (state) {
|
||||
case READY_FOR_REVIEW, SUCCEEDED -> COVERAGE;
|
||||
case FAILED, CANCELLED -> LOG;
|
||||
default -> GENERATED_FILES;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
alter table risk_compensatory_contract_info
|
||||
add column contract_type varchar(32) not null default 'GUARANTEE' comment '合约类型;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.ContractTypeEnum';
|
||||
|
||||
alter table risk_compensatory_order_info
|
||||
add column compensatory_contract_no varchar(64) not null default '' comment '代偿合约号',
|
||||
add column loan_status varchar(32) not null default '' comment '借据状态;见枚举:com.bestpay.ft.compensatory.core.common.enums.compensatory.LoanStatusEnum',
|
||||
add column insurance_flag varchar(32) not null default '' comment '投保标识;见枚举:com.bestpay.ft.compensatory.core.common.enums.compensatory.InsuranceFlagEnum',
|
||||
add column area_code varchar(64) not null default '' comment '区域编码;省编码;ALL=所有,上海、广东...',
|
||||
add column period_num int default null comment '期数数量;期数',
|
||||
add column terminate_time datetime default null comment '结清时间';
|
||||
|
||||
create table `risk_insurance_contract_info`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,用于标识行记录数据来源环境。其中全量枚举:线下:DEV(开发环境),SIT(集成测试环境),STB(稳定环境);线上:PRE(预发环境),GRY(灰度环境),PRD(生产环境)',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`enabled_flag` tinyint(4) not null default '0' comment '启用标识:0=未启用,1=已启用',
|
||||
`checked_at` datetime default null comment '审核时间',
|
||||
`checked_by` varchar(128) not null default '' comment '审核人',
|
||||
`config_changed_flag` tinyint(4) not null default '0' comment '配置变更标识:0=不存在待审核,1=存在待审核',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约编号',
|
||||
`insurer_org_ip_r_id` varchar(64) not null default '' comment '保险人机构iprid',
|
||||
`applicant_org_ip_r_id` varchar(64) not null default '' comment '投保人机构iprid',
|
||||
`insurance_mode` varchar(32) not null default '' comment '投保模式;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceModeEnum',
|
||||
`insurance_amount_basis` varchar(32) not null default '' comment '保险金额基准;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceAmountBasisEnum',
|
||||
`insurance_cost_calculate_rule` varchar(128) not null default '' comment '保费计算规则;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceCostCalculateRuleEnum',
|
||||
`insurance_cost_calculate_rounding_mode` varchar(32) not null default '' comment '保费计算舍位模式;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceCostCalculateRoundingModeEnum',
|
||||
`insurance_flow_cfg_code` varchar(64) not null default '' comment '投保流程配置表编码',
|
||||
`pack_day` varchar(32) not null default '' comment '打包日;格式:DD',
|
||||
`pack_frequency` varchar(128) not null default '' comment '打包频率;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.PackFrequencyEnum',
|
||||
`pack_basis` varchar(128) not null default '' comment '打包基准;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.PackBasisEnum',
|
||||
`pack_rule` varchar(32) not null default '' comment '打包规则;见枚举:com.bestpay.ft.compensatory.core.api.contract.enums.PackRuleEnum',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_compensatory_contract_no` (`compensatory_contract_no`),
|
||||
KEY `idx_insurance_flow_cfg_code` (`insurance_flow_cfg_code`)
|
||||
) default charset=utf8mb4 comment='投保合约信息表';
|
||||
|
||||
create table `risk_insurance_flow_cfg`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,用于标识行记录数据来源环境。其中全量枚举:线下:DEV(开发环境),SIT(集成测试环境),STB(稳定环境);线上:PRE(预发环境),GRY(灰度环境),PRD(生产环境)',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`enabled_flag` tinyint(4) not null default '0' comment '启用标识:0=未启用,1=已启用',
|
||||
`checked_by` varchar(128) not null default '' comment '审核人',
|
||||
`checked_at` datetime default null comment '审核时间',
|
||||
`config_changed_flag` tinyint(4) not null default '0' comment '配置变更标识;0=不存在待审核,1=存在待审核',
|
||||
`flow_code` varchar(128) not null default '' comment '流程编号',
|
||||
`flow_name` varchar(128) not null default '' comment '流程名称',
|
||||
`flow_desc` varchar(512) not null default '' comment '流程描述',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_flow_code` (`flow_code`)
|
||||
) default charset=utf8mb4 comment='风险投保流程配置表';
|
||||
|
||||
create table `risk_insurance_flow_node_cfg`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,用于标识行记录数据来源环境。其中全量枚举:线下:DEV(开发环境),SIT(集成测试环境),STB(稳定环境);线上:PRE(预发环境),GRY(灰度环境),PRD(生产环境)',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`enabled_flag` tinyint(4) not null default '0' comment '启用标识:0=未启用,1=已启用',
|
||||
`checked_by` varchar(128) not null default '' comment '审核人',
|
||||
`checked_at` datetime default null comment '审核时间',
|
||||
-- TODO 字段注释补充枚举;
|
||||
`flow_code` varchar(64) not null default '' comment '流程编号',
|
||||
`node_code` varchar(64) not null default '' comment '节点编码',
|
||||
`node_name` varchar(128) not null default '' comment '节点名称',
|
||||
`node_interaction_way` varchar(32) not null default '' comment '节点交互方式:见枚举:com.bestpay.ft.compensatory.core.common.enums.compensatory.NodeInteractionWayEnum',
|
||||
`node_interaction_config_value` varchar(64) not null default '' comment '节点交互配置值',
|
||||
`next_node_code` varchar(64) not null default '' comment '下个节点编号',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_flow_code` (`flow_code`),
|
||||
KEY `idx_node_code` (`node_code`)
|
||||
) default charset=utf8mb4 comment='风险投保流程节点配置表';
|
||||
|
||||
create table `risk_insurance_contract_insurance_cost_rate_cfg`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,用于标识行记录数据来源环境。其中全量枚举:线下:DEV(开发环境),SIT(集成测试环境),STB(稳定环境);线上:PRE(预发环境),GRY(灰度环境),PRD(生产环境)',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`enabled_flag` tinyint(4) not null default '0' comment '启用标识:0=未启用,1=已启用',
|
||||
`checked_by` varchar(128) not null default '' comment '审核人',
|
||||
`checked_at` datetime default null comment '审核时间',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
`area_code` varchar(64) not null default '' comment '区域编码;省编码;ALL=所有,上海、广东...',
|
||||
`area_name` varchar(64) not null default '' comment '区域名称;省名称',
|
||||
`period_num` int default null comment '期数数量:ALL=所有,12、24...',
|
||||
`insurance_cost_rate` varchar(32) not null default '' comment '保费费率;万分位',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_compensatory_contract_no` (`compensatory_contract_no`)
|
||||
) default charset=utf8mb4 comment='投保合约保费费率配置表';
|
||||
|
||||
------TODO 评审ID:
|
||||
create table `risk_insurance_evt`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`biz_no` varchar(64) not null default '' comment '上游业务单号;根据事件类型不一样,比如打包投保单,记录代偿执行任务批次号',
|
||||
`req_time` datetime not null comment '请求时间',
|
||||
`req_id` varchar(64) not null default '' comment '请求流水号',
|
||||
`evt_type` varchar(32) not null default '' comment '事件类型;见枚举:com.bestpay.ft.compensatory.core.api.insurance.enums.InsuranceEvtTypeEnum',
|
||||
`node_code` varchar(64) not null default '' comment '节点编码',
|
||||
`request_system` varchar(32) not null default '' comment '请求系统',
|
||||
-- TODO 补充:见枚举
|
||||
`operator_type` varchar(32) not null default '' comment '操作人类型:见枚举:',
|
||||
`operator_id` varchar(128) not null default '' comment '操作人id',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_req_id` (`req_id`),
|
||||
KEY `idx_req_time` (`req_time`),
|
||||
KEY `idx_biz_no` (`biz_no`)
|
||||
) default charset=utf8mb4 comment='投保流程事件表';
|
||||
|
||||
create table `risk_insurance_bill`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`biz_no` varchar(64) not null default '' comment '上游业务单号;打包投保单流程节点事件请求流水号;外键关联risk_insurance_evt.req_id',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单据号;生成规则前缀:IBN_',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
`pack_date` datetime not null comment '打包日期',
|
||||
`insurance_mode` varchar(32) not null default '' comment '投保模式,com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceModeEnum',
|
||||
`insurance_amount_basis` varchar(32) not null default '' comment '保险金额基准,com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceAmountBasisEnum',
|
||||
`insurance_cost_calculate_rule` varchar(64) not null default '' comment '保费计算规则,com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceCostCalculateRuleEnum',
|
||||
`insurance_cost_calculate_rounding_mode` varchar(32) not null default '' comment '保费计算舍位模式:取整模式,com.bestpay.ft.compensatory.core.api.contract.enums.InsuranceCostCalculateRoundingModeEnum',
|
||||
`pack_rule` varchar(32) not null default '' comment '打包规则,com.bestpay.ft.compensatory.core.api.contract.enums.PackRuleEnum',
|
||||
`total_insured_subject_count` int(11) not null default 0 comment '投保标的总数;总借据数',
|
||||
`total_insurance_amount` bigint(20) not null comment '总保险金额,单位:分',
|
||||
`insurance_status` varchar(32) not null default '' comment '投保状态;见枚举:com.bestpay.ft.compensatory.core.api.insurance.enums.InsuranceStatusEnum',
|
||||
`biz_trial_insurance_cost_amount` bigint(20) default null comment '业务试算保费金额;单位:分',
|
||||
`operation_trial_insurance_cost_amount` bigint(20) default null comment '运营试算保费金额;单位:分',
|
||||
`policy_no` varchar(64) not null default '' comment '保单号',
|
||||
`current_flow_node_code` varchar(64) not null default '' comment '当前流程节点编码',
|
||||
`version_no` int(11) not null default 0 comment '版本号,乐观锁版本号,update = 原值+1,初始化=0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_insurance_bill_no` (`insurance_bill_no`),
|
||||
KEY `idx_compensatory_contract_no` (`compensatory_contract_no`),
|
||||
KEY `idx_policy_no` (`policy_no`),
|
||||
KEY `idx_biz_no` (`biz_no`)
|
||||
) default charset=utf8mb4 comment='投保单据表';
|
||||
|
||||
create table `risk_insurance_cmd`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单号;关联risk_insurance_bill.insurance_bill_no',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
`flow_node_code` varchar(64) not null default '' comment '流程节点编码',
|
||||
`cmd_no` varchar(64) not null default '' comment '指令号',
|
||||
`cmd_type` varchar(32) not null default '' comment '指令类型,com.bestpay.ft.compensatory.core.api.insurance.enums.InsuranceCmdTypeEnum',
|
||||
`cmd_exec_start_time` datetime default null comment '指令执行开始时间',
|
||||
`cmd_exec_end_time` datetime default null comment '指令执行结束时间',
|
||||
`cmd_status` varchar(32) not null default '' comment '指令状态,com.bestpay.ft.compensatory.core.api.insurance.enums.InsuranceCmdStatusEnum',
|
||||
`ext_biz_no` varchar(128) not null default '' comment '拓展业务单号;外部业务单号',
|
||||
`fail_code` varchar(32) NOT NULL DEFAULT '' COMMENT '失败编码',
|
||||
`fail_message` varchar(128) NOT NULL DEFAULT '' COMMENT '失败消息',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_cmd_no` (`cmd_no`),
|
||||
KEY `idx_compensatory_contract_no` (`compensatory_contract_no`),
|
||||
KEY `idx_biz_no` (`biz_no`)
|
||||
) default charset=utf8mb4 comment='投保执行指令表';
|
||||
|
||||
create table `risk_insurance_subject_bill`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`insurance_subject_bill_no` varchar(64) not null default '' comment '投保标的单据号',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单据号;外键',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
`fund_org_contract_no` varchar(64) not null default '' comment '资金机构合约号',
|
||||
`loan_no` varchar(64) not null default '' comment '借据号',
|
||||
`insurance_start_season` varchar(32) not null default '' comment '投保开始账期;格式:yyyyMM',
|
||||
`insurance_end_season` varchar(32) not null default '' comment '投保结束账期;格式:yyyyMM',
|
||||
`insurance_amount` bigint(20) not null comment '保险金额;单位:分',
|
||||
`insurance_status` varchar(32) not null default '' comment '投保状态:com.bestpay.ft.compensatory.core.api.insurance.enums.InsuranceSubjectInsuranceStatusEnum',
|
||||
`insurance_status_desc` varchar(256) not null default '' comment '投保状态描述',
|
||||
`version_no` int(11) not null default 0 comment '版本号,乐观锁版本号,update = 原值+1,初始化=0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_insurance_subject_bill_no` (`insurance_subject_bill_no`),
|
||||
KEY `idx_insurance_bill_no` (`insurance_bill_no`),
|
||||
KEY `idx_loan_no` (`loan_no`)
|
||||
) default charset=utf8mb4 comment='投保标的单据表';
|
||||
|
||||
create table `risk_insurance_subject_insurance_cost_info`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`insurance_subject_bill_no` varchar(64) not null default '' comment '投保标的单据号;外键',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单据号;冗余',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号;冗余',
|
||||
`fund_org_contract_no` varchar(64) not null default '' comment '资金机构合约号;冗余',
|
||||
`loan_no` varchar(64) not null default '' comment '借据号;冗余',
|
||||
`insurance_cost_rate` varchar(64) not null default '' comment '保费费率;冗余',
|
||||
`biz_trial_insurance_cost_amount` bigint(20) default null comment '业务试算保费金额;单位;分',
|
||||
`operation_trial_insurance_cost_amount` bigint(20) default null comment '运营试算保费金额;单位:分',
|
||||
`insurance_company_insurance_cost_amount` bigint(20) default null comment '保司保费金额;单位:分',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_insurance_subject_bill_no` (`insurance_subject_bill_no`),
|
||||
KEY `idx_insurance_bill_no` (`insurance_bill_no`),
|
||||
KEY `idx_compensatory_contract_no` (`compensatory_contract_no`),
|
||||
KEY `idx_loan_no` (`loan_no`)
|
||||
) default charset=utf8mb4 comment='投保标的保费信息表';
|
||||
|
||||
--- 评审ID:hkoz8gfwly
|
||||
create table `risk_insurance_loan_notify_evt`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`req_time` datetime not null comment '请求时间',
|
||||
`req_id` varchar(64) not null default '' comment '请求流水号',
|
||||
`biz_no` varchar(64) not null default '' comment '上游业务单号;借据号',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_req_id` (`req_id`),
|
||||
KEY `idx_req_time` (`req_time`),
|
||||
KEY `idx_biz_no` (`biz_no`)
|
||||
) default charset=utf8mb4 comment='投保借据通知事件表';
|
||||
|
||||
create table `risk_insurance_policy_bill`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`tnt_id` varchar(64) not null default '' comment '租户id',
|
||||
`product_line_code` varchar(64) not null default '' comment '产品线编码',
|
||||
`biz_no` varchar(64) not null default '' comment '上游业务单号;录入保单流程节点事件请求流水号;关联risk_insurance_evt.req_id',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单据号;外键关联risk_insurance_bill.insurance_bill_no',
|
||||
`policy_no` varchar(64) not null default '' comment '保单号',
|
||||
`compensatory_contract_no` varchar(64) not null default '' comment '代偿合约号',
|
||||
`insurer_org_ip_r_id` varchar(64) not null default '' comment '保险人机构iprid',
|
||||
`applicant_org_ip_r_id` varchar(64) not null default '' comment '投保人机构iprid',
|
||||
`total_insurance_amount` bigint(20) not null comment '总保险金额;单位:分;总共能理赔多少钱',
|
||||
`total_insurance_cost_amount` bigint(20) not null comment '总保费金额;单位;分;总共要交多少钱',
|
||||
`total_paid_insurance_cost_amount` bigint(20) default null comment '总已支付保费金额;单位:分',
|
||||
`total_pending_payment_insurance_cost_amount` bigint(20) default null comment '总待支付保费金额;单位:分',
|
||||
`effective_start_date` date default null comment '生效开始日期',
|
||||
`effective_end_date` date default null comment '生效结束日期',
|
||||
`version_no` int(11) not null default 0 comment '版本号;乐观锁版本号,update = 原值+1,初始化=0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_policy_no` (`policy_no`),
|
||||
KEY `idx_insurance_bill_no` (`insurance_bill_no`),
|
||||
KEY `idx_biz_no` (`biz_no`)
|
||||
) default charset=utf8mb4 comment='保单单据表';
|
||||
|
||||
create table `risk_insurance_file_info`
|
||||
(
|
||||
`id` bigint(20) unsigned not null auto_increment comment '主键id',
|
||||
`created_at` datetime not null comment '创建时间',
|
||||
`created_by` varchar(128) not null default 'sys' comment '创建人',
|
||||
`updated_at` datetime default null comment '更新时间',
|
||||
`updated_by` varchar(128) not null default '' comment '更新人',
|
||||
`archived_at` datetime default null comment '归档时间',
|
||||
`archived_by` varchar(128) not null default '' comment '归档人',
|
||||
`archived_flag` tinyint(4) not null default '0' comment '归档标识:0=未归档,1=已归档',
|
||||
`env` varchar(32) not null default '' comment '环境标识,DEV/SIT/STB/PRE/GRY/PRD',
|
||||
`shadow` tinyint(4) not null default '0' comment '影子标识位 (1 影子数据 0 非影子数据)',
|
||||
`insurance_bill_no` varchar(64) not null default '' comment '投保单据号;外键关联:risk_insurance_bill',
|
||||
`flow_node_code` varchar(64) not null default '' comment '流程节点编码',
|
||||
`file_type` varchar(32) not null default '' comment '文件类型',
|
||||
`file_id` varchar(128) not null default '' comment '文件ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_insurance_bill_no` (`insurance_bill_no`)
|
||||
) default charset=utf8mb4 comment='投保单文件信息表';
|
||||
@@ -13,6 +13,7 @@ import com.example.workutils.system.ui.FxTestSupport;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Accordion;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextField;
|
||||
@@ -38,11 +39,20 @@ class AiTestPaneArchitectureTest {
|
||||
|
||||
assertEquals(null, FxTestSupport.fx(pane::getTop));
|
||||
TabPane mainTabs = assertInstanceOf(TabPane.class, FxTestSupport.fx(pane::getCenter));
|
||||
assertEquals("项目与增量", FxTestSupport.fx(
|
||||
() -> mainTabs.getSelectionModel().getSelectedItem().getText()));
|
||||
assertEquals(List.of("项目与增量", "工具链", "质量规范", "结果与合并"),
|
||||
FxTestSupport.fx(() -> mainTabs.getTabs().stream().map(tab -> tab.getText()).toList()));
|
||||
assertEquals(0, FxTestSupport.fx(() -> tabDescendants(mainTabs, Accordion.class).size()));
|
||||
assertEquals(2, FxTestSupport.fx(
|
||||
() -> tabDescendants(mainTabs, DirectoryPathSelector.class).size()));
|
||||
List<Button> buttons = FxTestSupport.fx(() -> tabDescendants(mainTabs, Button.class));
|
||||
assertEquals("生成单测并分析覆盖率", FxTestSupport.fx(() -> buttons.stream()
|
||||
.filter(button -> "generate-and-cover-action".equals(button.getId()))
|
||||
.findFirst().orElseThrow().getText()));
|
||||
assertEquals("仅分析增量", FxTestSupport.fx(() -> buttons.stream()
|
||||
.filter(button -> "analyze-only-action".equals(button.getId()))
|
||||
.findFirst().orElseThrow().getText()));
|
||||
|
||||
List<DirectoryPathSelector> selectors = FxTestSupport.fx(
|
||||
() -> tabDescendants(mainTabs, DirectoryPathSelector.class));
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.example.workutils.biz.aitest.application;
|
||||
|
||||
import com.example.workutils.biz.aitest.config.AiTestSettings;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class AiTestOneClickRunnerTest {
|
||||
private final AiTestWorkflow workflow = mock(AiTestWorkflow.class);
|
||||
private final AiTestOneClickRunner runner = new AiTestOneClickRunner(workflow);
|
||||
private final AiTestRequest request = new AiTestRequest(Path.of("project"), "app-start",
|
||||
"master", "HEAD", false, AiTestSettings.defaults());
|
||||
private final Consumer<AiTestProgress> progress = ignored -> { };
|
||||
|
||||
@Test
|
||||
void generatesAndVerifiesAfterSuccessfulAnalysis() {
|
||||
AiTestRun analyzed = AiTestRun.initial("run", request).analyzed(null, null);
|
||||
AiTestRun ready = analyzed.ready();
|
||||
when(workflow.analyze(request, progress)).thenReturn(analyzed);
|
||||
when(workflow.generateAndVerify(analyzed, progress)).thenReturn(ready);
|
||||
|
||||
assertSame(ready, runner.run(request, progress));
|
||||
|
||||
verify(workflow).generateAndVerify(analyzed, progress);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stopsWhenAnalysisFails() {
|
||||
AiTestRun failed = AiTestRun.initial("run", request).fail("analysis failed");
|
||||
when(workflow.analyze(request, progress)).thenReturn(failed);
|
||||
|
||||
assertSame(failed, runner.run(request, progress));
|
||||
|
||||
verify(workflow, never()).generateAndVerify(any(), any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.workutils.biz.aitest.ui;
|
||||
|
||||
import com.example.workutils.biz.aitest.application.AiTestRunState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AiTestResultSelectionTest {
|
||||
@Test
|
||||
void selectsCoverageForReviewableResult() {
|
||||
assertEquals(AiTestResultSelection.COVERAGE,
|
||||
AiTestResultSelection.forState(AiTestRunState.READY_FOR_REVIEW));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectsLogForFailure() {
|
||||
assertEquals(AiTestResultSelection.LOG,
|
||||
AiTestResultSelection.forState(AiTestRunState.FAILED));
|
||||
}
|
||||
}
|
||||