fix(formula): 导出文件安全、清理回收、撤权校验与定义库可靠性(F01/F02/F05-F08)
Verify / verify (push) Failing after 1s
Verify / verify (pull_request) Failing after 1s
AI Review / review (pull_request) Successful in 16s

- F01: _safe_unlink_export 不跟随符号链接、逐层校验父目录,导出失败路径与
  清理命令统一使用,只清理受控目录内本次创建的文件
- F02: 清理失败记录告警日志;清理命令回收 failed 任务残留文件
- F05: 定义保存事务内锁数据集行,配额原子检查;revision 冲突返回 409;
  保留最近 20 版历史;支持个人/团队共享;审计与变更同事务
- F06: 工作簿参数页毛利率改为只读快照;可编辑单元格解锁并加数值校验
- F07: 对账按指标分容差(人数 0、比率 0.0001、金额 0.01);自定义列使用
  服务端 Decimal 表达式求值参考
- F08: 导出任务执行/下载阶段重校验账号有效性与角色
- 对话草稿会话恢复;载入定义后追问携带原定义(F04);下载改浏览器直传
- 迁移 0006-0008;PostgreSQL 咨询锁语义修正(import_worker)
This commit is contained in:
Jayway Liu
2026-10-11 09:43:27 +08:00
parent 494490f6e6
commit 509b406254
16 changed files with 594 additions and 82 deletions
+42 -3
View File
@@ -331,19 +331,34 @@ def test_formula_definition_library_is_private_validated_and_audited(workspace):
"right": {"metric": "cost"},
},
}
saved = client.post(url, data={"definition": definition}, content_type="application/json")
saved = client.post(url, data={"definition": definition, "shared": True}, content_type="application/json")
assert saved.status_code == 201
assert saved.json()["shared"] is True
definition_id = saved.json()["id"]
assert client.get(url).json()["items"][0]["definition"] == definition
updated = client.post(
url,
data={"definition": {**definition, "name": " 收入成本差 ", "meaning": "更新说明"}},
data={
"definition": {**definition, "name": " 收入成本差 ", "meaning": "更新说明"},
"shared": True,
"revision": saved.json()["revision"],
},
content_type="application/json",
)
assert updated.status_code == 200
assert FormulaDefinition.objects.filter(dataset=ds, user=ds.created_by).count() == 1
assert updated.json()["definition"]["meaning"] == "更新说明"
assert updated.json()["revision"] == 2
assert len(updated.json()["history"]) == 1
stale = client.post(
url,
data={"definition": {**definition, "meaning": "旧标签页覆盖"}, "revision": 1},
content_type="application/json",
)
assert stale.status_code == 409
assert FormulaDefinition.objects.get(pk=definition_id).definition["meaning"] == "更新说明"
other = Client()
other_user = ds.created_by.__class__.objects.create_user(
@@ -351,7 +366,8 @@ def test_formula_definition_library_is_private_validated_and_audited(workspace):
)
Membership.objects.create(user=other_user, organization=ds.organization, role="analyst")
assert other.login(username=other_user.username, password="Formula-Library-TestPassword991!")
assert other.get(url).json()["items"] == []
shared = other.get(url).json()["items"]
assert len(shared) == 1 and shared[0]["owned"] is False and shared[0]["shared"] is True
assert other.delete(f"{url}/{definition_id}").status_code == 404
deleted = client.delete(f"{url}/{definition_id}")
@@ -385,3 +401,26 @@ def test_formula_definition_library_rejects_unavailable_metric(workspace):
)
assert response.status_code == 400
assert "成本" in response.json()["error"]
def test_formula_definition_audit_failure_rolls_back_definition(workspace, monkeypatch):
ds, _, client, *_ = workspace
definition = {
"name": "审计回滚",
"meaning": "实际收入",
"scope": "所选分组与期间",
"missing": "结果不可用(#N/A);零分母不可用",
"expression": {"metric": "revenue"},
}
def fail(*_args, **_kwargs):
raise RuntimeError("audit unavailable")
monkeypatch.setattr("finance.formula_views.audit", fail)
with pytest.raises(RuntimeError, match="audit unavailable"):
client.post(
f"/api/datasets/{ds.pk}/formula-definitions",
data={"definition": definition},
content_type="application/json",
)
assert not FormulaDefinition.objects.filter(dataset=ds, user=ds.created_by).exists()