Align branding, prompts, and deployment tooling
This commit is contained in:
@@ -13,7 +13,7 @@ class _DummyToolkit:
|
||||
return ""
|
||||
|
||||
|
||||
def test_workspace_manager_creates_extended_agent_files(tmp_path):
|
||||
def test_workspace_manager_creates_core_agent_files(tmp_path):
|
||||
manager = WorkspaceManager(project_root=tmp_path)
|
||||
|
||||
manager.initialize_default_assets(
|
||||
@@ -27,7 +27,7 @@ def test_workspace_manager_creates_extended_agent_files(tmp_path):
|
||||
assert (asset_dir / "PROFILE.md").exists()
|
||||
assert (asset_dir / "AGENTS.md").exists()
|
||||
assert (asset_dir / "MEMORY.md").exists()
|
||||
assert (asset_dir / "HEARTBEAT.md").exists()
|
||||
assert (asset_dir / "POLICY.md").exists()
|
||||
assert (asset_dir / "agent.yaml").exists()
|
||||
assert (asset_dir / "skills" / "installed").is_dir()
|
||||
assert (asset_dir / "skills" / "active").is_dir()
|
||||
@@ -35,6 +35,22 @@ def test_workspace_manager_creates_extended_agent_files(tmp_path):
|
||||
assert (asset_dir / "skills" / "local").is_dir()
|
||||
|
||||
|
||||
def test_workspace_manager_seeds_risk_prompt_content(tmp_path):
|
||||
manager = WorkspaceManager(project_root=tmp_path)
|
||||
manager.initialize_default_assets(
|
||||
config_name="demo",
|
||||
agent_ids=["risk_manager"],
|
||||
analyst_personas={},
|
||||
)
|
||||
|
||||
asset_dir = tmp_path / "runs" / "demo" / "agents" / "risk_manager"
|
||||
soul = (asset_dir / "SOUL.md").read_text(encoding="utf-8")
|
||||
guide = (asset_dir / "AGENTS.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "风险管理经理" in soul
|
||||
assert "优先使用可用的风险工具量化集中度" in guide
|
||||
|
||||
|
||||
def test_agent_workspace_config_controls_prompt_files(tmp_path, monkeypatch):
|
||||
manager = WorkspaceManager(project_root=tmp_path)
|
||||
manager.initialize_default_assets(
|
||||
@@ -72,6 +88,32 @@ def test_agent_workspace_config_controls_prompt_files(tmp_path, monkeypatch):
|
||||
assert "profile-line" not in prompt
|
||||
|
||||
|
||||
def test_prompt_is_built_from_workspace_defaults_without_system_templates(tmp_path, monkeypatch):
|
||||
manager = WorkspaceManager(project_root=tmp_path)
|
||||
manager.initialize_default_assets(
|
||||
config_name="demo",
|
||||
agent_ids=["portfolio_manager"],
|
||||
analyst_personas={},
|
||||
)
|
||||
|
||||
from backend.agents import prompt_factory
|
||||
|
||||
monkeypatch.setattr(
|
||||
prompt_factory,
|
||||
"SkillsManager",
|
||||
lambda: SkillsManager(project_root=tmp_path),
|
||||
)
|
||||
|
||||
prompt = build_agent_system_prompt(
|
||||
agent_id="portfolio_manager",
|
||||
config_name="demo",
|
||||
toolkit=_DummyToolkit(),
|
||||
)
|
||||
|
||||
assert "投资组合经理" in prompt
|
||||
assert "使用 `make_decision` 工具记录每个股票的最终决策" in prompt
|
||||
|
||||
|
||||
def test_skills_manager_applies_agent_level_skill_toggles(tmp_path):
|
||||
builtin_root = tmp_path / "backend" / "skills" / "builtin"
|
||||
for skill_name in ("risk_review", "extra_guard"):
|
||||
|
||||
@@ -8,24 +8,6 @@ import pytest
|
||||
from backend.services import gateway_cycle_support, gateway_runtime_support
|
||||
|
||||
|
||||
class _DummyDashboard:
|
||||
def __init__(self):
|
||||
self.updated = []
|
||||
self.tickers = []
|
||||
self.initial_cash = None
|
||||
self.enable_memory = False
|
||||
self.days_total = 0
|
||||
|
||||
def update(self, **kwargs):
|
||||
self.updated.append(kwargs)
|
||||
|
||||
def stop(self):
|
||||
return None
|
||||
|
||||
def print_final_summary(self):
|
||||
return None
|
||||
|
||||
|
||||
class _DummyScheduler:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
@@ -128,7 +110,6 @@ def make_gateway_stub():
|
||||
},
|
||||
storage=_DummyStorage(),
|
||||
state_sync=_DummyStateSync(),
|
||||
_dashboard=_DummyDashboard(),
|
||||
_watchlist_ingest_task=None,
|
||||
_market_status_task=None,
|
||||
_backtest_task=None,
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for HeartbeatHook."""
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.agents.base.hooks import HeartbeatHook
|
||||
|
||||
|
||||
class TestHeartbeatHook:
|
||||
"""Tests for HeartbeatHook._read_heartbeat_content."""
|
||||
|
||||
def test_read_heartbeat_content_with_content(self, tmp_path):
|
||||
"""Test reading HEARTBEAT.md when it exists and has content."""
|
||||
ws_dir = tmp_path / "analyst_workspace"
|
||||
ws_dir.mkdir()
|
||||
hb_file = ws_dir / "HEARTBEAT.md"
|
||||
hb_file.write_text("# 定期主动检查\n\n- [ ] 持仓是否健康\n", encoding="utf-8")
|
||||
|
||||
hook = HeartbeatHook(workspace_dir=ws_dir)
|
||||
content = hook._read_heartbeat_content()
|
||||
|
||||
assert content is not None
|
||||
assert "# 定期主动检查" in content
|
||||
assert "持仓是否健康" in content
|
||||
|
||||
def test_read_heartbeat_content_absent(self, tmp_path):
|
||||
"""Test reading when HEARTBEAT.md does not exist."""
|
||||
ws_dir = tmp_path / "analyst_workspace"
|
||||
ws_dir.mkdir()
|
||||
|
||||
hook = HeartbeatHook(workspace_dir=ws_dir)
|
||||
content = hook._read_heartbeat_content()
|
||||
|
||||
assert content is None
|
||||
|
||||
def test_read_heartbeat_content_empty(self, tmp_path):
|
||||
"""Test reading when HEARTBEAT.md is empty."""
|
||||
ws_dir = tmp_path / "analyst_workspace"
|
||||
ws_dir.mkdir()
|
||||
hb_file = ws_dir / "HEARTBEAT.md"
|
||||
hb_file.write_text("", encoding="utf-8")
|
||||
|
||||
hook = HeartbeatHook(workspace_dir=ws_dir)
|
||||
content = hook._read_heartbeat_content()
|
||||
|
||||
assert content is None
|
||||
|
||||
def test_read_heartbeat_content_whitespace_only(self, tmp_path):
|
||||
"""Test reading when HEARTBEAT.md contains only whitespace."""
|
||||
ws_dir = tmp_path / "analyst_workspace"
|
||||
ws_dir.mkdir()
|
||||
hb_file = ws_dir / "HEARTBEAT.md"
|
||||
hb_file.write_text(" \n\n ", encoding="utf-8")
|
||||
|
||||
hook = HeartbeatHook(workspace_dir=ws_dir)
|
||||
content = hook._read_heartbeat_content()
|
||||
|
||||
assert content is None
|
||||
|
||||
def test_completed_flag_path(self, tmp_path):
|
||||
"""Test that completion flag is placed in workspace directory."""
|
||||
ws_dir = tmp_path / "analyst_workspace"
|
||||
ws_dir.mkdir()
|
||||
|
||||
hook = HeartbeatHook(workspace_dir=ws_dir)
|
||||
|
||||
assert hook._completed_flag == ws_dir / ".heartbeat_completed"
|
||||
81
backend/tests/test_market_ingest.py
Normal file
81
backend/tests/test_market_ingest.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for market ingest watermark handling."""
|
||||
|
||||
from backend.data import market_ingest
|
||||
|
||||
|
||||
class _FakeStore:
|
||||
def __init__(self, *, last_news_fetch=None, latest_news_date=None):
|
||||
self._watermarks = {
|
||||
"symbol": "AAPL",
|
||||
"last_price_fetch": None,
|
||||
"last_news_fetch": last_news_fetch,
|
||||
}
|
||||
self._latest_news_date = latest_news_date
|
||||
self.updated = []
|
||||
|
||||
def get_ticker_watermarks(self, symbol):
|
||||
return dict(self._watermarks)
|
||||
|
||||
def get_latest_news_date(self, symbol):
|
||||
return self._latest_news_date
|
||||
|
||||
def upsert_ticker(self, **kwargs):
|
||||
return None
|
||||
|
||||
def upsert_ohlc(self, symbol, rows, source="polygon"):
|
||||
return len(rows)
|
||||
|
||||
def upsert_news(self, symbol, rows, source="polygon"):
|
||||
return len(rows)
|
||||
|
||||
def update_fetch_watermark(self, **kwargs):
|
||||
self.updated.append(kwargs)
|
||||
|
||||
|
||||
def test_refresh_news_incremental_does_not_advance_watermark_without_news(monkeypatch):
|
||||
store = _FakeStore(last_news_fetch="2026-03-28", latest_news_date="2026-03-28")
|
||||
|
||||
monkeypatch.setattr(market_ingest, "fetch_ticker_details", lambda ticker: {"name": ticker, "sic_description": None, "active": True})
|
||||
|
||||
class _Router:
|
||||
def get_company_news(self, **kwargs):
|
||||
return [], "polygon"
|
||||
|
||||
monkeypatch.setattr(market_ingest, "DataProviderRouter", lambda: _Router())
|
||||
monkeypatch.setattr(market_ingest, "align_news_for_symbol", lambda store, ticker: 0)
|
||||
|
||||
result = market_ingest.refresh_news_incremental(
|
||||
"AAPL",
|
||||
end_date="2026-03-29",
|
||||
store=store,
|
||||
)
|
||||
|
||||
assert result["start_news_date"] == "2026-03-29"
|
||||
assert result["news"] == 0
|
||||
assert store.updated[-1]["news_date"] is None
|
||||
|
||||
|
||||
def test_refresh_news_incremental_clamps_future_watermark_to_latest_stored_date(monkeypatch):
|
||||
store = _FakeStore(last_news_fetch="2026-03-30", latest_news_date="2026-03-28")
|
||||
captured = {}
|
||||
|
||||
monkeypatch.setattr(market_ingest, "fetch_ticker_details", lambda ticker: {"name": ticker, "sic_description": None, "active": True})
|
||||
|
||||
class _Router:
|
||||
def get_company_news(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return [], "polygon"
|
||||
|
||||
monkeypatch.setattr(market_ingest, "DataProviderRouter", lambda: _Router())
|
||||
monkeypatch.setattr(market_ingest, "align_news_for_symbol", lambda store, ticker: 0)
|
||||
|
||||
result = market_ingest.refresh_news_incremental(
|
||||
"AAPL",
|
||||
end_date="2026-03-29",
|
||||
store=store,
|
||||
)
|
||||
|
||||
assert result["start_news_date"] == "2026-03-29"
|
||||
assert captured["start_date"] == "2026-03-29"
|
||||
assert captured["end_date"] == "2026-03-29"
|
||||
Reference in New Issue
Block a user