Files
KSP_project/docs/asset_crud_retest_report_2026-05-31.md
ArmorandClaude Opus 4.7 e1d601a774 chore: add reference docs, scripts, tests, demo, prototypes
- Reference docs: known issues, user journey, location redesign, mission merge plan
- Scripts: fill_location_state, merge_missions, migrate_location, migrate_state
- Tests: e2e test suite, asset entries unit tests
- Demo: location board, hifi prototypes, test screenshots
- Data backup: asset log entries and state nodes
- Update .gitignore

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 15:29:47 +08:00

12 KiB
Raw Permalink Blame History

任务资产与日志 CRUD 复测报告

日期:2026-05-31

结论

本轮只做测试和报告,没有修改业务代码。

资产本体的新增、修改、删除主流程基本可用:测试资产可以创建,可以通过表单路由修改 name/program/home_region/note,也可以删除,删除后数据库中不再存在该测试资产。

资产日志和 state node 仍有 P0 问题。正式日志表单创建/编辑 state interval 时,entry 本体字段能保存,但 state node 的 state_labelprevious_locationtransit_locationtarget_location 全部没有保存。资产详情页的快速新增日志 modal 走 JSON 路由,这条路径更不完整:state/location/node location 字段被忽略;快速新增 Event Point 还会因为没有 start time 直接 400。

测试范围

按用户要求覆盖:

  • 新建资产
  • 修改资产信息
  • 删除资产
  • 新建资产日志
  • 修改资产日志
  • 修改 state node 信息,重点包括 target location

测试数据使用 TEST_Codex_... 前缀,测试结束后已清理。复测后查询确认剩余 TEST_Codex_% 资产数量为 0。

执行记录

1. 新建资产

通过 /assets/new?sim_time=2060-03-12T09:00 UI 表单创建:

  • Name: TEST_Codex_Asset_20260531_719359
  • Asset Type: Station
  • Program: TEST Program Alpha
  • Home Region: LEO
  • Note: Created by Codex asset CRUD retest; safe to delete.

结果:

  • POST 后 302 跳转到资产详情页。
  • 详情页 H1 显示测试资产名。
  • DB 中存在该资产。

状态:通过。

2. 修改资产信息

通过 /assets/<id>/edit 表单路由提交:

  • Name 改为 TEST_Codex_Asset_20260531_719359_EDITED
  • Program 改为 TEST Program Beta
  • Home Region 改为 Mars Orbit
  • Note 改为 Edited by Codex asset CRUD retest.

结果:

{
  "status": 302,
  "name": "TEST_Codex_Asset_20260531_719359_EDITED",
  "program": "TEST Program Beta",
  "home_region": "Mars Orbit",
  "note": "Edited by Codex asset CRUD retest."
}

状态:通过。

备注:使用 in-app browser 直接填 edit form 时遇到浏览器控制层 CDP/clipboard 超时,我没有把它记为应用 bug;最终用同一个 HTTP 表单路由验证保存契约。

3. 删除资产

通过 /assets/<id>/delete 表单路由删除测试资产。

结果:

{
  "status": 302,
  "location": "/assets?sim_time=2060-03-12T09:00",
  "exists_after_delete": false
}

状态:通过。

日志与 State Node 问题

P0-1: 正式日志表单没有保存 state node 的 location/state 字段

操作:

通过 /assets/<id>/entries/new 的正式表单路由创建 state interval

  • Entry title: TEST Formal State 20260531_719359
  • Mission: TEST Mission Beta
  • Start/End: 2060-03-13T09:002060-03-16T09:00
  • Node title: Formal Node A
  • Node state: Docked
  • Previous Location: LEO
  • Transit Location: Transfer
  • Target Location: Mars Orbit

结果:

{
  "title": "Formal Node A",
  "detail": "Formal node A detail",
  "state_label": null,
  "previous_location": null,
  "transit_location": null,
  "target_location": null
}

