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
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:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 == {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
"""PDF theme and resource policy regressions; no real providers or user files."""
|
||||
"""PDF主题和资源政策回归;没有真正的提供者或用户文件。"""
|
||||
import asyncio
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"]))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user