test(workspace): cover Windows backslash artifact open paths

openArtifactPath/_workspacePathExists split on '/' but Windows artifact
paths arrive as backslash absolute paths. Regression tests drive the real
functions from static/workspace.js via node (mirroring
test_issue3262_artifact_path_canonicalization.py): backslash subdir and
root artifacts must open, POSIX relative clicks must keep working, and a
genuinely missing artifact must still surface file_open_failed.
This commit is contained in:
fatbee0808 committed 2026-08-05 11:26:27 +08:00
1 parent 71ee457d7d
commit 5a01ce11cd
1 file changed
+126
@@ -0,0 +1,126 @@
"""Regression: artifact clicks with Windows backslash paths open correctly.
Windows artifact paths are absolute backslash paths, e.g.
``D:\\proj\\dir\\file.md``, while ``openArtifactPath()`` /
``_workspacePathExists()`` split on ``/``. Before the fix:
* the workspace-prefix strip (``rel.startsWith(normWs)``) never matched, so the
full absolute path was passed down, and
* ``_workspacePathExists()``'s ``split('/')`` treated the whole backslash path
as a single filename, so ``/api/list`` never matched and the UI reported
"cannot open file" (``file_open_failed``).
The fix normalizes both ``rel`` and the session workspace to ``/`` separators
before stripping. This drives the ACTUAL functions from static/workspace.js via
node (same pattern as test_issue3262_artifact_path_canonicalization.py) so the
test cannot drift from the real frontend code.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
WORKSPACE_JS = (REPO / "static" / "workspace.js").read_text(encoding="utf-8")
NODE = shutil.which("node")
pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH")
def _extract_function(source: str, name: str) -> str:
start = source.index(f"async function {name}(" if f"async function {name}(" in source else f"function {name}(")
brace = source.index("{", start)
depth = 0
for pos in range(brace, len(source)):
if source[pos] == "{":
depth += 1
elif source[pos] == "}":
depth -= 1
if depth == 0:
return source[start : pos + 1]
raise AssertionError(f"could not extract {name}")
def _run_artifact_open(path: str, workspace: str, entries_by_dir: dict) -> dict:
"""Run the real openArtifactPath + _workspacePathExists against stubbed deps.
``entries_by_dir`` maps a workspace-relative dir ('.' for root) to the list
of entry names /api/list would return, so the existence check is exercised
with realistic data. Returns the recorded calls (openFile / setStatus).
"""
open_artifact = _extract_function(WORKSPACE_JS, "openArtifactPath")
path_exists = _extract_function(WORKSPACE_JS, "_workspacePathExists")
entries_json = json.dumps(entries_by_dir)
driver = f"""
const S = {{ session: {{ session_id: 't', workspace: {json.dumps(workspace)} }} }};
const calls = [];
function switchWorkspacePanelTab() {{}}
function t(key) {{ return key; }}
function setStatus(msg) {{ calls.push({{status: msg}}); }}
function openFile(rel) {{ calls.push({{open: rel}}); }}
const ENTRIES_BY_DIR = {entries_json};
async function api(url) {{
const m = url.match(/[?&]path=([^&]*)/);
const dir = m ? decodeURIComponent(m[1]) : '.';
calls.push({{api: dir}});
return {{ entries: (ENTRIES_BY_DIR[dir] || []).map(name => ({{name, path: name}})) }};
}}
{path_exists}
{open_artifact}
(async () => {{
await openArtifactPath({json.dumps(path)});
process.stdout.write(JSON.stringify(calls));
}})();
"""
r = subprocess.run(
[NODE, "-e", driver],
capture_output=True, text=True, timeout=15,
)
assert r.returncode == 0, f"node failed: {r.stderr}"
return json.loads(r.stdout)
WS = r"D:\proj"
def test_windows_backslash_subdir_artifact_opens():
"""Subdir artifact with backslash absolute path strips to rel path and opens."""
calls = _run_artifact_open(
r"D:\proj\src\report.pdf",
WS,
{".": ["readme.md"], "src": ["report.pdf"]},
)
assert calls[-1] == {"open": "src/report.pdf"}, calls
def test_windows_backslash_root_artifact_opens():
"""Root-level artifact with backslash absolute path opens."""
calls = _run_artifact_open(
r"D:\proj\readme.md",
WS,
{".": ["readme.md"], "src": ["report.pdf"]},
)
assert calls[-1] == {"open": "readme.md"}, calls
def test_forward_slash_relative_behavior_unchanged():
"""Existing POSIX-style relative artifact clicks keep working (no regression)."""
calls = _run_artifact_open(
"./src/report.pdf",
WS,
{".": ["readme.md"], "src": ["report.pdf"]},
)
assert calls[-1] == {"open": "src/report.pdf"}, calls
def test_missing_artifact_still_reports_failure():
"""A genuinely missing artifact still surfaces file_open_failed."""
calls = _run_artifact_open(
r"D:\proj\src\missing.md",
WS,
{".": ["readme.md"], "src": ["report.pdf"]},
)
assert calls[-1] == {"status": "file_open_failed"}, calls