chore(refactor): mục chung của Team Gamma — khung, hợp đồng, cổng CASAN
Sáu việc trong "mục chung" của bản phân công, làm trước khi ba nhánh tính năng tách ra. 1. Khung 5 tầng theo đúng đường dẫn plan.md: domain/ application/ infrastructure/ presentation/ platform/ + tests/fakes/ — 38 __init__.py. Trước đó là 0 file, mà mọi task của cả ba người đều ghi vào đây. Đã kiểm platform/ không che khuất module platform của stdlib. 2. Hợp đồng SecretStore và ConfigRepository (Protocol, chưa cài đặt) + fake chạy trong bộ nhớ. Danh sách thuộc tính không bịa: đếm 156 lời gọi ctx.config.* trong 29 file rồi lấy những cái dùng thật, xếp theo số lần. Cố ý bỏ config.data (36 lời gọi, nhiều nhất) — bê dict thô sang kiến trúc mới là bê nguyên vấn đề cũ. 3. tests/test_contracts.py — bài nghiệm thu, không phải test cho vui. Bài chính chạy tiến trình riêng và khẳng định dùng fake KHÔNG kéo theo cowork_local.config lẫn PySide6; đó là điều kiện để N2 và N3 code ngay hôm nay thay vì đợi bản thật ngày 23 và 26/08. 4. scripts/audit_security.py — CASAN Check 1, Gamma chủ trì (hạn 30/08). Viết sớm để kiểm liên tục trong lúc chuyển API key, không đợi tới ngày cổng. Lần chạy đầu ra 3 báo động giả (secret_in_output là tên quy tắc, api_key="x" là dữ liệu test) nên đã siết: ngưỡng độ dài, hằng liệt kê, hình dạng khoá i18n, và dấu "# casan: allow" làm lối thoát chuẩn. --self-test cắm 4 credential thật + 5 mẫu vô hại để chứng minh nó còn cắn được — một máy quét không tìm thấy gì chỉ có giá trị nếu chứng minh được nó biết tìm. 5. Ba check CASAN vào CI, chạy mọi PR thay vì dồn tới 30/08. Check 2 và 3 thuộc Team Hoa và Team Duy, chưa có script — bước CI bỏ qua nếu file chưa tồn tại, để thêm cổng không làm đỏ CI của hai team kia. 6. docs/refactor/GammaTeam_decisions.md — hai quyết định chờ nhóm trưởng chốt: provider_conf() còn trả api_key hay không (ảnh hưởng 5 nơi, 3 nằm ngoài team), và số phận 24 checker UI sẽ vỡ khi file bị dời. 96 test xanh (90 cũ + 6 mới). CASAN Check 1: 0 credential lộ. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
09b1c93624
commit
8a9ee5f875
@@ -0,0 +1 @@
|
||||
"""Test double dùng chung cho cả 3 team — không phụ thuộc Qt."""
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Bản giả của ConfigRepository và SecretStore — chạy trong bộ nhớ.
|
||||
|
||||
Dùng để N2 (Giám sát) và N3 (Co4E) code và test ngay từ 21/08, không phải đợi
|
||||
bản thật xong ngày 23/08 và 26/08.
|
||||
|
||||
Không chạm đĩa, không chạm keyring, không cần Qt. Test dùng nó chạy trong vài
|
||||
mili giây.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
class FakeSecretStore:
|
||||
"""SecretStore trong bộ nhớ.
|
||||
|
||||
>>> s = FakeSecretStore({"provider:openai": "sk-test"})
|
||||
>>> s.get("provider:openai")
|
||||
'sk-test'
|
||||
>>> s.get("provider:chua-co") is None
|
||||
True
|
||||
"""
|
||||
|
||||
def __init__(self, seed: Dict[str, str] | None = None):
|
||||
self._items: Dict[str, str] = dict(seed or {})
|
||||
|
||||
def get(self, key: str) -> str | None:
|
||||
return self._items.get(key)
|
||||
|
||||
def set(self, key: str, value: str) -> None:
|
||||
self._items[key] = value
|
||||
|
||||
def delete(self, key: str) -> None:
|
||||
self._items.pop(key, None)
|
||||
|
||||
def has(self, key: str) -> bool:
|
||||
return key in self._items
|
||||
|
||||
|
||||
class FakeConfigRepository:
|
||||
"""ConfigRepository trong bộ nhớ, có sẵn giá trị mặc định hợp lý.
|
||||
|
||||
Mọi thứ ghi đè được qua tham số khởi tạo, nên test dựng đúng tình huống
|
||||
mình cần::
|
||||
|
||||
cfg = FakeConfigRepository(theme="light", shared_dir="/tmp/chung")
|
||||
"""
|
||||
|
||||
def __init__(self, *, active_provider: str = "ollama",
|
||||
providers: Dict[str, Dict[str, Any]] | None = None,
|
||||
shared_dir: str = "", theme: str = "dark", language: str = "vi",
|
||||
routing: Dict[str, Any] | None = None,
|
||||
auth: Dict[str, Any] | None = None,
|
||||
agent_security: Dict[str, Any] | None = None,
|
||||
tools_disabled: list[str] | None = None,
|
||||
history_dir: Path | None = None,
|
||||
output_dir: Path | None = None):
|
||||
self._active_provider = active_provider
|
||||
self._providers = providers or {
|
||||
"ollama": {"base_url": "http://localhost:11434/v1", "model": "llama3"},
|
||||
"openai": {"base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini"},
|
||||
}
|
||||
self._shared_dir = shared_dir
|
||||
self._theme = theme
|
||||
self._language = language
|
||||
self._routing = routing or {"mode": "off"}
|
||||
self._auth = auth or {}
|
||||
self._agent_security = agent_security or {"cowork_confirm_commands": True}
|
||||
self._tools_disabled = list(tools_disabled or [])
|
||||
self._history_dir = history_dir or Path("/fake/history")
|
||||
self._output_dir = output_dir or Path("/fake/workspace")
|
||||
#: số lần save() được gọi — để test khẳng định "có ghi" mà không cần đĩa
|
||||
self.saves = 0
|
||||
|
||||
# ---- provider ------------------------------------------------------
|
||||
@property
|
||||
def active_provider(self) -> str:
|
||||
return self._active_provider
|
||||
|
||||
def set_active_provider(self, name: str) -> None:
|
||||
self._active_provider = name
|
||||
|
||||
def provider_conf(self, name: str | None = None) -> Dict[str, Any]:
|
||||
return dict(self._providers.get(name or self._active_provider, {}))
|
||||
|
||||
# ---- đường dẫn -----------------------------------------------------
|
||||
@property
|
||||
def shared_dir(self) -> str:
|
||||
return self._shared_dir
|
||||
|
||||
def history_dir(self) -> Path:
|
||||
return self._history_dir
|
||||
|
||||
def cowork_output_dir(self) -> Path:
|
||||
return self._output_dir
|
||||
|
||||
# ---- giao diện -----------------------------------------------------
|
||||
@property
|
||||
def theme(self) -> str:
|
||||
return self._theme
|
||||
|
||||
def set_theme(self, value: str) -> None:
|
||||
self._theme = value
|
||||
|
||||
@property
|
||||
def language(self) -> str:
|
||||
return self._language
|
||||
|
||||
def set_language(self, value: str) -> None:
|
||||
self._language = value
|
||||
|
||||
# ---- nhóm cấu hình --------------------------------------------------
|
||||
@property
|
||||
def routing(self) -> Dict[str, Any]:
|
||||
return self._routing
|
||||
|
||||
@property
|
||||
def auth(self) -> Dict[str, Any]:
|
||||
return self._auth
|
||||
|
||||
@property
|
||||
def agent_security(self) -> Dict[str, Any]:
|
||||
return self._agent_security
|
||||
|
||||
@property
|
||||
def tools_disabled(self) -> list[str]:
|
||||
return list(self._tools_disabled)
|
||||
|
||||
def set_tool_enabled(self, name: str, enabled: bool) -> None:
|
||||
if enabled:
|
||||
self._tools_disabled = [t for t in self._tools_disabled if t != name]
|
||||
elif name not in self._tools_disabled:
|
||||
self._tools_disabled.append(name)
|
||||
|
||||
# ---- ghi ------------------------------------------------------------
|
||||
def save(self) -> None:
|
||||
self.saves += 1
|
||||
Reference in New Issue
Block a user