fix(formula): 导出文件安全、清理回收、撤权校验与定义库可靠性(F01/F02/F05-F08)
- 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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user