2026-08-03 18:42:47 +08:00
|
|
|
from pathlib import Path
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
if str(ROOT) not in sys.path:
|
|
|
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
|
|
|
|
|
|
from apps.cli.run_config_autofill import build_rows, merge_rows
|
|
|
|
|
from apps.cli.autofill_common import scan_files
|
2026-08-05 09:40:45 +08:00
|
|
|
from gfa_core.mas.agents.router_runtime import RouterRuntime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_direct_config_open_commands_map_to_config_dialog_action():
|
|
|
|
|
for query in (
|
|
|
|
|
"打开 config",
|
|
|
|
|
"打开 config.xlsx",
|
|
|
|
|
"重新打开配置表",
|
|
|
|
|
"帮我打开当前 job 的配置",
|
|
|
|
|
):
|
|
|
|
|
assert RouterRuntime._config_dialog_ui_action(query) == "open_config_dialog"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_config_discussion_does_not_map_to_config_dialog_action():
|
|
|
|
|
for query in (
|
|
|
|
|
"读取 config.xlsx 中有哪些物种",
|
|
|
|
|
"检查 config 是否正确",
|
|
|
|
|
"我已经打开 config,下一步做什么",
|
|
|
|
|
"config 为什么打不开",
|
|
|
|
|
"config 已确认,执行数据准备",
|
|
|
|
|
):
|
|
|
|
|
assert RouterRuntime._config_dialog_ui_action(query) is None
|
2026-08-03 18:42:47 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_at_spl_pep_upload_is_detected_as_seed_reference(tmp_path):
|
|
|
|
|
input_dir = tmp_path / "input"
|
|
|
|
|
input_dir.mkdir()
|
|
|
|
|
(input_dir / "At.SPL.pep.fa").write_text(">AT1G01010\nMAAA\n", encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
rows = build_rows(
|
|
|
|
|
input_dir=input_dir,
|
|
|
|
|
source_dir=input_dir,
|
|
|
|
|
description="",
|
|
|
|
|
species_code="",
|
|
|
|
|
gene_family="SPL",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
row = next(item for item in rows if item["Original_File_Name"] == "At.SPL.pep.fa")
|
|
|
|
|
assert row["Species_Code"] == "At"
|
|
|
|
|
assert row["Gene_Family"] == "SPL"
|
|
|
|
|
assert row["File_Type"] == "pep"
|
|
|
|
|
assert row["Ref"] == "TRUE"
|
|
|
|
|
assert row["Species_Role"] == "seed_reference"
|
|
|
|
|
assert row["File_Scope"] == "known_family"
|
|
|
|
|
assert row["confidence"] == "high"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_scan_files_uses_job_input_but_ignores_generated_or_archive_dirs(tmp_path):
|
|
|
|
|
input_dir = tmp_path / "input"
|
|
|
|
|
input_dir.mkdir()
|
|
|
|
|
(input_dir / "At.SPL.pep.fa").write_text(">AT\nMA\n", encoding="utf-8")
|
|
|
|
|
(input_dir / "final").mkdir()
|
|
|
|
|
(input_dir / "final" / "At.SPL.pep.fa").write_text(">generated\nMA\n", encoding="utf-8")
|
|
|
|
|
(input_dir / "uploaded_configs").mkdir()
|
|
|
|
|
(input_dir / "uploaded_configs" / "At.SPL.pep.fa").write_text(">archive\nMA\n", encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
scanned = [path.relative_to(input_dir).as_posix() for path in scan_files(input_dir)]
|
|
|
|
|
|
|
|
|
|
assert scanned == ["At.SPL.pep.fa"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_regenerated_draft_merges_new_upload_without_dropping_existing_rows(tmp_path):
|
|
|
|
|
input_dir = tmp_path / "input"
|
|
|
|
|
input_dir.mkdir()
|
|
|
|
|
(input_dir / "At.SPL.pep.fa").write_text(">AT1G01010\nMAAA\n", encoding="utf-8")
|
|
|
|
|
existing_rows = [
|
|
|
|
|
{
|
|
|
|
|
"Original_File_Name": "Arabidopsis_thaliana.TAIR10.pep.all.fa",
|
|
|
|
|
"Species_Code": "At",
|
|
|
|
|
"Gene_Family": "",
|
|
|
|
|
"File_Type": "pep",
|
|
|
|
|
"Ref": "TRUE",
|
|
|
|
|
"Species_Role": "seed_reference",
|
|
|
|
|
"File_Scope": "known_family",
|
|
|
|
|
"confidence": "high",
|
|
|
|
|
"notes": "",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
detected_rows = build_rows(input_dir, input_dir, "", "", "SPL")
|
|
|
|
|
merged = merge_rows(existing_rows, detected_rows)
|
|
|
|
|
names = [row["Original_File_Name"] for row in merged]
|
|
|
|
|
|
|
|
|
|
assert "Arabidopsis_thaliana.TAIR10.pep.all.fa" in names
|
|
|
|
|
assert "At.SPL.pep.fa" in names
|
|
|
|
|
assert len(names) == len(set(names))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_frontend_autofill_entrypoints_keep_open_and_regenerate_separate():
|
|
|
|
|
html = (ROOT / "apps/web/vue3/index.html").read_text(encoding="utf-8")
|
|
|
|
|
queue_start = html.index("const queueAutofillFromPayload")
|
|
|
|
|
queue_end = html.index("const handleUploadSuccess", queue_start)
|
|
|
|
|
queue_block = html[queue_start:queue_end]
|
|
|
|
|
|
|
|
|
|
assert queue_block.count("sourceDir: ''") >= 2
|
|
|
|
|
assert "await openAutofillDialog('config'" in queue_block
|
|
|
|
|
assert "await openAutofillDialog('samples'" in queue_block
|
|
|
|
|
assert "const openExistingAutofillDraft" in html
|
|
|
|
|
assert "options.keepCurrentRows && !(autofillState.rows && autofillState.rows.length)" in html
|
|
|
|
|
assert "autoGenerate: wantsAutofillRegenerate" in html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_backend_open_endpoints_prefer_draft_then_confirmed_file():
|
|
|
|
|
main_py = (ROOT / "apps/web/vue3/main.py").read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
assert '@app.post("/api/autofill/config/open")' in main_py
|
|
|
|
|
assert 'workspace_dir / "final" / "config.autofill.draft.xlsx"' in main_py
|
|
|
|
|
assert 'input_dir / "config.xlsx"' in main_py
|
|
|
|
|
assert '@app.post("/api/autofill/samples/open")' in main_py
|
|
|
|
|
assert 'workspace_dir / "final" / "samples.autofill.draft.yaml"' in main_py
|
|
|
|
|
assert 'input_dir / "samples.yaml"' in main_py
|
|
|
|
|
|
|
|
|
|
|
2026-08-05 09:40:45 +08:00
|
|
|
def test_chat_config_dialog_action_is_transmitted_and_opens_current_job_config():
|
|
|
|
|
main_py = (ROOT / "apps/web/vue3/main.py").read_text(encoding="utf-8")
|
|
|
|
|
html = (ROOT / "apps/web/vue3/index.html").read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
assert '"ui_action": outcome.get("ui_action")' in main_py
|
|
|
|
|
assert "data.intent.ui_action === 'open_config_dialog'" in html
|
|
|
|
|
assert "await openAutofillDialog('config'" in html
|
|
|
|
|
assert "await openExistingAutofillDraft('config')" in html
|
|
|
|
|
|
|
|
|
|
|
2026-08-03 18:42:47 +08:00
|
|
|
def test_samples_confirm_no_longer_blocks_on_pending_de_roles_and_closes_dialog():
|
|
|
|
|
html = (ROOT / "apps/web/vue3/index.html").read_text(encoding="utf-8")
|
|
|
|
|
confirm_start = html.index("const confirmAutofillDraft")
|
|
|
|
|
confirm_end = html.index("const loadFromPath", confirm_start)
|
|
|
|
|
confirm_block = html[confirm_start:confirm_end]
|
|
|
|
|
|
|
|
|
|
assert "链特异性为必填" in confirm_block
|
|
|
|
|
assert "分组角色为必填" not in confirm_block
|
|
|
|
|
assert "请在样本表中标记唯一对照组" not in confirm_block
|
|
|
|
|
assert "请在样本表中至少标记一个比较组" not in confirm_block
|
|
|
|
|
assert "autofillDialogVisible.value = false" in confirm_block
|
|
|
|
|
|