## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Mọi chỗ trong repo gán ``config.X = ...`` thì repository phải nhận được.
|
||||
|
||||
Bài này sinh ra từ một lỗi thật ngày 26/08.
|
||||
|
||||
R02 tráo ``config.py::AppConfig`` bằng ``JsonConfigRepository``. Trước khi
|
||||
tráo tôi có đối chiếu API: đếm đủ 34/34 thành viên công khai, không thiếu cái
|
||||
nào, nên kết luận là thay được. Đối chiếu đó **chỉ so tên**, không so việc một
|
||||
``property`` có setter hay không.
|
||||
|
||||
``AppConfig`` cũ là dataclass, ``config.language = "vi"`` chạy bình thường.
|
||||
Repository để ``language`` là property chỉ đọc, gán vào là ``AttributeError``.
|
||||
Ba chỗ trong ``app.py`` đang gán như thế: đổi ngôn ngữ, đổi giao diện, đổi
|
||||
provider trên thanh bên.
|
||||
|
||||
Điều làm nó khó thấy: cả ba đều nằm trong slot của Qt, mà Qt **nuốt ngoại lệ
|
||||
trong slot**. Không có traceback, không có thông báo — người dùng bấm đổi ngôn
|
||||
ngữ thì không có gì xảy ra. Bộ test đơn vị vẫn 709 xanh; chỉ ``check_nav`` bắt
|
||||
được vì nó bấm thật vào combo rồi kiểm ngôn ngữ có đổi không.
|
||||
|
||||
Nên bài này đi ngược từ mã nguồn: tìm mọi chỗ gán, rồi thử gán thật.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
|
||||
#: ``config.X = ...`` nhưng không phải ``==``.
|
||||
GAN = re.compile(r"\bconfig\.([a-z_][a-z_0-9]*)\s*=(?!=)")
|
||||
|
||||
#: Không phải thuộc tính cấu hình — là chỗ chứa chính đối tượng config.
|
||||
BO_QUA = {"data", "config"}
|
||||
|
||||
|
||||
def _cho_gan() -> set[str]:
|
||||
out = subprocess.run(["git", "ls-files", "*.py"], cwd=REPO,
|
||||
capture_output=True, text=True, encoding="utf-8",
|
||||
errors="replace").stdout.split()
|
||||
ten: set[str] = set()
|
||||
for f in out:
|
||||
p = REPO / f
|
||||
if not p.is_file():
|
||||
continue
|
||||
for m in GAN.finditer(p.read_text(encoding="utf-8", errors="replace")):
|
||||
if m.group(1) not in BO_QUA and not m.group(1).startswith("_"):
|
||||
ten.add(m.group(1))
|
||||
return ten
|
||||
|
||||
|
||||
def _repo(tmp_path):
|
||||
from cowork_local.config import DEFAULT_CONFIG
|
||||
from cowork_local.infrastructure.config.json_config_repository import (
|
||||
JsonConfigRepository,
|
||||
)
|
||||
return JsonConfigRepository.from_data(copy.deepcopy(DEFAULT_CONFIG),
|
||||
tmp_path / "config.json")
|
||||
|
||||
|
||||
def test_tim_duoc_cho_gan():
|
||||
"""Bảo vệ chính bài test: biểu thức tìm kiếm hỏng thì nó lặng lẽ xanh."""
|
||||
ten = _cho_gan()
|
||||
assert ten, "không tìm thấy chỗ nào gán config.X — kiểm lại GAN"
|
||||
assert "language" in ten, f"phải thấy config.language (thấy: {sorted(ten)})"
|
||||
|
||||
|
||||
def test_moi_thuoc_tinh_bi_gan_deu_gan_duoc(tmp_path):
|
||||
cfg = _repo(tmp_path)
|
||||
hong = []
|
||||
for ten in sorted(_cho_gan()):
|
||||
if not hasattr(type(cfg), ten) and not hasattr(cfg, ten):
|
||||
continue # thuộc tính của lớp khác, không phải config
|
||||
cu = getattr(cfg, ten, None)
|
||||
try:
|
||||
setattr(cfg, ten, cu)
|
||||
except AttributeError:
|
||||
hong.append(ten)
|
||||
|
||||
assert not hong, (
|
||||
"Repository không nhận gán, nhưng trong mã nguồn có chỗ gán:\n "
|
||||
+ "\n ".join("config.%s = ..." % t for t in hong)
|
||||
+ "\nQt nuốt AttributeError trong slot, nên chỗ đó sẽ im lặng không "
|
||||
"làm gì. Thêm @<tên>.setter vào JsonConfigRepository."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ten,gia_tri", [("language", "ja"), ("theme", "light"),
|
||||
("active_provider", "ollama")])
|
||||
def test_ba_cho_app_py_dang_gan(tmp_path, ten, gia_tri):
|
||||
"""Chốt riêng ba cái app.py gán, để bài trên có hỏng thì vẫn còn lưới."""
|
||||
cfg = _repo(tmp_path)
|
||||
setattr(cfg, ten, gia_tri)
|
||||
assert getattr(cfg, ten) == gia_tri
|
||||
Reference in New Issue
Block a user