haiku.rag/tests/test_uri.py
Yiorgis Gozadinos da6cdfbc51
Resolve file:// URIs to paths through url2pathname
urlparse().path keeps the leading slash in front of a Windows drive, so
file:///C:/docs/a.pdf read as \C:\docs\a.pdf and the ingester reported
"File does not exist" for every file it discovered. url2pathname is the
stdlib conversion that strips it, per platform.

Four sites each decided both "is this local" and "what path is this":
FSSource._uri_to_path and supports, resolve_adhoc_fetcher,
create_document_from_source and check_source_accessible, and convert.
is_local_uri and uri_to_path in haiku.rag.uri own those two decisions now,
which closes two more cases of the same root cause. A bare C:\docs\a.pdf
parses with scheme "c", so add-src raised "No source adapter for URI scheme
'c'" and convert silently treated the path as raw text. And convert and
check_source_accessible never percent-decoded at all, so a file named
a[b] c.md read as missing on Linux and macOS too.

A file URI's host is reattached after conversion rather than passed to
url2pathname, which as of 3.14 rejects a non-local authority off Windows.
file:////server/share is the empty-authority spelling of a UNC path, its
host being the first path segment, so that host is normalised into the
authority before conversion. Output is identical on 3.12, 3.13 and 3.14.

The ad-hoc FS fetcher roots at the path's own anchor rather than "/", which
on Windows is only the current drive.

test_uri.py runs on ubuntu, macos and windows across 3.13 and 3.14 without
the project installed: --noconftest because the repo conftest imports
dependencies that job does not need, and -o addopts= to drop the
repository's -n auto. The Windows legs are what cover the drive conversion.

Fixes #574.
2026-08-21 10:22:26 +03:00

64 lines
2 KiB
Python

"""Tests for haiku.rag.uri."""
import os
from pathlib import Path, PureWindowsPath
import pytest
from haiku.rag.uri import is_local_uri, uri_to_path
@pytest.mark.parametrize(
"uri,expected",
[
("file:///tmp/a.md", True),
("/tmp/a.md", True),
("relative/a.md", True),
# urlparse reads the Windows drive letter as the scheme.
("C:/docs/a.pdf", True),
("c:\\docs\\a.pdf", True),
("x:content", False),
("http://example.com/a.pdf", False),
("https://example.com/a.pdf", False),
("s3://bucket/a.pdf", False),
("webdav://host/a.pdf", False),
],
)
def test_is_local_uri(uri, expected):
assert is_local_uri(uri) is expected
def test_uri_to_path_decodes_percent_escapes():
assert uri_to_path("file:///tmp/a%5Bb%5D%20c.md") == Path("/tmp/a[b] c.md")
def test_uri_to_path_leaves_bare_paths_alone():
"""A bare path is not a URI, so ``a%20b.md`` is a filename, not ``a b.md``."""
assert uri_to_path("/tmp/a%20b.md") == Path("/tmp/a%20b.md")
def test_uri_to_path_rejects_remote_schemes():
with pytest.raises(ValueError, match="Not a local URI"):
uri_to_path("s3://bucket/key.pdf")
@pytest.mark.parametrize("authority", ["localhost", "LOCALHOST"])
def test_uri_to_path_omits_localhost_authority(authority):
assert uri_to_path(f"file://{authority}/tmp/a.md") == Path("/tmp/a.md")
@pytest.mark.parametrize(
"uri",
["file://server/share/a.md", "file:////server/share/a.md"],
ids=["authority", "empty_authority"],
)
def test_uri_to_path_reads_unc_host(uri):
"""Both spellings name a UNC host, the second through an empty authority."""
path = uri_to_path(uri)
assert path == Path("//server/share/a.md")
assert PureWindowsPath(path) == PureWindowsPath(r"\\server\share\a.md")
@pytest.mark.skipif(os.name != "nt", reason="Windows path semantics")
def test_uri_to_path_strips_windows_drive_prefix():
assert uri_to_path("file:///C:/docs/a.pdf") == Path("C:\\docs\\a.pdf")