Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f2dbafa
test(coverage-sprint): 覆盖率冲刺第一批——22个新测试文件、155个新用例
yutai78786 Aug 18, 2026
9ce071a
test(coverage-sprint): 覆盖率冲刺第二批——20个新测试文件、108个新用例
yutai78786 Aug 18, 2026
7ac8818
fix: 修复 pre-commit 检查问题
yutai78786 Aug 18, 2026
d31dcec
fix: 修复 router 测试文件的 pylint E0402 和格式问题
yutai78786 Aug 18, 2026
0be82be
fix: 改用 pytest fixture 注入 app_server,消除 pylint E0611
yutai78786 Aug 18, 2026
c3ac142
style: black 格式化 router 测试文件
yutai78786 Aug 18, 2026
6467c07
fix(coverage-sprint): 修复9条失败用例——端点路径、返回类型、枚举值全部改用例
yutai78786 Aug 18, 2026
c2b4e4a
fix(coverage-sprint): 沙箱测试改用 tempfile 兼容 Windows
yutai78786 Aug 18, 2026
3256fb1
fix(coverage-sprint): 恢复与上游 #7103 撞车的 3 个文件为上游版本
yutai78786 Aug 19, 2026
38fcd28
fix(coverage-sprint): 补齐用例 marker——247条全部可被 fork CI 收集执行
yutai78786 Aug 20, 2026
f712c41
fix(coverage-sprint): 19个旧async文件改现行api_request风格 + 18文件假端点改真实端点
yutai78786 Aug 20, 2026
61eb98d
fix(coverage-sprint): 36条失败用例按真实返回修断言/端点(本地全量实锤)
yutai78786 Aug 21, 2026
a78914d
fix(coverage-sprint): approval list 返回 dict(pending_approvals 列表),修最后…
yutai78786 Aug 21, 2026
0ad447d
style: pre-commit 自动修复(black/尾逗号/尾空格)+ 两行折行过 flake8
yutai78786 Aug 21, 2026
0329050
style: pre-commit 自动修复(add-trailing-comma 尾逗号 + black 折行),12 个 router…
yutai78786 Aug 21, 2026
88a393d
test(integration): poll until qq channel health reports running (flak…
yutai78786 Aug 22, 2026
32f5a3c
鬼谷子: test_acp_start_spawns_mock_runner 断言改带超时日志轮询,修时序抖动
yutai78786 Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions tests/integration/test_access_control_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""Integration tests for Access Control API endpoints.

Tests cover:
- GET /api/access-control: get access control settings
- POST /api/access-control: update access control settings
"""

import pytest


@pytest.mark.integration
@pytest.mark.p1
def test_access_control_get(app_server) -> None:
"""Test GET /api/access-control returns access control settings."""
response = app_server.api_request("GET", "/api/access-control")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)


@pytest.mark.integration
@pytest.mark.p1
def test_access_control_update_invalid(app_server) -> None:
"""Test POST /api/access-control with invalid data."""
response = app_server.api_request(
"POST",
"/api/access-control/pending/approve",
json={},
)
# Should handle gracefully
assert response.status_code in [200, 400, 422]


@pytest.mark.integration
@pytest.mark.p1
def test_access_control_structure(app_server) -> None:
"""Test access control response structure."""
response = app_server.api_request("GET", "/api/access-control")
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)
# Should have access control related fields
assert len(data) >= 0


@pytest.mark.integration
@pytest.mark.p1
def test_access_control_update_partial(app_server) -> None:
"""Test POST /api/access-control with partial update."""
# Try to update with empty dict
response = app_server.api_request(
"POST",
"/api/access-control/pending/approve",
json={},
)
assert response.status_code == 422


@pytest.mark.integration
@pytest.mark.p1
def test_access_control_get_specific(app_server) -> None:
"""Test GET /api/access-control with specific key."""
response = app_server.api_request("GET", "/api/access-control/console")
assert response.status_code in [200, 404]
65 changes: 61 additions & 4 deletions tests/integration/test_acp_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
_NEVER_FIRE_SCHEDULE = "0 0 1 1 *"
_MOCK_RUNNER_NAME = "mock_runner"
_MOCK_RUNNER_PATH = Path(__file__).parent / "fixtures" / "acp_mock_runner.py"
# Bound for polling the runner reply out of the server log buffer.
_REPLY_WAIT_SECS = 30.0


# ------------------------------------------------------------------ #
Expand Down Expand Up @@ -190,6 +192,40 @@ def _delete_job(app_server, job_id):
pass


def _wait_for_log_marker(
app_server,
marker: str,
baseline: int,
deadline: float,
) -> str | None:
"""Poll the live server log buffer for ``marker`` after ``baseline``.

``baseline`` is ``len(app_server.logs)`` captured before the run so
only lines produced by *this* test's execution are searched (the
module-scoped server's buffer also carries earlier tests' replies).

