mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
test(architecture): record direct adapter imports
This commit is contained in:
@@ -74,8 +74,8 @@ G-ARCH 只有在以下条件全部满足后才可完成:
|
||||
|---|---|---|---|
|
||||
| S0-L1 可信基线恢复 | `DELIVERED` | 无 | `5df388719`:交付架构审计/路线图,修复 `ARCH-001` 两个 mypy 增量错误;远端 `0/0` |
|
||||
| S0-L2.1 Host Oper/UoW 规范 | `DELIVERED` | S0-L1 | `3bf94ffed`:宿主无 Session Oper 规范债务归零,远端 `0/0` |
|
||||
| S0-L2.2 完整宿主 SCC policy | `VERIFIED` | S0-L2.1 | 完整宿主 SCC 全部精确分类;Chain 临时债务和 TMDB containment 分离 |
|
||||
| S0-L2.3 Adapter 直连事实 | `PLANNED` | S0-L2.1 | 收集 Application/Chain 的原始 Adapter import,不因父包展开重复计数 |
|
||||
| S0-L2.2 完整宿主 SCC policy | `DELIVERED` | S0-L2.1 | `a884ab5c2`:完整宿主 SCC 精确 policy 生效,远端 `0/0` |
|
||||
| S0-L2.3 Adapter 直连事实 | `VERIFIED` | S0-L2.1 | 收集 Application/Chain 的原始 Adapter import,不因父包展开重复计数 |
|
||||
| S0-L2.4 Adapter zero-growth | `PLANNED` | S0-L2.3 | 当前直连均登记迁移 owner,新增/替换失败,删除后要求清理陈旧 policy |
|
||||
| S0-L2.5 Event consumer 识别 | `PLANNED` | S0-L2.1 | consumer 只识别可静态证明的 EventManager 注册,动态误报归零 |
|
||||
| S0-L2.6 事实源与 CI 投影 | `PLANNED` | S0-L2.2,S0-L2.4,S0-L2.5 | fixture/policy/overview 职责固定,CI 分开报告语义 policy 与快照一致性 |
|
||||
@@ -153,42 +153,46 @@ G-ARCH 只有在以下条件全部满足后才可完成:
|
||||
|
||||
## 4. 当前活动叶子
|
||||
|
||||
### S0-L2.2 完整宿主 SCC policy
|
||||
### S0-L2.3 Adapter 直连事实
|
||||
|
||||
**Status:** `VERIFIED`(本地验收完成,等待提交、推送和远端一致性确认)
|
||||
|
||||
**Outcome**
|
||||
|
||||
让完整宿主静态依赖图中的每个 SCC 都有精确、人工审查的分类;生成快照只记录事实,
|
||||
不能通过刷新 baseline 自动批准新环。
|
||||
用原始 AST import 记录 Application/Chain 到 Adapter 的稳定直连事实,不把父包初始化边、
|
||||
导入符号、行号、TYPE_CHECKING 或动态字符串导入混入治理面。
|
||||
|
||||
**Ownership**
|
||||
|
||||
- `tests/test_architecture_dependencies.py` 的统一图算法和完整 SCC 语义门禁。
|
||||
- `tests/fixtures/architecture/dependency-policy.json` 的精确人工 policy。
|
||||
- `docs/rules/05-architecture.md` 与 `docs/architecture-overview.md` 的事实/policy 边界。
|
||||
- `scripts/architecture/baseline.py` 的 direct Adapter import collector。
|
||||
- `tests/test_architecture_adapter_imports.py` 的 AST 语义和当前 28 条事实断言。
|
||||
- `tests/fixtures/architecture/dependency-baseline.json` 的 schema v2 生成字段。
|
||||
- 本路线图的叶子状态和交付记录。
|
||||
|
||||
**Excluded**
|
||||
|
||||
- 不在本叶消除 Chain SCC;由 `ARCH-107`/S2-L2 删除包根环和临时 policy。
|
||||
- 不拆分 TMDB 移植包,不把普通单向包外依赖误判为 SCC 成员扩张。
|
||||
- 不修改 `app/plugins/**`、运行时代码或独立插件仓。
|
||||
- 不批准当前直连、不写 owner、不建立豁免;这些属于紧随其后的 S0-L2.4。
|
||||
- 不修改现有完整依赖图、SCC、digest、`app/plugins/**` 或运行时代码。
|
||||
- 不把 `app.db.adapters`、SDK、插件副本或第三方 HTTP egress 混入本字段。
|
||||
|
||||
**Acceptance**
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest \
|
||||
tests/test_architecture_adapter_imports.py \
|
||||
tests/test_architecture_contract_baseline.py \
|
||||
tests/test_architecture_dependencies.py \
|
||||
tests/test_architecture_baseline_cli.py -q
|
||||
.venv/bin/python scripts/architecture/baseline.py --check-host --diagnostics
|
||||
.venv/bin/python scripts/architecture/ruff_ratchet.py
|
||||
.venv/bin/python scripts/architecture/mypy_ratchet.py
|
||||
.venv/bin/pylint tests/test_architecture_dependencies.py
|
||||
.venv/bin/pylint scripts/architecture/baseline.py \
|
||||
tests/test_architecture_adapter_imports.py \
|
||||
tests/test_architecture_contract_baseline.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
**Delivery**
|
||||
|
||||
- 单一提交主题:建立完整宿主 SCC policy 与 zero-growth 语义门禁。
|
||||
- 单一提交主题:生成并锁定 Adapter 原始直连事实。
|
||||
- 推送 `origin/v3` 后确认提交祖先关系、远端 SHA 和 ahead/behind `0/0`。
|
||||
|
||||
@@ -252,6 +252,62 @@ def resolve_imports(
|
||||
return dependencies
|
||||
|
||||
|
||||
def _is_module_or_child(module_name: str, root: str) -> bool:
|
||||
"""判断模块是否等于指定根或位于其点分子树内。"""
|
||||
return module_name == root or module_name.startswith(f"{root}.")
|
||||
|
||||
|
||||
def _resolve_import_from_module(
|
||||
module_name: str,
|
||||
path: Path,
|
||||
node: ast.ImportFrom,
|
||||
) -> str:
|
||||
"""把 from-import 的相对模块解析为绝对模块名,非法越顶时返回空串。"""
|
||||
if not node.level:
|
||||
return node.module or ""
|
||||
package = module_name if path.name == "__init__.py" else module_name.rpartition(".")[0]
|
||||
package_parts = package.split(".") if package else []
|
||||
keep_count = len(package_parts) - node.level + 1
|
||||
if keep_count < 0:
|
||||
return ""
|
||||
base = ".".join(package_parts[:keep_count])
|
||||
return ".".join(part for part in (base, node.module or "") if part)
|
||||
|
||||
|
||||
def collect_direct_adapter_imports(
|
||||
modules: dict[str, Path],
|
||||
) -> list[dict[str, str]]:
|
||||
"""收集 Application/Chain 对 Adapter 的原始运行期 import,不展开父包。"""
|
||||
source_roots = ("app.application", "app.chain")
|
||||
target_root = "app.adapters"
|
||||
edges: set[tuple[str, str]] = set()
|
||||
for source, path in modules.items():
|
||||
if not any(_is_module_or_child(source, root) for root in source_roots):
|
||||
continue
|
||||
for node in iter_runtime_import_nodes(parse_source(path)):
|
||||
targets: list[str] = []
|
||||
if isinstance(node, ast.Import):
|
||||
targets.extend(alias.name for alias in node.names)
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
imported_module = _resolve_import_from_module(source, path, node)
|
||||
if imported_module:
|
||||
targets.append(imported_module)
|
||||
targets.extend(
|
||||
f"{imported_module}.{alias.name}"
|
||||
for alias in node.names
|
||||
if imported_module == "app" and alias.name != "*"
|
||||
)
|
||||
edges.update(
|
||||
(source, target)
|
||||
for target in targets
|
||||
if _is_module_or_child(target, target_root)
|
||||
)
|
||||
return [
|
||||
{"source": source, "target": target}
|
||||
for source, target in sorted(edges)
|
||||
]
|
||||
|
||||
|
||||
def strongly_connected_components(
|
||||
graph: dict[str, set[str]],
|
||||
) -> list[list[str]]:
|
||||
@@ -376,8 +432,15 @@ def collect_dependency_baseline() -> dict[str, Any]:
|
||||
for target in dependencies
|
||||
)
|
||||
digest = hashlib.sha256("\n".join(edges).encode("utf-8")).hexdigest()
|
||||
direct_adapter_imports = collect_direct_adapter_imports(modules)
|
||||
direct_adapter_sources = sorted(
|
||||
{edge["source"] for edge in direct_adapter_imports}
|
||||
)
|
||||
direct_adapter_targets = sorted(
|
||||
{edge["target"] for edge in direct_adapter_imports}
|
||||
)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"schema_version": 2,
|
||||
"scope": "MoviePilot host app excluding app/plugins",
|
||||
"module_count": len(modules),
|
||||
"edge_count": len(edges),
|
||||
@@ -385,6 +448,31 @@ def collect_dependency_baseline() -> dict[str, Any]:
|
||||
"modules": sorted(modules),
|
||||
"edges": edges,
|
||||
"strongly_connected_components": strongly_connected_components(graph),
|
||||
"direct_adapter_imports": {
|
||||
"scope": {
|
||||
"source_roots": ["app.application", "app.chain"],
|
||||
"target_root": "app.adapters",
|
||||
"runtime_only": True,
|
||||
"parent_package_expansion": False,
|
||||
"imported_symbols": False,
|
||||
},
|
||||
"count": len(direct_adapter_imports),
|
||||
"counts_by_source_root": {
|
||||
"app.application": sum(
|
||||
_is_module_or_child(edge["source"], "app.application")
|
||||
for edge in direct_adapter_imports
|
||||
),
|
||||
"app.chain": sum(
|
||||
_is_module_or_child(edge["source"], "app.chain")
|
||||
for edge in direct_adapter_imports
|
||||
),
|
||||
},
|
||||
"source_count": len(direct_adapter_sources),
|
||||
"sources": direct_adapter_sources,
|
||||
"target_count": len(direct_adapter_targets),
|
||||
"targets": direct_adapter_targets,
|
||||
"edges": direct_adapter_imports,
|
||||
},
|
||||
"boundary_edges": collect_boundary_edges(graph, modules),
|
||||
}
|
||||
|
||||
|
||||
+167
-1
@@ -13,6 +13,172 @@
|
||||
"runtime_to_db": [],
|
||||
"workflow_to_db": []
|
||||
},
|
||||
"direct_adapter_imports": {
|
||||
"count": 28,
|
||||
"counts_by_source_root": {
|
||||
"app.application": 15,
|
||||
"app.chain": 13
|
||||
},
|
||||
"edges": [
|
||||
{
|
||||
"source": "app.application.backup",
|
||||
"target": "app.adapters.system.backup.files"
|
||||
},
|
||||
{
|
||||
"source": "app.application.directory",
|
||||
"target": "app.adapters.system.host"
|
||||
},
|
||||
{
|
||||
"source": "app.application.image",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.image",
|
||||
"target": "app.adapters.network.ip"
|
||||
},
|
||||
{
|
||||
"source": "app.application.messaging.ingress",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.rss",
|
||||
"target": "app.adapters.network.browser"
|
||||
},
|
||||
{
|
||||
"source": "app.application.rss",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.rss",
|
||||
"target": "app.adapters.system"
|
||||
},
|
||||
{
|
||||
"source": "app.application.rules",
|
||||
"target": "app.adapters.system"
|
||||
},
|
||||
{
|
||||
"source": "app.application.security.cookie",
|
||||
"target": "app.adapters.external.ocr"
|
||||
},
|
||||
{
|
||||
"source": "app.application.security.cookie",
|
||||
"target": "app.adapters.network.browser"
|
||||
},
|
||||
{
|
||||
"source": "app.application.security.cookie",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.security.passkey",
|
||||
"target": "app.adapters.cache.redis"
|
||||
},
|
||||
{
|
||||
"source": "app.application.torrent",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.application.transfer",
|
||||
"target": "app.adapters.system.host"
|
||||
},
|
||||
{
|
||||
"source": "app.chain._recognition",
|
||||
"target": "app.adapters.external.server"
|
||||
},
|
||||
{
|
||||
"source": "app.chain._transfer",
|
||||
"target": "app.adapters.system.host"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.download",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.download",
|
||||
"target": "app.adapters.system.host"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.message",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.scraping",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.site",
|
||||
"target": "app.adapters.external.cookiecloud"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.site",
|
||||
"target": "app.adapters.network.browser"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.site",
|
||||
"target": "app.adapters.network.cloudflare"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.site",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.subscribe",
|
||||
"target": "app.adapters.external.server"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.system",
|
||||
"target": "app.adapters.network.http"
|
||||
},
|
||||
{
|
||||
"source": "app.chain.system",
|
||||
"target": "app.adapters.system.host"
|
||||
}
|
||||
],
|
||||
"scope": {
|
||||
"imported_symbols": false,
|
||||
"parent_package_expansion": false,
|
||||
"runtime_only": true,
|
||||
"source_roots": [
|
||||
"app.application",
|
||||
"app.chain"
|
||||
],
|
||||
"target_root": "app.adapters"
|
||||
},
|
||||
"source_count": 18,
|
||||
"sources": [
|
||||
"app.application.backup",
|
||||
"app.application.directory",
|
||||
"app.application.image",
|
||||
"app.application.messaging.ingress",
|
||||
"app.application.rss",
|
||||
"app.application.rules",
|
||||
"app.application.security.cookie",
|
||||
"app.application.security.passkey",
|
||||
"app.application.torrent",
|
||||
"app.application.transfer",
|
||||
"app.chain._recognition",
|
||||
"app.chain._transfer",
|
||||
"app.chain.download",
|
||||
"app.chain.message",
|
||||
"app.chain.scraping",
|
||||
"app.chain.site",
|
||||
"app.chain.subscribe",
|
||||
"app.chain.system"
|
||||
],
|
||||
"target_count": 11,
|
||||
"targets": [
|
||||
"app.adapters.cache.redis",
|
||||
"app.adapters.external.cookiecloud",
|
||||
"app.adapters.external.ocr",
|
||||
"app.adapters.external.server",
|
||||
"app.adapters.network.browser",
|
||||
"app.adapters.network.cloudflare",
|
||||
"app.adapters.network.http",
|
||||
"app.adapters.network.ip",
|
||||
"app.adapters.system",
|
||||
"app.adapters.system.backup.files",
|
||||
"app.adapters.system.host"
|
||||
]
|
||||
},
|
||||
"edge_count": 6810,
|
||||
"edge_sha256": "141ed79f9097aaed4b1933f2fe931b7364e7a1a88d63ea8ca79a6012255f8d9c",
|
||||
"edges": [
|
||||
@@ -7665,7 +7831,7 @@
|
||||
"app.workflow.actions.send_message",
|
||||
"app.workflow.actions.transfer_file"
|
||||
],
|
||||
"schema_version": 1,
|
||||
"schema_version": 2,
|
||||
"scope": "MoviePilot host app excluding app/plugins",
|
||||
"strongly_connected_components": [
|
||||
[
|
||||
|
||||
@@ -1266,9 +1266,6 @@
|
||||
"tests/test_api_response.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_architecture_contract_baseline.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_async_db_pooling.py": {
|
||||
"I001": 1
|
||||
},
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
"""Application/Chain 到 Adapter 原始直连事实的收集契约。"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.architecture.baseline import (
|
||||
collect_dependency_baseline,
|
||||
collect_direct_adapter_imports,
|
||||
)
|
||||
|
||||
EXPECTED_DIRECT_ADAPTER_IMPORTS = {
|
||||
("app.application.backup", "app.adapters.system.backup.files"),
|
||||
("app.application.directory", "app.adapters.system.host"),
|
||||
("app.application.image", "app.adapters.network.http"),
|
||||
("app.application.image", "app.adapters.network.ip"),
|
||||
("app.application.messaging.ingress", "app.adapters.network.http"),
|
||||
("app.application.rss", "app.adapters.network.browser"),
|
||||
("app.application.rss", "app.adapters.network.http"),
|
||||
("app.application.rss", "app.adapters.system"),
|
||||
("app.application.rules", "app.adapters.system"),
|
||||
("app.application.security.cookie", "app.adapters.external.ocr"),
|
||||
("app.application.security.cookie", "app.adapters.network.browser"),
|
||||
("app.application.security.cookie", "app.adapters.network.http"),
|
||||
("app.application.security.passkey", "app.adapters.cache.redis"),
|
||||
("app.application.torrent", "app.adapters.network.http"),
|
||||
("app.application.transfer", "app.adapters.system.host"),
|
||||
("app.chain._recognition", "app.adapters.external.server"),
|
||||
("app.chain._transfer", "app.adapters.system.host"),
|
||||
("app.chain.download", "app.adapters.network.http"),
|
||||
("app.chain.download", "app.adapters.system.host"),
|
||||
("app.chain.message", "app.adapters.network.http"),
|
||||
("app.chain.scraping", "app.adapters.network.http"),
|
||||
("app.chain.site", "app.adapters.external.cookiecloud"),
|
||||
("app.chain.site", "app.adapters.network.browser"),
|
||||
("app.chain.site", "app.adapters.network.cloudflare"),
|
||||
("app.chain.site", "app.adapters.network.http"),
|
||||
("app.chain.subscribe", "app.adapters.external.server"),
|
||||
("app.chain.system", "app.adapters.network.http"),
|
||||
("app.chain.system", "app.adapters.system.host"),
|
||||
}
|
||||
|
||||
|
||||
def _source(tmp_path: Path, name: str, content: str) -> tuple[str, Path]:
|
||||
"""创建一个供 AST collector 使用的独立源码文件。"""
|
||||
path = tmp_path / f"{name.replace('.', '_')}.py"
|
||||
path.write_text(content, encoding="utf-8")
|
||||
return name, path
|
||||
|
||||
|
||||
def test_direct_adapter_collector_preserves_raw_runtime_imports(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""原始模块只去重,不展开父包、猜测导入符号或纳入类型期依赖。"""
|
||||
modules = dict(
|
||||
[
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.application.sample",
|
||||
"""
|
||||
from typing import TYPE_CHECKING
|
||||
import typing
|
||||
import app.adapters.network.http as http
|
||||
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
|
||||
from app.adapters.system import rust as rust_accel
|
||||
if TYPE_CHECKING:
|
||||
import app.adapters.network.browser
|
||||
if typing.TYPE_CHECKING:
|
||||
import app.adapters.network.cloudflare
|
||||
def load():
|
||||
from app.adapters.cache import redis
|
||||
try:
|
||||
from app.adapters.external import ocr
|
||||
except ImportError:
|
||||
pass
|
||||
from app import adapters
|
||||
from app.adapters.network import *
|
||||
__import__("app.adapters.network.ip")
|
||||
""",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.chain.sample",
|
||||
"import app.adapters.system.backup.files\n",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
assert collect_direct_adapter_imports(modules) == [
|
||||
{"source": "app.application.sample", "target": "app.adapters"},
|
||||
{"source": "app.application.sample", "target": "app.adapters.cache"},
|
||||
{"source": "app.application.sample", "target": "app.adapters.external"},
|
||||
{"source": "app.application.sample", "target": "app.adapters.network"},
|
||||
{"source": "app.application.sample", "target": "app.adapters.network.http"},
|
||||
{"source": "app.application.sample", "target": "app.adapters.system"},
|
||||
{"source": "app.chain.sample", "target": "app.adapters.system.backup.files"},
|
||||
]
|
||||
|
||||
|
||||
def test_direct_adapter_collector_handles_relative_imports_and_scope(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""相对导入按 package 解析,伪前缀、DB Adapter 和非目标 source 必须排除。"""
|
||||
modules = dict(
|
||||
[
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.application.feature.worker",
|
||||
"from ...adapters.network import ip\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.application.too_high",
|
||||
"from ....adapters.network import http\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.application.db",
|
||||
"import app.db.adapters.workflow\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.application.fake",
|
||||
"import app.adaptersx.network\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.applicationx.fake",
|
||||
"import app.adapters.network.http\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.api.fake",
|
||||
"import app.adapters.network.http\n",
|
||||
),
|
||||
_source(
|
||||
tmp_path,
|
||||
"app.plugins.fake",
|
||||
"import app.adapters.network.http\n",
|
||||
),
|
||||
]
|
||||
)
|
||||
package_path = tmp_path / "__init__.py"
|
||||
package_path.write_text(
|
||||
"from ...adapters.cache import redis\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
modules["app.chain.feature"] = package_path
|
||||
|
||||
assert collect_direct_adapter_imports(modules) == [
|
||||
{
|
||||
"source": "app.application.feature.worker",
|
||||
"target": "app.adapters.network",
|
||||
},
|
||||
{"source": "app.chain.feature", "target": "app.adapters.cache"},
|
||||
]
|
||||
|
||||
|
||||
def test_current_direct_adapter_imports_are_stable_generated_facts() -> None:
|
||||
"""当前 28 条直连必须完整进入生成事实,且不保存符号或行号。"""
|
||||
contract = collect_dependency_baseline()["direct_adapter_imports"]
|
||||
edges = {
|
||||
(edge["source"], edge["target"])
|
||||
for edge in contract["edges"]
|
||||
}
|
||||
|
||||
assert contract["count"] == 28
|
||||
assert contract["counts_by_source_root"] == {
|
||||
"app.application": 15,
|
||||
"app.chain": 13,
|
||||
}
|
||||
assert contract["source_count"] == 18
|
||||
assert contract["target_count"] == 11
|
||||
assert edges == EXPECTED_DIRECT_ADAPTER_IMPORTS
|
||||
assert all(set(edge) == {"source", "target"} for edge in contract["edges"])
|
||||
@@ -7,7 +7,6 @@ from pathlib import Path
|
||||
|
||||
from app.schemas.types import ChainEventType, EventType
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parents[1]
|
||||
BASELINE_ROOT = PROJECT_ROOT / "tests" / "fixtures" / "architecture"
|
||||
|
||||
@@ -65,11 +64,19 @@ def test_dependency_baseline_records_nonempty_host_graph() -> None:
|
||||
baseline_path = BASELINE_ROOT / "dependency-baseline.json"
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
|
||||
assert baseline["schema_version"] == 1
|
||||
assert baseline["schema_version"] == 2
|
||||
assert baseline["module_count"] == len(baseline["modules"])
|
||||
assert baseline["edge_count"] == len(baseline["edges"])
|
||||
assert baseline["module_count"] > 0
|
||||
assert baseline["edge_count"] > 0
|
||||
direct_imports = baseline["direct_adapter_imports"]
|
||||
assert direct_imports["count"] == len(direct_imports["edges"]) == 28
|
||||
assert direct_imports["counts_by_source_root"] == {
|
||||
"app.application": 15,
|
||||
"app.chain": 13,
|
||||
}
|
||||
assert direct_imports["source_count"] == len(direct_imports["sources"]) == 18
|
||||
assert direct_imports["target_count"] == len(direct_imports["targets"]) == 11
|
||||
|
||||
|
||||
def test_official_discovery_plugins_explicitly_keep_host_page_envelope():
|
||||
|
||||
Reference in New Issue
Block a user