x
This commit is contained in:
@@ -34,13 +34,13 @@
|
||||
|
||||
```
|
||||
src/
|
||||
entrypoints/ # 独立部署入口(均为 create_app 组装,含 sys.path 修正)
|
||||
entrypoints/ # 独立部署入口(纯组装:sys.path 修正 + create_app + startup_hooks/register_routers)
|
||||
moldinsight.py # moldinsight-only 入口:/api 前缀挂 moldinsight router,端口 8000
|
||||
inventory.py # inventory-only 入口:inventory_router,端口 8001
|
||||
unified.py # 双模块统一入口:/api 挂 moldinsight + inventory,当前推荐后端
|
||||
moldinsight/ # 【模具分析模块】
|
||||
api/
|
||||
__init__.py # router 聚合:ROUTE_MODULES 清单 + _safe_include 挂载,失败登记 route_registry(/api/health 呈现 degraded,DEBUG 下 fail fast);debug_router 仅 settings.DEBUG 挂载
|
||||
__init__.py # router 聚合:ROUTE_MODULES 清单 + _safe_include 挂载,失败登记 route_registry(/api/health 呈现 degraded,DEBUG 下 fail fast);register_moldinsight_routers 入口单点调用(/api 聚合 + HTML 报告根路径挂载);debug_router 仅 settings.DEBUG 挂载
|
||||
route_registry.py # 路由装载注册表(loaded / failed / disabled,health_router 引用)
|
||||
health_router.py # /api/health 模块健康检查(含真实 pythonocc 探测与路由装载状态)
|
||||
upload_router.py # /api/upload STEP/STP 上传
|
||||
@@ -93,7 +93,7 @@ src/
|
||||
models/ # moldinsight 域 ORM(stp_analysis.py:stp_files 及各阶段产物 + processing_tasks,共 9 表)
|
||||
storage/
|
||||
rustfs_storage.py # RustFS/MinIO 客户端封装
|
||||
init_storage.py # 存储初始化
|
||||
init_storage.py # RustFS 连接初始化 + rustfs_startup_hook(入口经 startup_hooks 注入)
|
||||
inventory/ # 【进销存模块】
|
||||
api/ # 每域一个 routes 文件:product / supplier / customer / warehouse / inventory / stock_movement / purchase_order / sales_order / purchase_demand / finance / dashboard / material
|
||||
schemas/ # 每域一个 Pydantic schema 文件(与 api 一一对应)
|
||||
@@ -101,7 +101,7 @@ src/
|
||||
models/ # inventory 域 ORM(catalog / warehouse / trading / finance 四文件,共 15 表)
|
||||
utils.py
|
||||
shared/ # 【共享平台层:只放真正跨模块复用的基础能力,勿堆业务】
|
||||
app_factory.py # create_app:request_id 日志中间件 / auth_router / /health / SPA fallback / connect_rustfs 开关(D11 后 /html 由 moldinsight 代理路由提供,不再挂本地 StaticFiles)
|
||||
app_factory.py # create_app:纯平台引导(CORS / 请求日志 / /health / SPA fallback / db+Redis 启动);模块专属接线经 startup_hooks 注入(D3 收敛,原 connect_rustfs 已移除)
|
||||
config/settings.py # Settings 单例:dotenv + os.getenv;DB_*/SECRET_KEY 惰性校验无默认
|
||||
database/database.py # async engine / session / get_db_session
|
||||
database/init_db.py # 建表与管理员种子
|
||||
|
||||
+10
-4
@@ -75,8 +75,8 @@ geMoldInsight 的目标架构不是微服务,也不是继续维持历史单体
|
||||
- 应用工厂与共用中间件
|
||||
|
||||
说明:
|
||||
- `shared` 当前仍是“共享平台层 + 历史耦合区”的混合体
|
||||
- 后续会继续向更清晰的 platform 语义收敛,但当前仓库结构仍以 `shared` 为事实名称
|
||||
- `shared` 的定位已明确为**平台层(跨模块基础能力)**;模块专属接线(RustFS 启动 / HTML 报告挂载 / 路由聚合)已收敛回模块层(见 §6.2),当前仓库结构仍以 `shared` 为事实名称
|
||||
- 剩余语义收敛(identity 平台表 vs 模块表的命名与注释口径)随实际重构继续推进
|
||||
|
||||
---
|
||||
|
||||
@@ -191,9 +191,15 @@ geMoldInsight/
|
||||
|
||||
跨模块只允许裸 FK(规则见 §5.1);全量模型注册点收敛为 `migrations/env.py` 与 `tests/conftest.py`;归属边界由 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定(含单模块独立 mapper 配置与旧模块无 facade 断言)。
|
||||
|
||||
### 6.2 app factory 组合职责偏重
|
||||
### 6.2 app factory 组合职责 —— 已收敛(2026-09-18,D3 剩余)
|
||||
|
||||
当前 [src/shared/app_factory.py](../src/shared/app_factory.py) 仍承担较多平台与模块组合职责,是后续平台层收敛的重点。
|
||||
平台层与模块专属接线的边界已明确(`shared` = 跨模块基础能力,模块专属接线归属模块层):
|
||||
|
||||
- **平台工厂 [src/shared/app_factory.py](../src/shared/app_factory.py) 只做纯平台引导**:CORS / 请求日志 / 目录准备 / 静态托管 / 数据库与 Redis 启动 / auth 路由 / /health / SPA fallback。`startup_hooks` 参数承载模块专属启动接线——原 `connect_rustfs` 参数(平台工厂持有 moldinsight 依赖)已移除。
|
||||
- **moldinsight 专属接线收敛回 moldinsight 层**:
|
||||
- RustFS 启动 → [init_storage.py](../src/moldinsight/storage/init_storage.py) 的 `rustfs_startup_hook`(moldinsight/unified 入口经 `startup_hooks` 注入)
|
||||
- /api 路由聚合 + HTML 报告根路径挂载 → [moldinsight/api/__init__.py](../src/moldinsight/api/__init__.py) 的 `register_moldinsight_routers`(入口只做单点调用,不再重复 include html_report)
|
||||
- **入口 [src/entrypoints/](../src/entrypoints/) 退化为纯组装**:sys.path 修正 + 调 create_app + 传 startup_hooks / register_routers。
|
||||
|
||||
### 6.3 文档与结构尚未完全同步
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
> 文档定位:**唯一的「现在到哪了」**。README / AGENTS / 各主文档只链接到这里,不复制状态内容。
|
||||
> 维护规则:每完整完成一个需求,**倒序在本文顶部加一条**(日期 + 主题 + 关键事实);其余主文档(架构 / 规划 / 技术债 / 部署)维护各自的"当前有效说法",本文只记录"什么时候做到了哪一步"。维护规则出处见根目录 [AGENTS.md](../AGENTS.md)。
|
||||
|
||||
> 2026-09-18(**D3 剩余收敛:app_factory 平台层去模块化 + 路由单点聚合**:① 平台工厂 [app_factory.py](../src/shared/app_factory.py) 只做纯平台引导——`connect_rustfs` 参数移除,模块专属启动接线改经 `startup_hooks` 注入;RustFS 连接收敛为 [init_storage.py](../src/moldinsight/storage/init_storage.py) 的 `rustfs_startup_hook`(moldinsight/unified 入口注入)。② 路由挂载收敛——[moldinsight/api/__init__.py](../src/moldinsight/api/__init__.py) 新增 `register_moldinsight_routers` 单点聚合(/api 聚合路由 + HTML 报告根路径挂载),moldinsight/unified 入口不再各自重复 include html_report,退化为纯组装。③ 边界语义入文档:ARCHITECTURE §6.2/§2 说明更新(shared = 平台层,模块专属接线归属模块层);TECH_DEBT D3 剩余仅 identity/platform 注释口径。**接口面零变化**(openapi 无漂移已验)。**测试基线**:**144 passed, 0 skipped**(新增 [test_html_report_router.py](../tests/test_html_report_router.py) 单点聚合注册 1 项)。)
|
||||
|
||||
> 2026-09-18(**批次 4 后续专项五项完成:D11 清偿 + 部署参数 + D2 诚实标注 + CI 门禁 + OCC 方案 B 实施**:① **D11 清偿**(TECH_DEBT P2)——可视化报告 RustFS 单源化:写侧 HTMLGenerator 每任务写临时目录,`.html`/`_summary.json`/`_data.json` 三件统一裸传 RustFS 报告键 `html/reports/{filename}`(文件名寻址),`/html` StaticFiles 本地挂载删除,新增 [html_report_router.py](../src/moldinsight/api/html_report_router.py) 根路径代理(报告键直取 → 遗留 `html/{hash}.json` JSON 包装解析 → 本地卷存量兜底 → 404;URL 形状 `/html/{filename}` 不变,持久化 cavity JSON 与前端 iframe 引用零迁移);celery 服务摘除 `html_data` 卷,Dockerfile.moldinsight 删除 `COPY html_output/`(构建机陈旧报告不再进镜像);已知约束:报告路由不做认证(iframe 无法携带 Authorization 头,沿用 StaticFiles 时代既定姿态,文档已声明);顺带删除 `get_stp_file_with_data` 的死数据块(`html_content` 组装零消费方,此前每次完成任务查询白下载数 MB 正文)。② **OCC 方案 A 参数落地**——`CELERY_CONCURRENCY`/`CELERY_MAX_TASKS_PER_CHILD` 进 [Dockerfile.celery](../deploy/Dockerfile.celery) ENV + compose 透传 + `.env.example`。③ **D2 清偿**——铝价响应带 `source: "simulated"`,[HomeView.vue](../frontend/src/modules/home/HomeView.vue) 按来源渲染"模拟数据 · 参考走势"标注(原硬编码"上海期货交易所"属虚假声明),死代码 `getAluminumPrice` 删除。④ **CI 门禁**——[.gitea/workflows/ci.yml](.gitea/workflows/ci.yml) 三 job:pytest 全量 / 前端构建(含 vue-tsc)/ openapi 漂移检测(conda pythonocc 环境重导出比对;已实测 pytest 与导出均不依赖 .env)。⑤ **OCC 方案 B 实施**(TECH_DEBT D10 清偿)——`run_occ(fn, *args)` → `run_occ(op_name, payload)`,执行器由线程池替换为常驻工作进程池 [occ_process_pool.py](../src/moldinsight/services/occ_process_pool.py) + 操作注册表 [occ_worker.py](../src/moldinsight/core/occ_worker.py):超时/崩溃 terminate 换新补位、任务级超时 recover 整体重建,**残留线程泄漏根治**(进程边界回收 C++ 栈);TopoDS 形状不跨进程(`generate_cavity` 分模 + 方案 STEP 持久化导出全在子进程内,返回 export_manifest);调用点全量迁移(解析/网格/型腔/分析/倒扣/STEP 转换),删除内存形状缓存链(`_cache_export_shapes`/`get_export_shapes`/`_persist_step_exports`)、`CADExporter.export_mold_results`(零调用方)、shape_loader(→ [stp_materializer.py](../src/moldinsight/services/stp_materializer.py));回归测试 [test_occ_process_pool.py](../tests/test_occ_process_pool.py)(OCC-gated,6 例含真实盒体 STP 解析/分模端到端)。**接口变更三件套随批完成**:openapi.json 重导出(76→77 paths,新增 `/html/{filename}`)+ 前端 `gen:api` 再生 + 前端构建通过(方案 B 接口面零变化,无路由/schema 变更)。**测试基线**:**143 passed, 0 skipped**(D11 8 项 + 铝价 2 项 + OCC 进程池 6 项;基线 129 中原 2 个 skip 已随本地环境补齐 celery/alembic 转为执行)。**下一步**:回到 §3 主线 P2/P3 长期方向——D3 剩余收敛(app_factory 参数收敛、identity/platform 语义)、inventory 服务下沉、D13 pip 锁文件随下次镜像构建补齐,见 [ROADMAP.md](ROADMAP.md) §3。)
|
||||
|
||||
> 2026-09-17(**批次 4(架构演进)完成,§3.1 治理批次全部执行完毕**:① D3 主体清偿——891 行的旧 `shared/models/database.py`(31 模型类三类同居,已删除)按归属拆为 [shared/models/base.py](../src/shared/models/base.py)(唯一 Base)+ [shared/models/identity.py](../src/shared/models/identity.py)(身份权限 7 表)+ [moldinsight/models/](../src/moldinsight/models/)(分析域 9 表)+ [inventory/models/](../src/inventory/models/)(进销存 15 表,catalog/warehouse/trading/finance 四文件);**三条跨模块 ORM relationship(`User.stp_files` / `STPFile.user` / `STPFile.product`)经全仓核实零使用,直接删除**——跨模块桥接收敛为裸 FK 硬规则([ARCHITECTURE.md](ARCHITECTURE.md) §5.1),单模块部署 mapper 可独立配置;约 45 处 import 全量改写(含 migrations/env.py 全量注册、scripts/ 两个一次性脚本),旧模块物理删除无兼容 facade;零调用方死方法 `db_manager.create_tables` 一并删除(拆分后会静默建残缺 schema);② D10 治理——`_reset_occ_executor` 补 `cancel_futures=True`(旧实现下"慢恢复"的旧线程会继续消化旧队列,与新 executor **并发操作非线程安全的 OCC**,属数据竞争而非单纯泄漏);吞吐方案设计先行定稿 [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md)(短期 A:celery `--concurrency` 伸缩 + `--max-tasks-per-child` 兜底,启动参数已记 [OPERATIONS.md](OPERATIONS.md) §3;中期 B:`run_occ` 契约进程化 + kill-on-timeout,待独立排期);③ 新增 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定归属边界(31 表全量注册 / 单模块独立 mapper 配置 / 旧模块无 facade);④ 顺手清偿 D15——[vite.config.ts](../frontend/vite.config.ts) 删除未用的 `mode` 参数,`vue-tsc -b` 恢复通过,前端生产构建链路解除阻断。**接口面零变化**(无路由与 schema 变更,openapi.json 不触发重导出)。**测试基线**:**125 passed, 2 skipped**(基线 122 + 新增归属测试 3 项)。**下一步**:治理批次收尾后回到主线方向;遗留项 D11(HTML RustFS 单源)、D13(pip 锁文件)、OCC 方案 B 独立批次。)
|
||||
|
||||
+4
-4
@@ -99,17 +99,17 @@
|
||||
|
||||
优先级:**P2**
|
||||
|
||||
### D3. shared/platform 边界仍需继续收敛 —— ORM 归属已清偿(2026-09-17,批次 4)
|
||||
### D3. shared/platform 边界仍需继续收敛 —— 主体已清偿(2026-09-17 批次 4 + 2026-09-18 后续)
|
||||
|
||||
已完成部分:
|
||||
- 共享 ORM(原最强耦合点)按模块拆分:base / identity(shared)+ moldinsight/models + inventory/models;跨模块只允许裸 FK,单模块部署 mapper 可独立配置(详见 §2.8 与 [ARCHITECTURE.md](ARCHITECTURE.md) §6.1)
|
||||
- 旧 `shared/models/database.py` 物理删除,无兼容 facade;归属边界由 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定
|
||||
- **app_factory 组合职责收敛**(2026-09-18):平台工厂只做纯平台引导,`connect_rustfs` 参数移除;moldinsight 专属接线(RustFS 启动钩子 [init_storage.py](../src/moldinsight/storage/init_storage.py) `rustfs_startup_hook`、路由单点聚合 [moldinsight/api/__init__.py](../src/moldinsight/api/__init__.py) `register_moldinsight_routers`)收敛回模块层,入口退化为纯组装(ARCHITECTURE §6.2)
|
||||
|
||||
仍保留的收敛方向(低优先级,随实际重构推进):
|
||||
- [app_factory.py](../src/shared/app_factory.py) 组合职责偏重(ARCHITECTURE §6.2)
|
||||
- identity / platform 的边界语义(ROADMAP §2.1)
|
||||
- identity / platform 的边界语义(ROADMAP §2.1):平台表与模块表的命名/注释口径随实际重构推进
|
||||
|
||||
优先级:**P3**(剩余部分)
|
||||
优先级:**P3**(仅剩 identity/platform 语义注释口径)
|
||||
|
||||
### D4. 文档现状 / 规划 / 历史混放
|
||||
|
||||
|
||||
@@ -10,28 +10,22 @@ sys.path.insert(0, str(src_root))
|
||||
os.chdir(Path(__file__).parent.parent.parent)
|
||||
|
||||
from shared.app_factory import create_app
|
||||
from moldinsight.storage.init_storage import rustfs_startup_hook
|
||||
|
||||
|
||||
def _register_routers(app):
|
||||
"""注册 moldinsight 业务路由"""
|
||||
"""注册 moldinsight 业务路由(/api 聚合 + HTML 报告代理,收敛于 moldinsight.api)"""
|
||||
try:
|
||||
from moldinsight.api import router as moldinsight_router
|
||||
app.include_router(moldinsight_router, prefix="/api")
|
||||
from moldinsight.api import register_moldinsight_routers
|
||||
register_moldinsight_routers(app)
|
||||
except Exception as e:
|
||||
print(f"[WARN] MoldInsight 路由: {e}")
|
||||
|
||||
# D11:HTML 报告代理挂根路径,URL 形状与原 StaticFiles 保持一致(/html/{filename})
|
||||
try:
|
||||
from moldinsight.api.html_report_router import include_into as include_html_report
|
||||
include_html_report(app)
|
||||
except Exception as e:
|
||||
print(f"[WARN] HTML 报告路由: {e}")
|
||||
|
||||
|
||||
app = create_app(
|
||||
title="Gemold - 模具分析引擎",
|
||||
service_name="moldinsight",
|
||||
connect_rustfs=True,
|
||||
serve_frontend_static=False,
|
||||
startup_hooks=[rustfs_startup_hook],
|
||||
register_routers=_register_routers,
|
||||
)
|
||||
|
||||
@@ -10,23 +10,17 @@ sys.path.insert(0, str(src_root))
|
||||
os.chdir(Path(__file__).parent.parent.parent)
|
||||
|
||||
from shared.app_factory import create_app
|
||||
from moldinsight.storage.init_storage import rustfs_startup_hook
|
||||
|
||||
|
||||
def _register_routers(app):
|
||||
"""注册 unified 业务路由"""
|
||||
"""注册 unified 业务路由(moldinsight 单点聚合 + inventory)"""
|
||||
try:
|
||||
from moldinsight.api import router as moldinsight_router
|
||||
app.include_router(moldinsight_router, prefix="/api")
|
||||
from moldinsight.api import register_moldinsight_routers
|
||||
register_moldinsight_routers(app)
|
||||
except Exception as e:
|
||||
print(f"[WARN] MoldInsight 路由: {e}")
|
||||
|
||||
# D11:HTML 报告代理挂根路径,URL 形状与原 StaticFiles 保持一致(/html/{filename})
|
||||
try:
|
||||
from moldinsight.api.html_report_router import include_into as include_html_report
|
||||
include_html_report(app)
|
||||
except Exception as e:
|
||||
print(f"[WARN] HTML 报告路由: {e}")
|
||||
|
||||
try:
|
||||
from inventory.api import inventory_router
|
||||
app.include_router(inventory_router)
|
||||
@@ -37,7 +31,7 @@ def _register_routers(app):
|
||||
app = create_app(
|
||||
title="Gemold - Unified Backend",
|
||||
service_name="unified",
|
||||
connect_rustfs=True,
|
||||
serve_frontend_static=False,
|
||||
startup_hooks=[rustfs_startup_hook],
|
||||
register_routers=_register_routers,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import importlib
|
||||
from shared.config.settings import settings
|
||||
from shared.utils.logger import get_logger
|
||||
from moldinsight.api.route_registry import route_load_status
|
||||
from moldinsight.api.html_report_router import include_into as include_html_report
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -54,3 +55,15 @@ def _safe_include(label: str, module_path: str, debug_only: bool = False):
|
||||
|
||||
for _label, _module_path, _debug_only in ROUTE_MODULES:
|
||||
_safe_include(_label, _module_path, _debug_only)
|
||||
|
||||
|
||||
def register_moldinsight_routers(app):
|
||||
"""注册 moldinsight 全部业务路由到 app(D3 收敛:入口侧单点调用)。
|
||||
|
||||
- /api 聚合路由:各子路由模块装载失败经 ROUTE_MODULES/route_load_status 呈现
|
||||
(/api/health degraded,DEBUG fail-fast),见 _safe_include
|
||||
- HTML 报告代理挂根路径 /html/{filename}(URL 形状与原 StaticFiles 一致),
|
||||
失败同样登记 route_load_status(html_report_router.include_into)
|
||||
"""
|
||||
app.include_router(router, prefix="/api")
|
||||
include_html_report(app)
|
||||
|
||||
@@ -36,6 +36,15 @@ async def init_rustfs_storage():
|
||||
return False
|
||||
|
||||
|
||||
async def rustfs_startup_hook():
|
||||
"""app_factory startup_hooks 注入点(D3 收敛):moldinsight/unified 入口把
|
||||
本钩子传入 create_app(startup_hooks=[rustfs_startup_hook]),RustFS 连接接线
|
||||
归属 moldinsight 层,平台工厂不再持有模块专属依赖(原 connect_rustfs 已移除)。
|
||||
"""
|
||||
ok = await init_rustfs_storage()
|
||||
print(f"[{'OK' if ok else 'WARN'}] RustFS")
|
||||
|
||||
|
||||
async def test_storage():
|
||||
"""测试 RustFS 对象存储功能"""
|
||||
try:
|
||||
|
||||
@@ -27,7 +27,6 @@ def create_app(
|
||||
title: str,
|
||||
service_name: str,
|
||||
version: str = "4.0.0",
|
||||
connect_rustfs: bool = False,
|
||||
serve_frontend_static: bool = False,
|
||||
startup_hooks: Optional[List[Callable[[], Awaitable[None]]]] = None,
|
||||
register_routers: Optional[Callable[[FastAPI], None]] = None,
|
||||
@@ -38,11 +37,10 @@ def create_app(
|
||||
title: 应用标题
|
||||
service_name: 服务名(用于 /health 响应)
|
||||
version: 版本号
|
||||
connect_rustfs: 是否连接 RustFS 对象存储(moldinsight 需要)。
|
||||
D11:/html 报告读取由 moldinsight.api.html_report_router 代理
|
||||
(RustFS 报告键直取 + 遗留对象/本地卷兜底),不再挂本地 StaticFiles。
|
||||
serve_frontend_static: 是否由后端托管 /static 与 SPA fallback(默认关闭,前端独立部署)
|
||||
startup_hooks: 额外的 startup 钩子列表(在数据库/RustFS/Redis 初始化后执行)
|
||||
startup_hooks: 额外的 startup 钩子列表(在数据库/Redis 初始化后执行)。
|
||||
模块专属的启动接线(如 moldinsight 的 RustFS 连接)经此参数注入——
|
||||
D3 收敛:平台工厂不再持有模块专属依赖(原 connect_rustfs 已移除)。
|
||||
register_routers: 回调函数,用于注册业务路由
|
||||
"""
|
||||
app = FastAPI(title=title, version=version)
|
||||
@@ -115,20 +113,6 @@ def create_app(
|
||||
success = await init_database(keep_connected=True)
|
||||
print(f"[{'OK' if success else 'FAIL'}] 数据库初始化")
|
||||
|
||||
# RustFS(仅 moldinsight 需要)
|
||||
if connect_rustfs:
|
||||
try:
|
||||
from moldinsight.storage.rustfs_storage import rustfs_manager
|
||||
await rustfs_manager.connect(
|
||||
endpoint=settings.RUSTFS_ENDPOINT,
|
||||
access_key=settings.RUSTFS_ACCESS_KEY,
|
||||
secret_key=settings.RUSTFS_SECRET_KEY,
|
||||
timeout=settings.RUSTFS_TIMEOUT,
|
||||
)
|
||||
print("[OK] RustFS连接成功")
|
||||
except Exception as e:
|
||||
print(f"[WARN] RustFS连接失败: {e}")
|
||||
|
||||
# Redis
|
||||
try:
|
||||
from shared.services.redis_task_manager import redis_task_manager
|
||||
|
||||
@@ -158,6 +158,22 @@ async def test_json_media_type_from_extension(client, tmp_path):
|
||||
assert "application/json" in resp.headers["content-type"]
|
||||
|
||||
|
||||
# ── D3 收敛:单点聚合注册 ───────────────────────────────────────
|
||||
|
||||
|
||||
def test_register_moldinsight_routers_mounts_api_and_html():
|
||||
"""register_moldinsight_routers 一次挂载 /api 聚合路由 + 根路径 /html 报告代理
|
||||
(入口侧不再重复 include_html_report)。"""
|
||||
from moldinsight.api import register_moldinsight_routers
|
||||
|
||||
application = FastAPI()
|
||||
register_moldinsight_routers(application)
|
||||
paths = {route.path for route in application.routes}
|
||||
assert "/api/upload" in paths
|
||||
# Starlette route.path 保留 :path 转换器(OpenAPI 路径才显示 /html/{filename})
|
||||
assert any(p.startswith("/html/{") for p in paths)
|
||||
|
||||
|
||||
# ── 未命中与防护 ────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user