docs: 将仓库代码注释统一为中文
CI / docs-check (push) Canceled after 0s
CI / backend-test (push) Canceled after 0s
CI / service-test (push) Canceled after 0s
CI / frontend-test (push) Canceled after 0s
CI / rust-core (push) Canceled after 0s
CI / docs-check (pull_request) Canceled after 0s
CI / backend-test (pull_request) Canceled after 0s
CI / service-test (pull_request) Canceled after 0s
CI / frontend-test (pull_request) Canceled after 0s
CI / rust-core (pull_request) Canceled after 0s

This commit is contained in:
2026-09-10 00:40:56 +08:00
parent 51c592841d
commit d703ab64e3
249 changed files with 707 additions and 900 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ def _isolate_data_dir(tmp_path, monkeypatch):
monkeypatch.setenv("APP_VAULT_PATH", str(tmp_path / "vault"))
# 清除 lru 缓存,让本次测试内的 get_settings() 读到临时目录
get_settings.cache_clear()
# Unit tests explicitly inject deterministic embeddings. Production uses real models.
# 单元测试显式注入确定性嵌入。生产使用真实模型。
from app import container as container_module
from app.services import note_service
from app.retrieval.engine import engine
+3 -4
View File
@@ -152,7 +152,7 @@ def test_mcp_split_config_and_secret_requests_persist_without_plaintext(
"startup_timeout_seconds": 120,
"tool_timeout_seconds": 300,
}
# Reproduce the old frontend payload. The backend still enforces separation.
# 重现旧的前端有效负载。后端仍然强制分离。
invalid = client.post(
"/api/mcp/servers",
json={
@@ -179,7 +179,7 @@ def test_mcp_split_config_and_secret_requests_persist_without_plaintext(
assert "synthetic-only" not in service._path.read_text(encoding="utf-8")
_, credentials_path = service.credentials._paths()
assert "synthetic-only" not in credentials_path.read_text(encoding="utf-8")
assert not current.json()["enabled"] # Saving never starts a third-party process.
assert not current.json()["enabled"] # 保存永远不会启动第三方进程。
client.close()
@@ -223,8 +223,7 @@ def test_mcp_lifecycle_lock_contention_keeps_event_loop_responsive(
monkeypatch.setattr(service, operation, observed)
holder = threading.Thread(target=hold_lifecycle_lock, daemon=True)
holder.start()
# An independent watchdog lets the test fail rather than hang if a regression
# blocks the event loop itself (an asyncio timeout alone cannot catch that).
# 如果回归阻止事件循环本身,独立的看门狗会让测试失败而不是挂起(单独的异步超时无法捕获该情况)。
watchdog = threading.Timer(5, release.set)
watchdog.start()
+1 -1
View File
@@ -64,7 +64,7 @@ def test_regeneration_persists_context_per_answer_without_rewriting_original(mon
yield ModelEvent(event=ModelEventType.text_delta, sequence=0, data={'text':'answer'}, timestamp=utc_now())
yield ModelEvent(event=ModelEventType.done, sequence=1, data={}, timestamp=utc_now())
monkeypatch.setattr('app.routes.provider_or_404',lambda _:SimpleNamespace(adapter=Adapter()))
# Keep attachment parsing out of this persistence test; the route must save raw IDs.
# 将附件解析排除在此持久性测试之外;路由必须保存原始 ID
async def prepare(request, provider):
return request.model_copy(update={'attachments':[]})
monkeypatch.setattr('app.services.chat_attachments.prepare',prepare)
+1 -1
View File
@@ -1,4 +1,4 @@
"""Host-only adapter contracts use fake documents; process coverage lives in Rust."""
"""Host-only适配器约定使用虚假文档;流程覆盖位于 Rust 中。"""
from types import SimpleNamespace
import pytest
import asyncio
+3 -3
View File
@@ -89,7 +89,7 @@ def _create_and_wait(request: ExportRequest) -> object:
# --------------------------------------------------------------------------- #
# markdown → Document AST
# Markdown → 文档 AST
# --------------------------------------------------------------------------- #
def _types(nodes) -> list[str]:
return [n.type for n in nodes]
@@ -158,7 +158,7 @@ def test_parse_document_function_plot_dash_alias() -> None:
# --------------------------------------------------------------------------- #
# HtmlExporter
# HtmlExporter 导出器
# --------------------------------------------------------------------------- #
async def _render(markdown: str, *, title: str = "") -> str:
doc = parse_document(markdown)
@@ -232,7 +232,7 @@ def test_html_exporter_include_title_and_metadata() -> None:
# --------------------------------------------------------------------------- #
# ExportService
# 导出服务
# --------------------------------------------------------------------------- #
def _markdown_request(markdown: str, *, format: ExportFormat = ExportFormat.html) -> ExportRequest:
return ExportRequest(
+1 -1
View File
@@ -18,7 +18,7 @@ def zipped(files):
for name, value in files:
if isinstance(name, str) and '\\' in name:
entry = zipfile.ZipInfo()
entry.filename = name # Keep malicious separators on Windows too.
entry.filename = name # Windows 上也保留恶意分隔符。
name = entry
archive.writestr(name, value)
return output.getvalue()
+2 -2
View File
@@ -205,7 +205,7 @@ def test_old_failure_callback_cannot_stop_replacement_host(monkeypatch) -> None:
old_callback(f"mcp.{created.server_id}", "delayed old failure")
callback_finished.set()
# Queue the old callback while a replacement owns the lifecycle lock.
# 将旧回调排队,而替换者拥有生命周期锁。
with service._lifecycle_lock:
callback_thread = threading.Thread(target=delayed_failure, daemon=True)
callback_thread.start()
@@ -305,7 +305,7 @@ def test_ambiguous_legacy_credentials_are_not_assigned_to_two_variables() -> Non
assert current.last_test_succeeded is None
assert migrated.credentials.has(
legacy_id
) # Keep the original ciphertext recoverable.
) # 保持原始密文可恢复。
migrated.put_secret(created.server_id, "TOKEN", "upper")
migrated.put_secret(created.server_id, "token", "lower")
assert registry().get(created.server_id).secret_environment == {
+2 -2
View File
@@ -1,4 +1,4 @@
"""Durability, cancellation and optimistic editing without model downloads."""
"""无需模型下载的耐久性、取消和乐观编辑。"""
import asyncio
from contextlib import closing
@@ -57,7 +57,7 @@ def test_cancel_before_start_retry_and_restart_recovery():
assert next_job.job_id != job.job_id
await jobs._tasks[jobs.task_key(next_job.job_id)]
assert jobs.require_job(next_job.job_id).status == "completed"
# Simulate a persisted job left behind by a stopped process.
# 模拟已停止进程留下的持久作业。
cancelled.status = "running"
jobs.save(cancelled, "TranscriptionStarted")
jobs.recover_interrupted()
+4 -8
View File
@@ -1,8 +1,4 @@
"""Offline model-routing contracts, HTTP validation, media lifetimes and persistence.
All HTTP uses MockTransport (or the in-process API). Credentials, models and
attachments are fakes, and conftest redirects all storage to temporary paths.
"""
"""离线模型路由约定、HTTP 验证、介质生命周期和持久性。所有HTTP都使用MockTransport(或进程内API)。凭证、模型和附件都是假的,conftest 将所有存储重定向到临时路径。"""
from __future__ import annotations
@@ -31,7 +27,7 @@ def run(awaitable):
def response(data, status=200):
# Raw JSON intentionally permits NaN/Infinity to exercise hostile API output.
# 原始 JSON 特意允许 NaN/Infinity,用于测试恶意 API 输出。
return httpx.Response(status, content=json.dumps(data).encode(), headers={"content-type": "application/json"})
@@ -512,7 +508,7 @@ def test_config_references_require_existing_supported_providers(rig, capability,
@pytest.fixture
def api(monkeypatch, no_real_http, _isolate_data_dir):
# Import the production container only after temporary storage is configured.
# 配置临时存储后才导入生产容器。
from app import container as container_module, routes
from app.main import app
@@ -656,7 +652,7 @@ def test_api_speech_failure_reports_reason_in_503_and_transcription_job(api):
@pytest.mark.parametrize("capability", ["embedding", "speaker_matching"])
def test_out_of_float_range_json_number_is_invalid_remote_and_falls_back(rig, audio, capability):
"""JSON integers may be finite but too large to convert to a Python float."""
"""JSON 整数可能是有限的,但太大而无法转换为 Python 浮点数。"""
bind(rig, capability)
data = {"data": [{"index": 0, "embedding": [10 ** 400, 1]}]} if capability == "embedding" else {"score": 10 ** 400}
rig.http.handler = lambda request: response(data)
@@ -1,4 +1,4 @@
"""Finalization regressions: device recovery, durable facts and guarded writes."""
"""最终回归:设备恢复、持久事实和受保护的写入。"""
import asyncio
import json
import sys
+2 -2
View File
@@ -37,7 +37,7 @@ def test_logs_exclude_content_and_legacy_exception_messages():
record = logging.LogRecord('app.sample', logging.ERROR, __file__, 1,
'private note and secret %s', ('credentials',), None)
handler.emit(record)
handler.emit(record) # a logger propagated to another installed handler
handler.emit(record) # 记录器传播到另一个已安装的处理程序
store = get_store()
store.queue.join()
data = json.dumps(store.query())
@@ -85,7 +85,7 @@ def test_trace_writer_batches_off_loop_and_survives_cancel():
while not started.is_set():
await asyncio.sleep(.001)
pending.cancel()
writer.worker.cancel() # simultaneous application shutdown
writer.worker.cancel() # 同时应用程序关闭
await asyncio.sleep(.005)
assert not pending.done()
release.set()
+1 -1
View File
@@ -1,4 +1,4 @@
"""PDF theme and resource policy regressions; no real providers or user files."""
"""PDF主题和资源政策回归;没有真正的提供者或用户文件。"""
import asyncio
import base64
from io import BytesIO
+1 -1
View File
@@ -177,7 +177,7 @@ def test_agent_parameter_matching_is_independent_of_call_order(order):
run = NS(status=NS(value='completed'),tool_results=[],output='',citations=[],run_id='test',current_step=1,token_usage=0,error_code=None)
result = score(case, run, events, 1, 0)
assert result.success and result.accurate_calls == result.selected_calls == 2
# Two expectations cannot reuse one matching call.
# 两个期望不能重复使用一个匹配的调用。
result = score(case, run, events[:1], 1, 0)
assert not result.success and result.accurate_calls == 1
+4 -4
View File
@@ -107,7 +107,7 @@ def test_render_svg_contains_polyline_and_axes() -> None:
assert "<line" in svg # 坐标轴/网格
assert "<script" not in svg
assert rendered.width == 640
assert rendered.height == 504 # Includes the legend row.
assert rendered.height == 504 # 包括图例行。
def test_render_svg_multiple_functions() -> None:
@@ -319,7 +319,7 @@ def test_function_plot_static_renderer_renders_svg() -> None:
assert "<polyline" in result.content
assert result.mime_type == "image/svg+xml"
assert result.width == 640
assert result.height == 504 # Includes the legend row.
assert result.height == 504 # 包括图例行。
def test_function_plot_static_renderer_parse_exposes_node_count() -> None:
@@ -513,7 +513,7 @@ def test_visible_midpoint_does_not_bridge_a_pole():
for px, py in seg:
x = (px-_PLOT_X0)/(_PLOT_X1-_PLOT_X0)*2
y = 1-(py-_PLOT_Y0)/(_PLOT_Y1-_PLOT_Y0)*2
# On the visible branch, 1000*t + .001/t - 1.5 >= .5.
# 在可见分支上,1000*t + .001/t - 1.5 >= .5
assert x > .001
assert y >= .5-1e-8
assert y == pytest.approx(1000*(x-.0025)+.001/(x-.001),abs=.002)
@@ -544,7 +544,7 @@ def test_refinement_budget_is_shared_by_both_subtrees(monkeypatch):
monkeypatch.setattr(rendering, 'evaluate', oscillate)
samples = rendering._refine_crossing(None, (0,-2), (1,2), -1,1)
assert len(calls) == rendering._REFINE_MAX_EVALUATIONS
assert None in samples # Exhaustion leaves gaps, never unchecked chords.
assert None in samples # 疲惫会留下间隙,永远不会不受控制的和弦。
@pytest.mark.parametrize('factor,pole', [(0.0001,.001),(-0.0001,.001),(.001,.001),(.0001,.0025),(.0001,.00419)])
+3 -3
View File
@@ -1,4 +1,4 @@
"""Wire-level provider tests: no credentials, SDKs, clocks, or network services."""
"""线路级提供商测试:无凭据、SDK、时钟或网络服务。"""
import asyncio
import json
@@ -400,7 +400,7 @@ def test_incremental_delivery_cancellation_and_explicit_close(protocol, cancel):
seen.append(event)
if event.event == E.text_delta:
break
# The first token arrives while the response is still open and blocked.
# 第一个令牌到达,而响应仍处于打开状态并被阻止。
assert seen[-1].data["text"] == "你好"
assert not body.closed
if cancel:
@@ -472,7 +472,7 @@ def test_native_structured_format_mapping(protocol):
@pytest.mark.parametrize("protocol", NATIVE)
def test_invalid_tool_arguments_and_unclosed_tool(protocol):
frames = responses_tool_events() if protocol == "responses" else anthropic_tool_events()
# A syntactically valid terminal cannot rescue an unfinished tool block.
# 语法上有效的终端无法挽救未完成的工具块。
index = next(i for i, frame in enumerate(frames)
if frame["type"] in {"response.function_call_arguments.delta", "content_block_delta"}
and (frame.get("output_index") == 2 or frame.get("index") == 2))
+1 -1
View File
@@ -525,7 +525,7 @@ def test_patch_tags_semantics(vault) -> None:
)
assert note.tags == ["a"]
updated = asyncio.run(note_service.update_note(note.note_id, title="改名")) # tags=None
updated = asyncio.run(note_service.update_note(note.note_id, title="改名")) # 标签=None
assert updated.tags == ["a"] # 省略 tags 保留原标签
updated = asyncio.run(note_service.update_note(note.note_id, tags=["b"]))
+9 -9
View File
@@ -1,4 +1,4 @@
"""Phase E route integration: deterministic runtimes, isolated DBs, no network."""
"""E 阶段路由集成:确定性运行时间、隔离数据库、无网络。"""
from __future__ import annotations
@@ -24,7 +24,7 @@ from app.services import index_service, note_service
@dataclass
class FakeRuntime:
model_id: str = "space-a"
dimensions: int = 3 # Deliberately differs from sqlite-vec's fixed 128.
dimensions: int = 3 # 特意与 sqlite-vec 的固定 128 不同。
source: str = "api"
error: BaseException | None = None
calls: list[list[str]] = field(default_factory=list)
@@ -38,7 +38,7 @@ class FakeRuntime:
return self.result_override
vectors = []
for text in texts:
# The API associates "apple" with banana; hash retrieval picks apple.
# API 将“苹果”与香蕉联系起来;哈希检索选择了苹果。
first = text == "apple orchard"
if self.model_id == "space-b":
first = not first
@@ -78,7 +78,7 @@ def test_native_spaces_isolate_dimensions_and_reuse_without_json_scan(runtime, m
assert conn.execute('SELECT COUNT(DISTINCT dimensions) FROM routed_block_vectors').fetchone()[0] == 2
finally:
conn.close()
# A new connection uses the persistent native index, without reading vector JSON.
# 新连接使用持久性本机索引,不读取向量 JSON
def forbidden(*args, **kwargs):
raise AssertionError('query decoded stored JSON')
monkeypatch.setattr(space_index.json, 'loads', forbidden)
@@ -159,7 +159,7 @@ def test_concurrent_first_search_serializes_migration_and_warm_search_is_read_on
first, other = await asyncio.gather(*tasks)
assert first == other and len(first) == 2
assert len(calls) == 1
# Prepared indexes are reusable even with SQLite query_only enforced.
# 即使强制执行 SQLite query_only,准备好的索引也可以重用。
original_connect = routed_vectors.connect
def read_only():
connection = original_connect()
@@ -195,7 +195,7 @@ def test_save_waits_for_migration_even_when_search_is_cancelled(runtime, monkeyp
assert release.wait(5)
return original(*args)
monkeypatch.setattr(space_index, 'ensure', slow)
# Keep the subsequent vector job queued; test saving and its durable marker.
# 保持后续向量作业排队;测试保存及其耐用标记。
monkeypatch.setattr(index_service, 'schedule_workspace_rebuild', lambda: None)
query = asyncio.create_task(routed_vectors.search_remote('apple orchard', top_k=2, strict=True))
save = None
@@ -211,7 +211,7 @@ def test_save_waits_for_migration_even_when_search_is_cancelled(runtime, monkeyp
assert saved.markdown == 'Saved during migration'
assert (await note_service.get_note(apple.note_id)).markdown == saved.markdown
assert repository.get_index_meta()[f'note_vectors_pending:{apple.note_id}'] == '1'
# Query may observe the saved revision's pending index, but saving must succeed.
# 查询可以观察已保存修订的挂起索引,但保存必须成功。
result = (await asyncio.gather(query, return_exceptions=True))[0]
if cancel_search:
assert isinstance(result, asyncio.CancelledError)
@@ -338,7 +338,7 @@ def test_rebuild_failure_preserves_concurrent_configuration_and_all_indexes(runt
name="saved during rebuild", base_url="https://unused.invalid/v1")
container.providers.register(config, container.provider_factory.build(config))
task_service.update_task(task.task_id, {"title": "saved during rebuild"})
# Preparation keeps the old searchable index intact while API I/O is pending.
# 当 API I/O 待处理时,准备工作会保持旧的可搜索索引完好无损。
assert repository.stats()["notes"] == 2
if failure == "cancel":
rebuilding.cancel()
@@ -577,7 +577,7 @@ def test_fts_skips_routing_and_hybrid_uses_routed_vector_channel(runtime, monkey
runtime.calls.clear()
await engine.search(request(SearchMode.fts))
assert runtime.calls == []
# Empty lexical channel isolates the vector contribution to hybrid fusion.
# 空词汇通道隔离了向量对混合融合的贡献。
monkeypatch.setattr(repository, "fts_search", lambda *_: [])
class PreserveOrder:
+1 -1
View File
@@ -79,7 +79,7 @@ def test_interpreter_keeps_cpu_default_and_respects_explicit_override(monkeypatc
(components.ROOT / 'ready.json').write_text('{}')
monkeypatch.setattr(runtime, 'configuration', lambda: runtime.RuntimeConfig(device='cpu'))
assert runtime.interpreter() != python
# A queued attempt keeps its frozen device even after the saved setting changes.
# 即使保存的设置随后改变,已排队的尝试仍使用冻结的提供商配置。
assert runtime.interpreter(runtime.RuntimeConfig(device='cuda')) == python
assert runtime.interpreter(runtime.RuntimeConfig(device='cpu')) != python
monkeypatch.setenv('APP_MODEL_PYTHON', 'explicit-python.exe')
+1 -1
View File
@@ -55,7 +55,7 @@ def test_open_returns_before_vectors_and_deduplicates_background(monkeypatch):
await asyncio.wait_for(workspace_service.open_workspace(None), 1)
assert index_service._background_task is task
assert index_service.get_status().status == 'running'
# A mutation still completes while the model is waiting.
# 模型等待时,突变仍会完成。
await asyncio.wait_for(workspace_service.create_folder('/', 'new-folder'), 1)
assert repository.list_note_locations()[0].note_id == snapshot.items[0].note_id
release.set()