feat(scripts/resources): align list_scripts visibility with list_resources
69a9a48 把 data resources 的可见性从 list 恒真改成
workspace-wide + visibility 过滤 + admin 短路,但 scripts 端
没动。两端不对称,导致:
* 非 admin 调 list_scripts 走 user_relative_path →
workspace/{当前用户ID}/...,永远拿不到别人的脚本
* count_scripts 同样 user-scoped,dashboard "全部脚本"
只统计自己
* list_resources 响应没带 owner_display_name,data-only
owner 的目录名回退到 userId.slice(-6),显示不友好
修复:
* list_scripts / count_scripts 改 workspace-wide(新建
_build_list_scripts_workspace_descendant_prefix helper;
旧 _build_list_scripts_descendant_prefix 保留标 deprecated
避免破坏其它调用方);非 admin 追加
or_(owner_user_id = me, visibility in {workspace, public})
与 list_resources 完全对称;admin 短路
* resource_payload 加 owner_display_name 字段(与
script_payload 对称);list_resources SELECT 加
Users.display_name + outerjoin
* 前端 ScriptExplorer displayName 回退链:scripts 的
owner_display_name → data resources 的 owner_display_name
→ 本人 user.display_name → userId.slice(-6) 占位
;ResourceItem 类型同步加 owner_display_name?: string|null
存储物理布局仍是 workspace/{user_id}/...,仅读取侧
listing/count 跨 owner。
This commit is contained in:
@@ -322,6 +322,8 @@ def test_resource_payload_legacy_dot_resources():
|
||||
)
|
||||
assert payload["jupyter_accessible_path"] == ".resources/data.csv"
|
||||
assert payload["absolute_path"].endswith(f"{ws}/{user}/.resources/data.csv")
|
||||
# Default None when no Users join row is provided (bind_resource path).
|
||||
assert payload["owner_display_name"] is None
|
||||
|
||||
|
||||
def test_resource_payload_new_flat_path():
|
||||
@@ -333,6 +335,13 @@ def test_resource_payload_new_flat_path():
|
||||
)
|
||||
assert payload["jupyter_accessible_path"] == "data.csv"
|
||||
assert payload["absolute_path"].endswith(f"{ws}/{user}/data.csv")
|
||||
# Explicit owner_display_name is passed through to the payload.
|
||||
payload = resource_payload(
|
||||
_make_resource(ws, user),
|
||||
_make_storage_object(f"{ws}/{user}/data.csv"),
|
||||
owner_display_name="张三",
|
||||
)
|
||||
assert payload["owner_display_name"] == "张三"
|
||||
|
||||
|
||||
def test_resource_payload_new_nested_path():
|
||||
@@ -344,6 +353,7 @@ def test_resource_payload_new_nested_path():
|
||||
)
|
||||
assert payload["jupyter_accessible_path"] == "train/v1/data.csv"
|
||||
assert payload["absolute_path"].endswith(f"{ws}/{user}/train/v1/data.csv")
|
||||
assert payload["owner_display_name"] is None
|
||||
|
||||
|
||||
def test_compute_jupyter_relative_path_for_legacy_and_new_paths():
|
||||
@@ -621,6 +631,27 @@ async def test_list_resources_without_parent_path_adds_no_like_clause() -> None:
|
||||
assert " not like " not in sql
|
||||
|
||||
|
||||
async def test_list_resources_joins_users_for_display_name() -> None:
|
||||
"""list_resources must OUTER JOIN users and SELECT users.display_name so
|
||||
every resource carries owner_display_name (frontend displayName chain)."""
|
||||
from backend.resources import list_resources
|
||||
|
||||
captured_sql: list[str] = []
|
||||
mock_session = _list_resources_capturing_session(captured_sql)
|
||||
|
||||
await list_resources(
|
||||
parent_path="",
|
||||
context=_resource_ctx(),
|
||||
session=mock_session,
|
||||
visibility=None,
|
||||
keyword=None,
|
||||
)
|
||||
sql = captured_sql[0]
|
||||
sql_lower = sql.lower()
|
||||
assert "outer join users" in sql_lower
|
||||
assert "users.display_name" in sql_lower
|
||||
|
||||
|
||||
# ─── layer 3: behavioral test on real LIKE execution (SQLite) ────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user