Why polling instead of a one-shot ``logs_tail`` snapshot (fork run
32445098533, macOS job 96676931023):

1. The reply travels through several async hops after cron reports
``status=success`` in history (stream events → console channel
print → tee thread append), so it can land after the assertion
point.
2. The setup config writes (provider registration, ACP config PUT,
tool toggle) each schedule an async zero-downtime reload whose
workspace-rebuild logs (command registration, watchers, …) can
exceed any fixed tail window and push an already-printed reply
out of it — even though the full chain succeeded.
"""
while time.time() < deadline:
new_lines = app_server.logs[baseline:]
for line in new_lines:
if marker in line:
return line
time.sleep(0.5)
return None


# ------------------------------------------------------------------ #
# A1: list runners
# ------------------------------------------------------------------ #
Expand Down Expand Up @@ -365,6 +401,9 @@ def test_acp_start_spawns_mock_runner(app_server, mock_llm) -> None:

spec = _agent_spec("acp_start_real")
job_id = _create_job(app_server, spec)
# Baseline for scoped log polling: only lines produced from this
# point on belong to this test run (see _wait_for_log_marker).
log_baseline = len(app_server.logs)
try:
run_resp = app_server.api_request(
"POST",
Expand All @@ -385,11 +424,29 @@ def test_acp_start_spawns_mock_runner(app_server, mock_llm) -> None:
# text "mock reply" (ACP_MOCK_REPLY_TEXT). Assert it surfaces in
# the server logs — proves the JSON-RPC reply is wired back into
# the agent response path, not just that cron returned success.
logs = app_server.logs_tail(20000)
assert "mock reply" in logs, (
"ACP runner reply not surfaced to agent runtime:\n"
f"{logs[-3000:]}"
#
# Poll the live log buffer with a generous deadline instead of a
# one-shot tail snapshot: the reply is printed after cron
# history lands, and async zero-downtime reloads triggered by
# this test's own config writes flood the tail of the buffer
# with workspace-rebuild logs (fork run 32445098533 showed the
# full chain succeeding while the reply was pushed out of a
# fixed 20000-char window).
reply_line = _wait_for_log_marker(
app_server,
"mock reply",
log_baseline,
time.time() + _REPLY_WAIT_SECS,
)
if reply_line is None:
scoped_new = "".join(app_server.logs[log_baseline:])
tool_called = "delegate_external_agent" in scoped_new
raise AssertionError(
"ACP runner reply not surfaced to agent runtime within "
f"{_REPLY_WAIT_SECS:.0f}s "
f"(delegate_external_agent invoked: {tool_called}):\n"
f"{scoped_new[-3000:]}",
)
finally:
_delete_job(app_server, job_id)
srv.force_tool_call = False
Expand Down
74 changes: 74 additions & 0 deletions tests/integration/test_agent_scoped_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
"""Integration tests for Agent Scoped API endpoints.