再通过 /assets/<id>/entries/<entry_id>/edit 修改同一个 node

  • Node title 改为 Formal Node A EDITED
  • Node state 改为 Exploration
  • Previous Location 改为 Mars Orbit
  • Transit Location 改为 Transfer
  • Target Location 改为 Mars Surface

结果仍然是:

{
  "title": "Formal Node A EDITED",
  "detail": "Formal node A detail edited",
  "state_label": null,
  "previous_location": null,
  "transit_location": null,
  "target_location": null
}

状态:未通过。

根因线索:

  • app/routes/web.py_parse_state_node_rows()enumerate(..., start=1),但读取 node_locations[index] / node_state_labels[index]。表单数组是 0-based,第一个 node 应该用 row_index,当前第一个 node 永远读不到这些扩展字段。
  • app/routes/web.py 把 target location 存进 row key "location"
  • app/routes/web.py _sync_state_nodes() 却读取 row.get("target_location")。即使修掉 indextarget location 仍会因为 key 不一致继续丢失。

P0-2: 资产详情页快速新增日志 JSON 路由忽略 location/state/node 扩展字段

资产详情页底部 quick modal 的 saveLog() 走 JSON

  • /assets/<id>/entries/new
  • Content-Type: application/json

这条后端路径只写:

  • entry_kind
  • title
  • summary
  • start_at
  • end_at
  • state node 的 title/detail/at_time

它没有写入:

  • entry 的 mission_label
  • node 的 state_label
  • node 的 previous_location
  • node 的 transit_location
  • node 的 target_location

复测 payload 中传了这些字段:

{
  "state_label": "Maintenance",
  "location": "Lunar Orbit",
  "state_nodes": [
    {
      "state_label": "Maintenance",
      "previous_location": "LEO",
      "transit_location": "Transfer",
      "target_location": "Lunar Orbit"
    }
  ]
}

保存后 DB 中 node 仍是:

{
  "state_label": null,
  "previous_location": null,
  "transit_location": null,
  "target_location": null
}

状态:未通过。

根因线索:

  • app/routes/web.py JSON create path 手写字段映射,没有复用 _apply_asset_entry_fields() / _parse_state_node_rows()
  • app/routes/web.py JSON edit path 也有同样问题。
  • app/templates/asset_detail.html quick modal 的 state_nodes payload 只包含 title/at_time/detailUI 本身也没有 target location/state node fields。

P0-3: 资产详情页快速新增 Event Point 无法保存

操作:

模拟资产详情页 openNewLog('event')saveLog() 的 JSON 行为:start_at=nullend_at=null

结果:

  • HTTP 400
  • 数据库报错:null value in column "start_at" of relation "asset_log_entries" violates not-null constraint

原因:

状态:未通过。

P1-1: 正式日志编辑页切换 Entry Type 的 JS 有潜在错误

代码中:

document.getElementById("state-wrap").style.display = isState ? "" : "none";

但当前模板没有 id="state-wrap" 的元素。切换 entry type 时会触发空引用错误。

位置:

状态:未完整浏览器复现,但静态检查明确有风险。

P1-2: 资产详情页日志搜索和类型筛选控件没有实际逻辑

资产详情页有:

  • #ad-lq
  • #ad-lf

但模板中没有对应筛选脚本,也没有表单提交参数。控件看起来可用,实际不会过滤日志表。

位置:

状态:静态检查明确未实现。

当前通过项

  • 新建资产:通过。
  • 修改资产本体字段:通过。
  • 删除资产:通过,关联日志随资产清理。
  • 正式日志表单的 entry 本体字段保存:部分通过,title/mission_label/start_at/end_at/summary/note 可保存和修改。

当前未通过项

  • 正式日志表单保存 state node 的 target_location:失败。
  • 正式日志表单保存 state node 的 state_label/previous_location/transit_location:失败。
  • 资产详情页 quick modal 保存 state node 扩展字段:失败。
  • 资产详情页 quick modal 新增 Event Point:失败。
  • 资产详情页日志搜索/类型筛选:未实现。
  • 正式日志编辑页切换 Entry Type:存在 JS 空引用风险。

