feat: release 2.4.2 with batch human review badges
This commit is contained in:
1 parent
ac3d8cf54a
commit
ffd2261f38
14 files changed
+192
-14
No files matched your search
@@ -21,7 +21,7 @@
|
||||
| 探索、示范、用例提炼与复跑 | [探索说明](docs/exploration-mode.md) |
|
||||
| 插件协议、适配或市场协同 | [插件开发规范](docs/plugin-development.md)及插件仓库自身的 AGENTS.md、DESIGN.md |
|
||||
| 远程 Python SDK | [SDK 设计](packages/artemis-client/DESIGN.md),保持标准库薄客户端边界 |
|
||||
| 继续历史开发、确认版本或发布 | [迁入时的开发快照](docs/development-handoff.md)、[2.1.0 发行说明](docs/releases/2.1.0.md),再核对实时 Git 状态;`docs/plans/` 保留各自阶段的状态 |
|
||||
| 继续历史开发、确认版本或发布 | [README 当前发行入口](README.md)、对应发行说明及实时 Git 状态;[开发快照](docs/development-handoff.md)与 `docs/plans/` 仅作为历史线索,不替代当前源码与发布包核验 |
|
||||
|
||||
## Quick Commands
|
||||
|
||||
@@ -60,7 +60,7 @@ Windows 首次安装使用 `start.bat`,国内镜像使用 `start-cn.bat`。启
|
||||
## Conventions
|
||||
|
||||
- Python 使用绝对导入、4 空格缩进和双引号,由 Ruff 检查;不要放宽质量基线来掩盖新增问题。
|
||||
- 新增可调参数集中进入配置层,并更新环境示例、网页/MCP 入口和必要文档;默认值同步 `config/artemis.jsonc` 与 `artemis/resources/config/artemis.jsonc`。
|
||||
- 新增可调参数集中进入配置层,按作用域更新环境示例、网页/MCP 入口和文档;明确仅供环境配置的参数不增加前端入口。JSON 默认值同步 `config/artemis.jsonc` 与 `artemis/resources/config/artemis.jsonc`。
|
||||
- MCP 工具增删时同步工具注册、CLI 安装白名单、测试、Skill 和能力文档。
|
||||
- 同步 Codex MCP/Skill 时保留已有工具白名单、禁用状态和审批策略;异常时保留原配置并告警,不生成非法 TOML 或阻断网页启动。
|
||||
- Windows PowerShell 启动脚本保持兼容 Windows PowerShell 5.1,避免编码导致解析失败。升级只做 Git 快进,保护已有配置和本地修改。
|
||||
@@ -75,6 +75,16 @@ Windows 首次安装使用 `start.bat`,国内镜像使用 `start-cn.bat`。启
|
||||
- 设备验证沿用用户指定设备;多设备且未指定时先确认。编写应用交互测试前,先通过 ARTEMIS 探索实际页面和操作路径。
|
||||
- 提交前检查 `git diff --check` 和暂存范围,只暂存本次具体路径;报告实际执行的验证及未完成项,不将未运行的检查写成通过。
|
||||
|
||||
## Release Gates
|
||||
|
||||
- 主程序、SDK、插件和第三方依赖独立版本化。主程序升版只修改 `pyproject.toml` 的项目版本、`uv.lock` 的 artemis 条目及前端应用的根版本字段,发行文档另行更新。禁止对仓库或整个锁文件全局替换版本字符串。
|
||||
- 单独审查锁文件差异。第三方条目的版本、下载地址、完整性校验和依赖声明必须相互匹配;主程序升版不应改变这些条目。真正升级依赖时由包管理器生成并核对上游声明;发生 EINTEGRITY 先核验来源,禁止关闭校验或只照错误提示改哈希。
|
||||
- 发布前必须在隔离目录解压最终 ZIP,使用独立空 npm 缓存完成 `npm ci` 和生产构建,覆盖本次发布的官方/国内安装路径。包内安装或构建输入变化后必须重跑;不得清空用户全局缓存或改动用户运行目录来制造干净环境。
|
||||
- 记录实际 Node/npm 版本、镜像、源码提交、ZIP SHA256 和验证范围。已有 node_modules、构建指纹、缓存命中或大量单元测试通过,均不能证明全新安装成功;某个 Node 版本通过也不能冒充其他版本实测。测试和构建命令见前端局部指南。
|
||||
- 发布顺序:固定提交并打包 → 验证最终包 → 推送标签 → 创建草稿并上传包及校验文件 → 回读校验 → 正式发布 → 无认证下载核验。标签必须指向打包提交;推送标签后检查 Release 状态,本仓库曾出现 Gitea 将已有草稿自动发布的情况。失败时修复并重新验证,不提前宣布完成。
|
||||
- 候选包与正式包目录明确区分。候选包失效后移出可分发路径并标明替代版本,不能留下同名旧 ZIP 供用户误取;对外只给已核验的正式发布链接或准确文件路径。正式版本不覆盖原标签或附件,后续修复发补丁版。
|
||||
- 安装或市场版本不符时,先核对用户实际 ZIP 的 SHA256、运行源码提交及生效市场地址;不凭界面版本号判断,也不把未经验证的缓存假设当成原因。主程序与插件版本、索引兼容性分别验证。
|
||||
|
||||
## Boundaries
|
||||
|
||||
- 始终保护用户任务、历史库、环境配置和凭据。不要把个人设备序列号、绝对目录或 API Key 写入源码。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
基于 [ARTEMIS](https://github.com/google/artemis) 扩展的 Android AI 自动化测试平台。用自然语言描述任务,即可通过简体中文网页或 MCP 驱动真机、模拟器执行操作并收集结果。模型统一使用 **OpenAI Responses API**。
|
||||
|
||||
**2.4.1**:修复全新安装时的 npm 校验失败,准确检查 Node.js 版本要求。人工评审插件继续使用 1.1.0,市场使用兼容旧宿主的独立索引。见 [2.4.1 发行说明](docs/releases/2.4.1.md);功能更新见 [2.4.0](docs/releases/2.4.0.md)。
|
||||
**2.4.2**:批次任务增加人工已审标签,优化收藏、标题与状态标签布局。人工评审插件保持 1.1.0。见 [2.4.2 发行说明](docs/releases/2.4.2.md);安装修复见 [2.4.1](docs/releases/2.4.1.md)。
|
||||
|
||||
## 主要功能
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from apps.admin_console.services.exploration_listing import EXPLORATION_RUNS_CTE
|
||||
from apps.admin_console.services.automation_store import identifier, store
|
||||
from apps.admin_console.services import automation_service as service
|
||||
from apps.admin_console.services import batch_archive as archive_service
|
||||
from apps.admin_console.services import report_export, task_import
|
||||
from apps.admin_console.services import report_export, task_extensions, task_import
|
||||
from artemis.config.settings import settings
|
||||
from apps.admin_console.services.case_review import read_reviewed_result
|
||||
|
||||
@@ -131,7 +131,9 @@ def batch_by_request(request_id: str):
|
||||
@router.get("/batches/{batch_id}")
|
||||
def batch_detail(batch_id: str):
|
||||
jobs = store.jobs(batch_id)
|
||||
reviewed = task_extensions.reviewed_batch_sessions(batch_id, store)
|
||||
for job in jobs:
|
||||
job["human_reviewed"] = job["session_id"] in reviewed
|
||||
job["test_result"] = read_reviewed_result(
|
||||
job["session_id"], job["status"], job.get("error") or "", storage=store
|
||||
)
|
||||
|
||||
@@ -36,6 +36,32 @@ def installation_signature() -> str:
|
||||
)
|
||||
|
||||
|
||||
def reviewed_batch_sessions(batch_id: str, store: AutomationStore) -> set[str]:
|
||||
"""Read current human review values once per batch, without loading history."""
|
||||
manifest = next((item for item in active_manifests() if item.id == "task-review"), None)
|
||||
if manifest is None or manifest.workspace is None:
|
||||
return set()
|
||||
with store.connection() as conn:
|
||||
rows = conn.execute(
|
||||
"""SELECT j.session_id, json_extract(r.payload, '$."task-review".values') AS review_values
|
||||
FROM automation_jobs j JOIN automation_records r
|
||||
ON r.kind='task_extensions' AND r.id=j.session_id
|
||||
WHERE j.batch_id=?""",
|
||||
(batch_id,),
|
||||
).fetchall()
|
||||
reviewed = set()
|
||||
for row in rows:
|
||||
values = json.loads(row["review_values"] or "{}")
|
||||
if any(
|
||||
isinstance(value := values.get(field.id), str)
|
||||
and value.strip()
|
||||
and (field.kind == "text" or value in field.options)
|
||||
for field in manifest.workspace.fields
|
||||
):
|
||||
reviewed.add(row["session_id"])
|
||||
return reviewed
|
||||
|
||||
|
||||
def task_context(session_id: str, store: AutomationStore) -> dict:
|
||||
identifier(session_id)
|
||||
session = SessionRepository(store.path).get_session_by_id(session_id) or {}
|
||||
|
||||
@@ -30,6 +30,9 @@ npm run build
|
||||
|
||||
## Local Conventions
|
||||
|
||||
- 发版遵守根指南的 Release Gates。前端应用版本只改 package.json 顶层 `version` 和 package-lock.json 顶层及 `packages[""].version`;禁止把相同数字的第三方依赖一并替换。
|
||||
- Node 支持范围以锁定的 Angular CLI/构建依赖实际 `engines` 为准,核对启动脚本的主、次、补丁版本边界和提示;不要只判断“Node 22/24/26”。
|
||||
- 安装或依赖变更需在隔离目录、独立空 npm 缓存下运行 `npm ci`,再运行上述测试和构建;国内入口同时核验镜像安装。不得以关闭完整性校验、强制安装或删除锁文件掩盖不一致,也不得用已装依赖的构建结果宣称全新安装通过。
|
||||
- 页面组件在 src/app/components,服务在 src/app/services;先复用已有组件、样式及 API 服务。
|
||||
- 保持现有简体中文文案、颜色、间距和控件样式,菜单、弹窗和表单必须清晰可读。
|
||||
- 修改任务或配置交互时覆盖加载、保存、失败和空状态;不要将执行中误标成录屏中或把完成误标成验证通过。
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "artemis-showcase-ui",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "artemis-showcase-ui",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^22.2.4",
|
||||
"@angular/common": "^22.2.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "artemis-showcase-ui",
|
||||
"version": "2.4.1",
|
||||
"version": "2.4.2",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --proxy-config proxy.conf.json",
|
||||
|
||||
+14
-1
@@ -108,6 +108,19 @@
|
||||
</div>
|
||||
}
|
||||
@if(b.report_error){<p class="error">报告生成失败:{{b.report_error}}</p>}
|
||||
@for(j of b.jobs; track j.session_id){<div class="job" [class.selected]="agent.currentSessionId() === j.session_id"><app-favorite-button [sourceId]="j.session_id" /><button (click)="openBatchTask(j.session_id)" [title]="j.case.goal" [attr.aria-pressed]="agent.currentSessionId() === j.session_id">{{j.case.case_id ? j.case.case_id + ' ' : ''}}{{j.case.name}}@if(j.repeat_count > 1){ · 第 {{j.iteration}} / {{j.repeat_count}} 轮}</button><span>{{label(j.status)}}</span><app-test-result-badge [result]="j.test_result" [executionStatus]="j.status" [compact]="true" />@if(j.error){<small>{{j.error}}</small>}</div>}
|
||||
@for(j of b.jobs; track j.session_id){
|
||||
<div class="job" [class.selected]="agent.currentSessionId() === j.session_id">
|
||||
<div class="job-heading">
|
||||
<app-favorite-button [sourceId]="j.session_id" />
|
||||
<button (click)="openBatchTask(j.session_id)" [title]="j.case.goal" [attr.aria-pressed]="agent.currentSessionId() === j.session_id">{{j.case.case_id ? j.case.case_id + ' ' : ''}}{{j.case.name}}@if(j.repeat_count > 1){ · 第 {{j.iteration}} / {{j.repeat_count}} 轮}</button>
|
||||
</div>
|
||||
<div class="job-statuses">
|
||||
<span>{{label(j.status)}}</span>
|
||||
<app-test-result-badge [result]="j.test_result" [executionStatus]="j.status" [compact]="true" />
|
||||
@if(j.human_reviewed){<span class="human-review-badge" title="人工评审已有保存内容,不代表测试通过">人工已审</span>}
|
||||
</div>
|
||||
@if(j.error){<small>{{j.error}}</small>}
|
||||
</div>
|
||||
}
|
||||
</section>}
|
||||
}
|
||||
+11
-1
@@ -37,7 +37,15 @@ input[type=checkbox]{width:15px;height:15px;accent-color:var(--app-primary-text,
|
||||
@keyframes archive-spin{to{transform:rotate(360deg);}}
|
||||
.code{font-family:Consolas,monospace;font-size:12px;line-height:1.6;resize:vertical;background:var(--app-surface-soft, #f8fafc);}.table-wrap{max-height:260px;overflow:auto;border:1px solid var(--app-border, #e2e8f0);border-radius:8px;margin-bottom:14px;}table{width:100%;border-collapse:collapse;}th,td{padding:9px 10px;border-bottom:1px solid var(--app-border, #e2e8f0);text-align:left;overflow-wrap:anywhere;}th{background:var(--app-surface-soft, #f8fafc);color:var(--app-text-secondary, #475569);font-weight:500;}
|
||||
.preview{white-space:pre-wrap;max-height:400px;overflow:auto;font:inherit;color:var(--app-text-control, #334155);background:var(--app-surface-soft, #f8fafc);border-radius:8px;padding:12px;border:1px solid var(--app-border, #e2e8f0);}
|
||||
.job{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 0;border-bottom:1px solid var(--app-border, #f1f5f9);}.job:last-child{border-bottom:0;}.job button{flex:1;justify-content:flex-start;text-align:left;min-width:150px;}.job span{font-size:12px;background:var(--app-surface-soft, #f1f5f9);color:var(--app-text-secondary, #475569);border-radius:5px;padding:3px 7px;}.job small{width:100%;}details{margin:14px 0;}summary{color:var(--app-text-secondary, #475569);cursor:pointer;padding:8px 0;}
|
||||
.job{display:flex;flex-direction:column;gap:8px;padding:12px 0;border-bottom:1px solid var(--app-border, #f1f5f9);}
|
||||
.job:last-child{border-bottom:0;}
|
||||
.job-heading{position:relative;min-width:0;}
|
||||
.job-heading>app-favorite-button{position:absolute;left:6px;top:4px;z-index:1;}
|
||||
.job-heading>button{box-sizing:border-box;width:100%;min-width:0;padding-left:40px;justify-content:flex-start;text-align:left;overflow-wrap:anywhere;}
|
||||
.job-statuses{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}
|
||||
.job-statuses>span{display:inline-flex;align-items:center;box-sizing:border-box;height:20px;padding:0 6px;border:1px solid transparent;border-radius:5px;font-size:11px;line-height:14px;background:var(--app-surface-soft, #f1f5f9);color:var(--app-text-secondary, #475569);white-space:nowrap;}
|
||||
.job small{overflow-wrap:anywhere;}
|
||||
details{margin:14px 0;}summary{color:var(--app-text-secondary, #475569);cursor:pointer;padding:8px 0;}
|
||||
.task-guidance{margin-top:16px;padding:14px;border:1px solid var(--app-primary-border, #bfdbfe);border-radius:10px;background:var(--app-primary-soft, #f8fbff);}
|
||||
.task-guidance label{margin:8px 0 4px;}
|
||||
.report-overview{padding:14px;border:1px solid var(--app-primary-border, #dbeafe);border-radius:10px;background:var(--app-primary-soft, #f8fbff);}
|
||||
@@ -58,3 +66,5 @@ input[type=checkbox]{width:15px;height:15px;accent-color:var(--app-primary-text,
|
||||
table{width:100%;border-collapse:collapse;font-size:12px;}th,td{padding:7px 9px;border:1px solid var(--app-border, #e2e8f0);vertical-align:top;}th{background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1e3a8a);}
|
||||
.md-para-gap{height:8px;}
|
||||
}
|
||||
|
||||
.job .human-review-badge{white-space:nowrap;border:1px solid var(--app-primary-border, #bfdbfe);background:var(--app-primary-soft, #eff6ff);color:var(--app-primary-text, #1d4ed8);font-weight:500;}
|
||||
+25
-1
@@ -90,6 +90,30 @@ describe('AutomationPanelComponent batch deletion', () => {
|
||||
expect(http.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows only saved human review badges and removes them on a batch refresh', async () => {
|
||||
const fixture = TestBed.createComponent(AutomationPanelComponent);
|
||||
const component = fixture.componentInstance;
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
||||
component.tab.set('batches');
|
||||
const batch = { id: 'review-batch', name: '评审批次', status: 'completed', jobs: [
|
||||
{ session_id: 'reviewed', status: 'completed', human_reviewed: true, case: { name: '已评用例' } },
|
||||
{ session_id: 'empty', status: 'completed', human_reviewed: false, case: { name: '空白用例' } },
|
||||
{ session_id: 'legacy', status: 'completed', case: { name: '旧版任务' } }
|
||||
] };
|
||||
component.batch.set(batch);
|
||||
fixture.detectChanges();
|
||||
const rows = fixture.nativeElement.querySelectorAll('.job');
|
||||
expect(rows[0].querySelector('.human-review-badge').textContent).toBe('人工已审');
|
||||
expect(rows[1].querySelector('.human-review-badge')).toBeNull();
|
||||
expect(rows[2].querySelector('.human-review-badge')).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).not.toContain('人工未审');
|
||||
(http.get.and as any).callFake((url: string) => of(url.endsWith('/review-batch')
|
||||
? { ...batch, jobs: batch.jobs.map(job => ({ ...job, human_reviewed: false })) } : []));
|
||||
await component.refresh();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('.human-review-badge')).toBeNull();
|
||||
});
|
||||
|
||||
it('confirms deletion and removes the batch from the visible list', async () => {
|
||||
const fixture = TestBed.createComponent(AutomationPanelComponent);
|
||||
fixture.componentInstance.tab.set('batches');
|
||||
@@ -129,7 +153,7 @@ describe('AutomationPanelComponent batch deletion', () => {
|
||||
{session_id:'task-b',status:'completed',case:{name:'用例 B',goal:'观察主页'}}
|
||||
]});
|
||||
fixture.detectChanges();
|
||||
const jobs: HTMLButtonElement[] = [...fixture.nativeElement.querySelectorAll('.job > button')];
|
||||
const jobs: HTMLButtonElement[] = [...fixture.nativeElement.querySelectorAll('.job-heading > button')];
|
||||
jobs[0].click(); jobs[1].click();
|
||||
expect(agent.selectSession.calls.allArgs()).toEqual([['task-a',true],['task-b',true]]);
|
||||
expect(component.tab()).toBe('batches');
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# ARTEMIS 2.4.2
|
||||
|
||||
本版为批次任务增加人工评审标记,并调整任务列表布局。人工评审插件保持 **1.1.0**,第三方依赖版本不变。
|
||||
|
||||
## 更新
|
||||
|
||||
- 安装并启用人工评审插件后,任务存在已保存的有效评审内容时,批次列表显示“人工已审”。评审结论或非空备注均计入;清空全部内容后隐藏,禁用或卸载插件后也不显示。
|
||||
- 标记仅说明已有人工评审内容,不代表测试通过,不覆盖 AI 判定或改变任务执行流程。没有安装插件的用户不显示该标记。
|
||||
- 批次任务标题与状态分行显示:收藏星标位于标题框内,执行状态、AI 判定和人工已审标签在下方等高、左对齐排列。此布局适用于所有用户。
|
||||
- 按批次一次读取当前评审内容,沿用列表自动刷新,不逐条增加网络请求;重跑任务不继承旧任务的已审标记。
|
||||
- 补充发版约束:应用与第三方依赖独立版本化,检查锁文件差异,并验证最终源码包的全新安装。
|
||||
|
||||
## 验证
|
||||
|
||||
- 默认 Python 回归:3269 通过,2 跳过,8 按标记排除;Ruff、质量基线和核心类型检查通过。
|
||||
- 前端 ChromeHeadless:348 项通过;生产构建通过。
|
||||
- 内置浏览器使用隔离数据检查收藏星标、两行布局及有评审/无评审标签显示。未写入真实任务评审数据。
|
||||
- 最终源码包另行执行独立目录、空 npm 缓存的官方源和国内镜像安装及生产构建,结果随发布附件提供。
|
||||
|
||||
## 升级
|
||||
|
||||
已有安装请等待任务结束,运行 `update-cn.bat` 或 `update.bat` 后重启。新安装下载 `artemis-2.4.2-source.zip`,按 `SHA256SUMS.txt` 校验后启动。请勿直接覆盖旧安装目录。
|
||||
|
||||
人工评审插件仍需单独安装并启用;本次无需升级已安装的 1.1.0。发布过程不构建 Docker,不重启用户服务,不执行设备或真实业务任务。
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
[project]
|
||||
name = "artemis"
|
||||
version = "2.4.1"
|
||||
version = "2.4.2"
|
||||
description = "Autonomous AI Assistant for Mobile Automation"
|
||||
|
||||
authors = [
|
||||
|
||||
@@ -57,15 +57,16 @@ def manifest_payload():
|
||||
}
|
||||
|
||||
|
||||
def install(plugins, original_case=False):
|
||||
def install(plugins, original_case=False, plugin_id="review"):
|
||||
package = BytesIO()
|
||||
manifest = manifest_payload()
|
||||
manifest["id"] = plugin_id
|
||||
if original_case:
|
||||
manifest["workspace"]["panels"][0]["kind"] = "original_case"
|
||||
with zipfile.ZipFile(package, "w") as archive:
|
||||
archive.writestr("plugin.json", json.dumps(manifest))
|
||||
install_archive(plugins, package.getvalue())
|
||||
plugins.set_enabled("review", True)
|
||||
plugins.set_enabled(plugin_id, True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -108,6 +109,71 @@ def save(client, values, revision=0, request_id=None):
|
||||
)
|
||||
|
||||
|
||||
def test_batch_review_badge_tracks_current_values_and_plugin_lifecycle(context, monkeypatch):
|
||||
from apps.admin_console.routers import automation
|
||||
|
||||
store, plugins, client = context
|
||||
monkeypatch.setattr(automation, "store", store)
|
||||
result = {"status": "inconclusive", "source": "execution"}
|
||||
monkeypatch.setattr(automation, "read_reviewed_result", lambda *args, **kwargs: result)
|
||||
client.app.include_router(automation.router)
|
||||
store.create_batch({"id": "review-batch", "status": "completed"}, [], {})
|
||||
store.save_job(
|
||||
{"session_id": "batch-task", "batch_id": "review-batch", "status": "completed", "case": {}}
|
||||
)
|
||||
store.save_job(
|
||||
{"session_id": "new-run", "batch_id": "review-batch", "status": "completed", "case": {}}
|
||||
)
|
||||
|
||||
def badge():
|
||||
response = client.get("/api/automation/batches/review-batch")
|
||||
assert response.status_code == 200
|
||||
jobs = {job["session_id"]: job for job in response.json()["jobs"]}
|
||||
assert "batch-task" in jobs, response.json()
|
||||
assert jobs["batch-task"]["test_result"] == result
|
||||
assert jobs["new-run"]["human_reviewed"] is False
|
||||
return jobs["batch-task"]["human_reviewed"]
|
||||
|
||||
assert badge() is False
|
||||
install(plugins)
|
||||
assert (
|
||||
client.put(
|
||||
"/api/task-extensions/batch-task/review",
|
||||
json={
|
||||
"values": {"accuracy": "Pass"},
|
||||
"expected_revision": 0,
|
||||
"request_id": str(uuid4()),
|
||||
},
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
assert badge() is False # Other workspace plugins are not the human review plugin.
|
||||
install(plugins, plugin_id="task-review")
|
||||
assert badge() is False
|
||||
for revision, (values, expected) in enumerate(
|
||||
[
|
||||
({}, False),
|
||||
({"accuracy_note": " \n"}, False),
|
||||
({"accuracy": "Pass"}, True),
|
||||
({"accuracy": "Failed"}, True),
|
||||
({"accuracy": "", "accuracy_note": ""}, False),
|
||||
({"accuracy_note": "人工核对了截图"}, True),
|
||||
]
|
||||
):
|
||||
response = client.put(
|
||||
"/api/task-extensions/batch-task/task-review",
|
||||
json={"values": values, "expected_revision": revision, "request_id": str(uuid4())},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert badge() is expected
|
||||
plugins.set_enabled("task-review", False)
|
||||
assert badge() is False
|
||||
plugins.set_enabled("task-review", True)
|
||||
assert badge() is True
|
||||
plugins.uninstall("task-review")
|
||||
assert badge() is False
|
||||
|
||||
|
||||
def test_original_case_import_requires_enabled_plugin_and_keeps_execution_prompt(context):
|
||||
from apps.admin_console.services import task_import
|
||||
|
||||
|
||||
Reference in new issue
Block a user