debug-control-plane 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/PKG-INFO +4 -4
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/README.md +3 -3
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/__init__.py +1 -1
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/server.py +1 -1
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/PKG-INFO +4 -4
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/SOURCES.txt +1 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/pyproject.toml +1 -1
- debug_control_plane-0.5.2/tests/test_dart_plane_persistence.py +217 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_server.py +1 -1
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/LICENSE +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/__init__.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/device_candidates.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/device_pool.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/__init__.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/cross_identify.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/lan_scan.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/manual_registry.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/usb_identity.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/discovery/vpn_immune.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/endpoint.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/device_discovery/protocol.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/__init__.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/bridge_client.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/capability_mirror.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/semantic_provider.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/token_provider.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/dependency_links.txt +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/entry_points.txt +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/requires.txt +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/top_level.txt +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/setup.cfg +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_acceptance_flutter_app_auth.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_bridge_client.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_capability_mirror.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_cross_lang_kotlin_plane.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_device_candidates.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_device_pool.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_cross_identify.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_lan_scan.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_manual_registry.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_usb_identity.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_vpn_immune.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_e2e_mock.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_endpoint.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_import_sanity.py +0 -0
- {debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_token_provider.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debug-control-plane
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Multi-product reusable debug control plane (device discovery + MCP adapter).
|
|
5
5
|
Author: tangxiaolu
|
|
6
6
|
License: MIT
|
|
@@ -62,7 +62,7 @@ python/
|
|
|
62
62
|
├── README.md # this file
|
|
63
63
|
├── LICENSE # MIT
|
|
64
64
|
└── debug_control_plane/
|
|
65
|
-
├── __init__.py # __version__ = "0.5.
|
|
65
|
+
├── __init__.py # __version__ = "0.5.2"
|
|
66
66
|
├── device_discovery/ # USB/LAN device discovery + device pool
|
|
67
67
|
│ ├── device_candidates.py
|
|
68
68
|
│ ├── device_pool.py # identity-keyed pool, TTL expiry / 身份键池,TTL 过期
|
|
@@ -92,8 +92,8 @@ Points at `debug_control_plane.mcp_plane.server:main` — a bare server (no busi
|
|
|
92
92
|
|
|
93
93
|
## Version / 版本
|
|
94
94
|
|
|
95
|
-
`0.5.
|
|
96
|
-
`0.5.
|
|
95
|
+
`0.5.2` — aligned with Kotlin/Dart/Flutter `0.5.2`, API unstable.
|
|
96
|
+
`0.5.2` —— 与 Kotlin/Dart/Flutter `0.5.2` 对齐,API 不稳定。
|
|
97
97
|
|
|
98
98
|
## License / 许可证
|
|
99
99
|
|
|
@@ -43,7 +43,7 @@ python/
|
|
|
43
43
|
├── README.md # this file
|
|
44
44
|
├── LICENSE # MIT
|
|
45
45
|
└── debug_control_plane/
|
|
46
|
-
├── __init__.py # __version__ = "0.5.
|
|
46
|
+
├── __init__.py # __version__ = "0.5.2"
|
|
47
47
|
├── device_discovery/ # USB/LAN device discovery + device pool
|
|
48
48
|
│ ├── device_candidates.py
|
|
49
49
|
│ ├── device_pool.py # identity-keyed pool, TTL expiry / 身份键池,TTL 过期
|
|
@@ -73,8 +73,8 @@ Points at `debug_control_plane.mcp_plane.server:main` — a bare server (no busi
|
|
|
73
73
|
|
|
74
74
|
## Version / 版本
|
|
75
75
|
|
|
76
|
-
`0.5.
|
|
77
|
-
`0.5.
|
|
76
|
+
`0.5.2` — aligned with Kotlin/Dart/Flutter `0.5.2`, API unstable.
|
|
77
|
+
`0.5.2` —— 与 Kotlin/Dart/Flutter `0.5.2` 对齐,API 不稳定。
|
|
78
78
|
|
|
79
79
|
## License / 许可证
|
|
80
80
|
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/server.py
RENAMED
|
@@ -257,7 +257,7 @@ class McpServer:
|
|
|
257
257
|
pool: DevicePool,
|
|
258
258
|
*,
|
|
259
259
|
server_name: str = "mcp-debug-bridge",
|
|
260
|
-
server_version: str = "0.5.
|
|
260
|
+
server_version: str = "0.5.2",
|
|
261
261
|
providers: list[SemanticProvider] | None = None,
|
|
262
262
|
tool_handlers: dict[str, Any] | None = None,
|
|
263
263
|
) -> None:
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: debug-control-plane
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Multi-product reusable debug control plane (device discovery + MCP adapter).
|
|
5
5
|
Author: tangxiaolu
|
|
6
6
|
License: MIT
|
|
@@ -62,7 +62,7 @@ python/
|
|
|
62
62
|
├── README.md # this file
|
|
63
63
|
├── LICENSE # MIT
|
|
64
64
|
└── debug_control_plane/
|
|
65
|
-
├── __init__.py # __version__ = "0.5.
|
|
65
|
+
├── __init__.py # __version__ = "0.5.2"
|
|
66
66
|
├── device_discovery/ # USB/LAN device discovery + device pool
|
|
67
67
|
│ ├── device_candidates.py
|
|
68
68
|
│ ├── device_pool.py # identity-keyed pool, TTL expiry / 身份键池,TTL 过期
|
|
@@ -92,8 +92,8 @@ Points at `debug_control_plane.mcp_plane.server:main` — a bare server (no busi
|
|
|
92
92
|
|
|
93
93
|
## Version / 版本
|
|
94
94
|
|
|
95
|
-
`0.5.
|
|
96
|
-
`0.5.
|
|
95
|
+
`0.5.2` — aligned with Kotlin/Dart/Flutter `0.5.2`, API unstable.
|
|
96
|
+
`0.5.2` —— 与 Kotlin/Dart/Flutter `0.5.2` 对齐,API 不稳定。
|
|
97
97
|
|
|
98
98
|
## License / 许可证
|
|
99
99
|
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/SOURCES.txt
RENAMED
|
@@ -29,6 +29,7 @@ tests/test_acceptance_flutter_app_auth.py
|
|
|
29
29
|
tests/test_bridge_client.py
|
|
30
30
|
tests/test_capability_mirror.py
|
|
31
31
|
tests/test_cross_lang_kotlin_plane.py
|
|
32
|
+
tests/test_dart_plane_persistence.py
|
|
32
33
|
tests/test_device_candidates.py
|
|
33
34
|
tests/test_device_pool.py
|
|
34
35
|
tests/test_discovery_cross_identify.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "debug-control-plane"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "Multi-product reusable debug control plane (device discovery + MCP adapter)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""R005-BF002 — dart plane token 持久化验收入口(pytest 侧子集)。
|
|
2
|
+
|
|
3
|
+
消费「已在运行的 example app Dart plane endpoint」(R002-BF005 双通道:
|
|
4
|
+
pytest ``--endpoint`` option 或 env ``ACCEPTANCE_ENDPOINT``;由
|
|
5
|
+
``.dev-flow/R005/test-overrides/R005-BF002/run-integration.sh`` 驱动或
|
|
6
|
+
操作者手动启动)。两者皆缺 → 每用例 ``pytest.skip(setup_required)``,
|
|
7
|
+
绝不误报 pass。session 级健康探测 GET /hello 不可达 → 全部
|
|
8
|
+
skip(setup_required: endpoint unreachable)。
|
|
9
|
+
|
|
10
|
+
覆盖范围(明示):
|
|
11
|
+
- 本文件只覆盖 I1/I4-TTL/I5 的 wire 子集 + I1 的 python 侧落盘断言。
|
|
12
|
+
- I2(冷重启)/ I3(损坏自愈)/ I4 的「篡改 app 沙箱文件 + simctl
|
|
13
|
+
terminate/relaunch」部分**仅在驱动脚本执行**(需要 simctl udid/bundle
|
|
14
|
+
与 driver 重启能力,pytest 会话内不掌握这些环境句柄),对应断言在
|
|
15
|
+
``.dev-flow/R005/test-overrides/R005-BF002/ios-simulator-persistence.py``
|
|
16
|
+
(I1-I5 全量)。
|
|
17
|
+
- 全量执行入口:``bash .dev-flow/R005/test-overrides/R005-BF002/run-integration.sh``。
|
|
18
|
+
|
|
19
|
+
Refs:
|
|
20
|
+
- tasks: .dev-flow/R005/dart-plane-token-persistence-tasks.md (R005-BF002)
|
|
21
|
+
- pattern: python/tests/test_acceptance_flutter_app_auth.py (R002-BF005)
|
|
22
|
+
- wire source: flutter_debug_control_plane/example/lib/src/acceptance_plane.dart
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import stat
|
|
30
|
+
import tempfile
|
|
31
|
+
import time
|
|
32
|
+
import urllib.error
|
|
33
|
+
import urllib.request
|
|
34
|
+
import uuid
|
|
35
|
+
from datetime import datetime, timezone
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
import pytest
|
|
40
|
+
|
|
41
|
+
_ENDPOINT_ENV = "ACCEPTANCE_ENDPOINT"
|
|
42
|
+
_REQUEST_TIMEOUT = 5.0
|
|
43
|
+
_APPROVE_WAIT_TIMEOUT = 30.0
|
|
44
|
+
_POLL_INTERVAL = 0.5
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_pytest_endpoint(request: pytest.FixtureRequest) -> str | None:
|
|
48
|
+
"""--endpoint option 优先,其次 ACCEPTANCE_ENDPOINT,皆缺返回 None。"""
|
|
49
|
+
option_value = request.config.getoption("--endpoint")
|
|
50
|
+
return option_value or os.environ.get(_ENDPOINT_ENV) or None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _http_json(endpoint: str, method: str, path: str,
|
|
54
|
+
body: dict[str, Any] | None = None, bearer: str | None = None,
|
|
55
|
+
timeout: float = _REQUEST_TIMEOUT) -> tuple[int, Any]:
|
|
56
|
+
url = endpoint.rstrip("/") + path
|
|
57
|
+
data = json.dumps(body or {}).encode()
|
|
58
|
+
req = urllib.request.Request(url, data=data, method=method)
|
|
59
|
+
req.add_header("Content-Type", "application/json")
|
|
60
|
+
if bearer:
|
|
61
|
+
req.add_header("Authorization", f"Bearer {bearer}")
|
|
62
|
+
try:
|
|
63
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
64
|
+
raw = r.read()
|
|
65
|
+
return r.status, (json.loads(raw) if raw else {})
|
|
66
|
+
except urllib.error.HTTPError as e:
|
|
67
|
+
raw = e.read()
|
|
68
|
+
try:
|
|
69
|
+
return e.code, (json.loads(raw) if raw else {})
|
|
70
|
+
except ValueError:
|
|
71
|
+
return e.code, raw.decode(errors="replace")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _EndpointUnreachable(Exception):
|
|
75
|
+
def __init__(self, reason: str) -> None:
|
|
76
|
+
super().__init__(reason)
|
|
77
|
+
self.reason = reason
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.fixture(scope="session")
|
|
81
|
+
def plane_endpoint(request: pytest.FixtureRequest) -> str:
|
|
82
|
+
"""endpoint 解析 + session 级健康探测;缺/不可达 → skip(setup_required)。
|
|
83
|
+
|
|
84
|
+
与 test_acceptance_flutter_app_auth.py 同模式:session fixture 内直接
|
|
85
|
+
pytest.skip,依赖用例呈现 skip 而非 error。
|
|
86
|
+
"""
|
|
87
|
+
endpoint = _get_pytest_endpoint(request)
|
|
88
|
+
if not endpoint:
|
|
89
|
+
pytest.skip("setup_required: no endpoint (use --endpoint or ACCEPTANCE_ENDPOINT)")
|
|
90
|
+
try:
|
|
91
|
+
st, _ = _http_json(endpoint, "GET", "/hello", timeout=3.0)
|
|
92
|
+
if st != 200:
|
|
93
|
+
pytest.skip(f"setup_required: endpoint unreachable (/hello -> {st})")
|
|
94
|
+
except Exception as e: # noqa: BLE001
|
|
95
|
+
pytest.skip(f"setup_required: endpoint unreachable ({type(e).__name__}: {e})")
|
|
96
|
+
return endpoint
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture
|
|
100
|
+
def endpoint_or_skip(plane_endpoint: str) -> str:
|
|
101
|
+
"""session 探测通过的 endpoint(失败已在 session fixture skip)。"""
|
|
102
|
+
return plane_endpoint
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _wait_approved(endpoint: str, rid: str, nonce: str,
|
|
106
|
+
timeout: float = _APPROVE_WAIT_TIMEOUT) -> None:
|
|
107
|
+
"""轮询等 driver(按 clientLabel 非 deny*/hold* 自动 approve)。"""
|
|
108
|
+
deadline = time.monotonic() + timeout
|
|
109
|
+
while time.monotonic() < deadline:
|
|
110
|
+
st, body = _http_json(endpoint, "POST", "/auth/status",
|
|
111
|
+
{"requestId": rid, "clientNonce": nonce})
|
|
112
|
+
if st == 200 and isinstance(body, dict) and body.get("status") == "approved":
|
|
113
|
+
return
|
|
114
|
+
if st == 403:
|
|
115
|
+
raise AssertionError(f"denied: {body}")
|
|
116
|
+
time.sleep(_POLL_INTERVAL)
|
|
117
|
+
raise AssertionError(f"approve timeout for {rid}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _auth_chain(endpoint: str, store: Path,
|
|
121
|
+
label: str) -> tuple[str, dict[str, Any]]:
|
|
122
|
+
"""授权链(标准库 urllib 版,与 R002 模式一致)。"""
|
|
123
|
+
nonce = f"r005-py-{label}-{uuid.uuid4().hex[:8]}"
|
|
124
|
+
st, body = _http_json(endpoint, "POST", "/auth/request",
|
|
125
|
+
{"clientLabel": f"r005-py-{label}", "clientNonce": nonce})
|
|
126
|
+
assert st in (200, 202), f"/auth/request: {st} {body!r:.60}"
|
|
127
|
+
rid = body["requestId"]
|
|
128
|
+
_wait_approved(endpoint, rid, nonce)
|
|
129
|
+
st, body = _http_json(endpoint, "POST", "/auth/claim",
|
|
130
|
+
{"requestId": rid, "clientNonce": nonce})
|
|
131
|
+
assert st == 200 and isinstance(body.get("token"), str), \
|
|
132
|
+
f"/auth/claim: {st} {body!r:.60}"
|
|
133
|
+
# python 侧 FileTokenProvider 落盘(等价 R004 S1 语义)
|
|
134
|
+
row = {"token": body["token"], "tokenId": body.get("tokenId"),
|
|
135
|
+
"expiresAt": body.get("expiresAt"), "obtainedAt": datetime.now(timezone.utc).isoformat()}
|
|
136
|
+
store.parent.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
store.write_text(json.dumps({"version": 1, "tokens": {"ios-sim": row}},
|
|
138
|
+
indent=2), encoding="utf-8")
|
|
139
|
+
os.chmod(store, 0o600)
|
|
140
|
+
return body["token"], body
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.fixture
|
|
144
|
+
def claimed(endpoint_or_skip: str, tmp_path: Path) -> tuple[str, dict[str, Any]]:
|
|
145
|
+
"""session 内每用例独立授权链(新 nonce 新 token)。"""
|
|
146
|
+
return _auth_chain(endpoint_or_skip, tmp_path / "tokens.json", "case")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# 用例(I1/I4-TTL/I5 子集;I2/I3 仅驱动脚本执行,见模块 docstring)
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
def test_i1_first_auth_python_side(claimed: tuple[str, dict[str, Any]],
|
|
154
|
+
tmp_path: Path) -> None:
|
|
155
|
+
"""I1 子集(python 侧):claim 200 + token 落盘 0600 + expiresAt 在场。
|
|
156
|
+
|
|
157
|
+
I1 的 app 沙箱断言(Documents/debug_control_plane/debug_auth_tokens.json
|
|
158
|
+
存在/version==1/字节无明文)仅在驱动脚本执行(需 simctl 沙箱句柄)。
|
|
159
|
+
"""
|
|
160
|
+
token, claim = claimed
|
|
161
|
+
assert token, "claim token 空"
|
|
162
|
+
store = tmp_path / "tokens.json"
|
|
163
|
+
data = json.loads(store.read_text(encoding="utf-8"))
|
|
164
|
+
row = data["tokens"]["ios-sim"]
|
|
165
|
+
assert row["token"] == token, "落盘 token 与 claim 不一致"
|
|
166
|
+
mode = stat.S_IMODE(store.stat().st_mode)
|
|
167
|
+
assert mode == 0o600, f"权限 {oct(mode)} != 0600"
|
|
168
|
+
assert row["tokenId"] and row["expiresAt"], f"metadata 未并入: {row!r}"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_i4_ttl_window(claimed: tuple[str, dict[str, Any]]) -> None:
|
|
172
|
+
"""I4 子集(TTL 窗口):claim expiresAt - now ∈ [604790, 604810]s。
|
|
173
|
+
|
|
174
|
+
I4 的「篡改 app 沙箱 + 冷重启 → 401」部分仅在驱动脚本执行。
|
|
175
|
+
"""
|
|
176
|
+
token, claim = claimed
|
|
177
|
+
exp = datetime.fromisoformat(claim["expiresAt"].replace("Z", "+00:00"))
|
|
178
|
+
delta = (exp - datetime.now(timezone.utc)).total_seconds()
|
|
179
|
+
assert 604790 <= delta <= 604810, f"expiresAt-now={delta:.0f}s 超出窗口"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_i5_no_token_401(endpoint_or_skip: str) -> None:
|
|
183
|
+
"""I5:无 Authorization → 敏感路由 401。"""
|
|
184
|
+
st, _ = _http_json(endpoint_or_skip, "POST", "/debug/secure-action",
|
|
185
|
+
body={"via": "r005-py"})
|
|
186
|
+
assert st == 401, f"no-token: {st}"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_i5_forged_401_invalid_token(endpoint_or_skip: str) -> None:
|
|
190
|
+
"""I5:伪造 Bearer → 401 invalid_token。"""
|
|
191
|
+
st, body = _http_json(endpoint_or_skip, "POST", "/debug/secure-action",
|
|
192
|
+
body={"via": "r005-py"}, bearer="tok-forged-r005")
|
|
193
|
+
assert st == 401 and body.get("code") == "invalid_token", \
|
|
194
|
+
f"forged: {st} {body!r:.60}"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_i5_pending_claim_no_token_leak(endpoint_or_skip: str) -> None:
|
|
198
|
+
"""I5:hold* 前缀保持 pending → claim 200 status=pending 且无 token。"""
|
|
199
|
+
nonce = f"r005-py-hold-{uuid.uuid4().hex[:8]}"
|
|
200
|
+
st, body = _http_json(endpoint_or_skip, "POST", "/auth/request",
|
|
201
|
+
{"clientLabel": "hold-r005-py", "clientNonce": nonce})
|
|
202
|
+
rid = body.get("requestId")
|
|
203
|
+
assert st in (200, 202) and rid, f"/auth/request: {st} {body!r:.60}"
|
|
204
|
+
st, body = _http_json(endpoint_or_skip, "POST", "/auth/claim",
|
|
205
|
+
{"requestId": rid, "clientNonce": nonce})
|
|
206
|
+
assert st == 200 and body.get("status") == "pending" and "token" not in body, \
|
|
207
|
+
f"pending claim: {st} {body!r:.60}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_i5_bearer_multi_capability(endpoint_or_skip: str,
|
|
211
|
+
claimed: tuple[str, dict[str, Any]]) -> None:
|
|
212
|
+
"""I5:真 token Bearer 走 /debug/echo(capability 语义回归)。"""
|
|
213
|
+
token, _ = claimed
|
|
214
|
+
st, body = _http_json(endpoint_or_skip, "POST", "/debug/echo",
|
|
215
|
+
body={"r005": "multi"}, bearer=token)
|
|
216
|
+
assert st == 200 and body.get("capability") == "debug.echo", \
|
|
217
|
+
f"echo: {st} {body!r:.60}"
|
|
@@ -1313,7 +1313,7 @@ class TestMakeApp:
|
|
|
1313
1313
|
srv = assembled[0]
|
|
1314
1314
|
app = srv._make_app() # noqa: SLF001
|
|
1315
1315
|
assert app.name == "mcp-debug-bridge"
|
|
1316
|
-
assert app.version == "0.5.
|
|
1316
|
+
assert app.version == "0.5.2"
|
|
1317
1317
|
|
|
1318
1318
|
def test_make_app_registers_stub_provider(self, assembled):
|
|
1319
1319
|
"""★ BF008-010 (A 类): assembled fixture 注入的 _StubProvider 已注册到
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane/mcp_plane/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/requires.txt
RENAMED
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/debug_control_plane.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_acceptance_flutter_app_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_cross_lang_kotlin_plane.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_cross_identify.py
RENAMED
|
File without changes
|
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_manual_registry.py
RENAMED
|
File without changes
|
{debug_control_plane-0.5.0 → debug_control_plane-0.5.2}/tests/test_discovery_usb_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|