改进建议

P0

  1. 统一日志保存路径。

    • 不要让 JSON create/edit 和 form create/edit 各写一套字段映射。
    • 建议把 request JSON 转成同一份 internal DTO,然后复用同一个 validation + persistence 函数。
  2. 修复 _parse_state_node_rows() 的数组下标。

    • 读取 node 扩展字段时使用 row_index,不要用从 1 开始的 index
  3. 修复 target location key。

    • _parse_state_node_rows() 应输出 "target_location",或 _sync_state_nodes() 应读取 "location";两边必须一致。
  4. 资产详情 quick modal 要么删除,要么补齐。

    • 如果保留 quick modalstate log 必须能填写 node state/previous/transit/target。
    • Event Point 必须显示 Start At。
    • JSON route 必须保存同等字段。
  5. 后端 JSON 错误不要向用户暴露 SQLAlchemy/psycopg 全量错误。

    • 当前 Event Point 失败时返回了完整 SQL、参数和 DB 约束信息。
    • 应返回用户级 validation error,例如 Start At is required

P1

  1. 修复 state-wrap 空引用。
  2. 实现资产详情日志搜索/类型筛选,或移除未接线控件。
  3. 资产详情日志表应展示 node 的 state/location 字段,否则保存成功与否很难肉眼验证。
  4. 给 location/state combobox 增加可测试的稳定选择器或 data attributes。

端到端测试方向

建议新增一个独立的资产 CRUD E2E 套件,必须使用唯一测试前缀并自动清理。

Fixture

  • 创建 TEST_E2E_ASSET_<timestamp>
  • 固定 sim_time=2060-03-12T09:00
  • 每个测试结束查询并删除 TEST_E2E_% 资产。

用例 1: 资产本体 CRUD

  1. 打开 /assets/new?sim_time=2060-03-12T09:00
  2. 填写 name/type/program/home_region/note。
  3. 提交后断言跳到详情页。
  4. 打开 edit 页修改 name/program/home_region/note/retired。
  5. 提交后断言详情页 Quick Info 和 DB 一致。
  6. 删除资产。
  7. 断言列表页搜不到,DB 不存在。

用例 2: 正式 State Interval + State Node

  1. 从资产详情点击 +Entry 或打开 /assets/<id>/entries/new?kind=state
  2. 填写 entry title、mission、start/end、summary。
  3. 添加两个 state nodes。
  4. 每个 node 填 title/time/detail/state/previous/transit/target。
  5. 保存后断言 DB 中两个 node 全字段一致,并按 at_time 排序。
  6. 打开 edit 页修改第一个 node 的 target location。
  7. 保存后断言 DB 更新。
  8. 详情页断言 current location / log row location 使用最新 target location。

用例 3: Quick Modal State Log

  1. 在资产详情点击 + State Interval
  2. 验证 modal 是否有足够字段保存 state node location/state。
  3. 保存后断言 JSON route 与正式 form route 行为一致。

用例 4: Quick Modal Event Point

  1. 在资产详情点击 + Event Point
  2. 断言 Start At 可见且必填。
  3. 不填 Start At 时应显示 validation,不应返回原始 SQL 错误。
  4. 填 Start At 后保存成功。

用例 5: 日志编辑和删除

  1. 打开 entry edit 页。
  2. 修改 entry 本体字段。
  3. 修改已有 node。
  4. 新增一个 node。
  5. 删除一个 node。
  6. 保存后断言 DB 与页面都一致。
  7. 删除 entry 后断言关联 state nodes 级联删除。

用例 6: 资产详情日志筛选

  1. 给同一 asset 创建 state 和 event 两类日志。
  2. 输入搜索词,断言行数变化。
  3. 选择 State Interval,断言只显示 state。
  4. 选择 Event Point,断言只显示 event。

复测清理状态

  • TEST_Codex_Asset_20260531_719359 / edited variant:已删除。
  • TEST_Codex_EventModal_20260531_719359:已删除。
  • DB 查询 TEST_Codex_%0 条。