Lane N3 (Co4E Studio) — dùng bộ workflow refactor-god-file, mỗi bước có characterization test trước khi tách, hậu kiểm ranh giới tầng sau mỗi bước: - skills_list_panel.py / agent_list_panel.py — 2 khu vực sidebar - co4e_canvas_widget.py + canvas_items.py + canvas_interaction_mixin.py — Co4ECanvas tách 3 file (vượt 400 dòng nếu đứng một mình) - node_property_panel.py + node_property_actions_mixin.py + step_config_section.py — StepConfigPanel, cùng lý do - co4e_run_control_widget.py — RunsPagePanel (trang Flow Status) - co4e_chat_view.py — ChatPanel + _ChatInput + helper autocomplete - palette_list.py — _PaletteList dời khỏi ui/co4e_tab.py, hết import ngược presentation -> ui (agent/skills panel giờ import top-level) ui/co4e_tab.py giảm 2089 -> 1878 dòng, chỉ còn phần wiring + business logic (Co4ERunManager/AgentWorker chưa đổi — nằm ngoài phạm vi này, xem docstring presentation/co4e/co4e_tab.py). ui/co4e_canvas.py và ui/co4e_config_panel.py còn lại là compat shim re-export, không đổi API cho bên gọi. Thêm tests/test_co4e_integration.py — dựng thật Co4ETab qua build_co4e_tab(), lái luồng qua nhiều panel trong cùng instance (thêm node, mở/gập chat, chuyển trang Flow Status rồi quay lại không mất state canvas) — bắt lỗi wiring xuyên-panel mà characterization test từng panel riêng không thấy được. Đã xác minh: pytest 348 passed/1 skipped, tools/check_co4e.py sạch, không file nào >400 dòng, domain/application không import PySide6, và so pixel before/after (git worktree tại HEAD cũ) ra 0/1.125.000 pixel khác biệt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
103 lines
4.4 KiB
Python
103 lines
4.4 KiB
Python
"""Smoke test cho ``presentation.co4e.co4e_tab.build_co4e_tab`` — gọi thật
|
|
factory, dựng thật ``Co4ETab``, xác nhận nó không vỡ.
|
|
|
|
Vì sao chạy trong tiến trình con thay vì import thẳng trong tiến trình pytest
|
|
chính: ``CONFIG_DIR`` (config.py) và ``CO4E_DIR`` (core/co4e.py) đều là hằng số
|
|
module tính MỘT LẦN lúc import từ ``Path.home()``. Nhiều file test khác trong
|
|
bộ này (chạy trước theo thứ tự collect) đã import ``cowork_local.config``/
|
|
``cowork_local.core.co4e`` với HOME thật rồi — monkeypatch thuộc tính module
|
|
(cách ``tests/characterization/test_co4e_run_manager_behavior.py`` dùng cho
|
|
``CO4E_DIR``) chỉ vá được đúng chỗ đó, còn ``AppConfig.load()`` có thêm một bẫy
|
|
riêng: tham số mặc định ``path: Path = CONFIG_PATH`` được gán MỘT LẦN lúc định
|
|
nghĩa hàm, nên monkeypatch ``CONFIG_PATH`` sau đó không đổi được giá trị mặc
|
|
định đã đóng băng — gọi ``AppConfig.load()`` không tham số vẫn đọc file thật
|
|
dù đã vá module. Dựng ``Co4ETab`` thật kéo theo cả hai đường trên (và có thể
|
|
còn đường khác chưa biết, vì lớp này 2000+ dòng). Cô lập bằng biến môi trường
|
|
``HOME``/``USERPROFILE`` TRƯỚC bất kỳ import nào, trong một tiến trình con
|
|
sạch hoàn toàn, né được toàn bộ lớp bẫy này một lần — không cần biết hết mọi
|
|
hằng số tính lúc import ở đâu trong file 2000+ dòng đó.
|
|
|
|
Bắt được gì: đổi sai độ sâu dấu chấm ở import tương đối trong
|
|
``presentation/co4e/co4e_tab.py`` (``from ...ui.co4e_tab import Co4ETab``),
|
|
đổi chữ ký ``Co4ETab.__init__`` mà quên sửa lệnh gọi trong factory, hoặc
|
|
factory trả sai kiểu/sai ``ctx`` — không có test nào khác trong bộ này gọi
|
|
``build_co4e_tab()``, nên đây là lưới an toàn DUY NHẤT cho hàm này.
|
|
|
|
KHÔNG chạm dữ liệu thật: sandbox trống hoàn toàn, không copy
|
|
``~/.cowork_local`` thật (khác ``tools/capture_screens.py::_isolate_home()`` —
|
|
ở đó cố tình copy để chụp ảnh có dữ liệu mẫu; ở đây không cần, càng sạch càng
|
|
tốt cho một smoke test).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
REPO_PARENT = REPO_ROOT.parent
|
|
|
|
_SMOKE_SCRIPT = """
|
|
import sys
|
|
sys.path.insert(0, {repo_parent!r})
|
|
|
|
from PySide6.QtWidgets import QApplication, QWidget
|
|
|
|
from cowork_local.config import AppConfig, CONFIG_DIR
|
|
from cowork_local.state import AppContext
|
|
from cowork_local.presentation.co4e.co4e_tab import build_co4e_tab
|
|
|
|
sandbox = sys.argv[1]
|
|
assert str(CONFIG_DIR).startswith(sandbox), "khong co lap: CONFIG_DIR=" + str(CONFIG_DIR)
|
|
|
|
app = QApplication([])
|
|
ctx = AppContext(AppConfig.load())
|
|
|
|
|
|
class _FakeWorkflowService:
|
|
\"\"\"Chua dung toi trong than ham build_co4e_tab hien tai (xem docstring
|
|
cua factory) - chi can mot doi tuong bat ky de kiem factory nhan dung
|
|
tham so bat buoc thu hai.\"\"\"
|
|
|
|
|
|
widget = build_co4e_tab(ctx, _FakeWorkflowService())
|
|
assert isinstance(widget, QWidget), "khong phai QWidget: " + repr(type(widget))
|
|
assert widget.ctx is ctx, "factory khong gan dung ctx cho widget tra ve"
|
|
print("SMOKE_OK")
|
|
"""
|
|
|
|
|
|
def test_build_co4e_tab_dung_that_va_gan_dung_ctx(tmp_path):
|
|
sandbox = tmp_path / "home"
|
|
sandbox.mkdir()
|
|
|
|
env = dict(os.environ)
|
|
env["HOME"] = str(sandbox)
|
|
env["USERPROFILE"] = str(sandbox)
|
|
env["QT_QPA_PLATFORM"] = "offscreen"
|
|
env.pop("HOMEDRIVE", None)
|
|
env.pop("HOMEPATH", None)
|
|
|
|
script = _SMOKE_SCRIPT.format(repo_parent=str(REPO_PARENT))
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", script, str(sandbox)],
|
|
cwd=str(REPO_ROOT),
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
|
|
assert result.returncode == 0, (
|
|
f"smoke build_co4e_tab that bai (exit {result.returncode}):\n"
|
|
f"--- stdout ---\n{result.stdout}\n--- stderr ---\n{result.stderr}"
|
|
)
|
|
assert "SMOKE_OK" in result.stdout, result.stdout
|
|
|
|
# Sandbox khong duoc dung: chua co gi ghi vao no truoc khi tien trinh con
|
|
# chay (con AppConfig.load() khong ghi gi ca - chi save() moi ghi).
|
|
assert not (sandbox / ".cowork_local").exists(), (
|
|
"AppConfig.load() khong duoc tu tao thu muc config that trong sandbox"
|
|
)
|