Tests cover:
- GET /api/agent-scoped: get agent-scoped settings
- POST /api/agent-scoped: update agent-scoped settings
"""

import pytest


@pytest.mark.integration
@pytest.mark.p1
def test_agent_scoped_get(app_server) -> None:
"""Test GET /api/agent-scoped returns agent-scoped settings."""
response = app_server.api_request(
"GET",
"/api/agents/default/agent-status",
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)


@pytest.mark.integration
@pytest.mark.p1
def test_agent_scoped_update_invalid(app_server) -> None:
"""Test POST /api/agent-scoped with invalid data."""
response = app_server.api_request(
"POST",
"/api/agents/default/cron/jobs",
json={},
)
# Should handle gracefully
assert response.status_code in [200, 400, 422]


@pytest.mark.integration
@pytest.mark.p1
def test_agent_scoped_structure(app_server) -> None:
"""Test agent-scoped response structure."""
response = app_server.api_request(
"GET",
"/api/agents/default/agent-status",
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, dict)
# Should have agent-scoped fields
assert len(data) >= 0


@pytest.mark.integration
@pytest.mark.p1
def test_agent_scoped_update_partial(app_server) -> None:
"""Test POST /api/agent-scoped with partial update."""
# Try to update with empty dict
response = app_server.api_request(
"POST",
"/api/agents/default/cron/jobs",
json={},
)
assert response.status_code == 422


@pytest.mark.integration
@pytest.mark.p1
def test_agent_scoped_get_specific(app_server) -> None:
"""Test GET /api/agent-scoped with specific key."""
response = app_server.api_request(
"GET",
"/api/agents/default/config/channels",
)
assert response.status_code in [200, 404]
111 changes: 111 additions & 0 deletions tests/integration/test_agent_stats_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
"""Integration tests for the agent-stats router.

Covers GET /api/agent-stats with various date range parameters.
"""

from __future__ import annotations

import pytest
from helpers import default_http_timeout

_STATS_TIMEOUT = default_http_timeout(15.0)


@pytest.mark.integration
@pytest.mark.p1
def test_agent_stats_summary_default_range(app_server) -> None:
"""Test purpose:
- Verify GET /api/agent-stats with no parameters returns a valid
summary for the default 30-day range. Console dashboard renders
this on load.

Test flow:
1. GET /api/agent-stats with no params.
2. Assert 200 and response is a dict.

API endpoints:
- GET /api/agent-stats
"""
resp = app_server.api_request(
"GET",
"/api/agent-stats",
timeout=_STATS_TIMEOUT,
)
assert resp.status_code == 200, app_server.logs_tail()
payload = resp.json()
assert isinstance(payload, dict)


@pytest.mark.integration
@pytest.mark.p1
def test_agent_stats_summary_with_date_range(app_server) -> None:
"""Test purpose:
- Verify date range parameters are accepted.

Test flow:
1. GET /api/agent-stats with start_date and end_date.
2. Assert 200.

API endpoints:
- GET /api/agent-stats
"""
resp = app_server.api_request(
"GET",
"/api/agent-stats",
params={
"start_date": "2026-01-01",
"end_date": "2026-12-31",
},
timeout=_STATS_TIMEOUT,
)
assert resp.status_code == 200, app_server.logs_tail()


@pytest.mark.integration
@pytest.mark.p1
def test_agent_stats_summary_reversed_dates_swapped(app_server) -> None:
"""Test purpose:
- Verify that reversed start/end dates are handled gracefully.

Test flow:
1. GET /api/agent-stats with start_date > end_date.
2. Assert 200.

API endpoints:
- GET /api/agent-stats
"""
resp = app_server.api_request(
"GET",
"/api/agent-stats",
params={
"start_date": "2026-12-31",
"end_date": "2026-01-01",
},
timeout=_STATS_TIMEOUT,
)
assert resp.status_code == 200, app_server.logs_tail()


@pytest.mark.integration
@pytest.mark.p1
def test_agent_stats_summary_invalid_date_format(app_server) -> None:
"""Test purpose:
- Verify invalid date format returns None (falls back to default).

Test flow:
1. GET /api/agent-stats with invalid date string.
2. Assert 200.

API endpoints:
- GET /api/agent-stats
"""
resp = app_server.api_request(
"GET",
"/api/agent-stats",
params={
"start_date": "not-a-date",
},
timeout=_STATS_TIMEOUT,
)
assert resp.status_code == 200, app_server.logs_tail()
Loading
Loading