58 lines
2.2 KiB
Python
58 lines
2.2 KiB
Python
"""对象存储适配器单元测试(仅 local 后端,不依赖网络)。"""
|
|||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from app.storage import (LocalStorage, get_storage, parse_ref,
|
||
|
|
resolve_display_path, reset_storage)
|
||
|
|
|
||
|
|
|
||
|
|
def test_local_save_read_delete(tmp_path):
|
||
|
|
storage = LocalStorage(root=tmp_path)
|
||
|
|
ref = storage.save("artifacts/seg_1.md", "正文内容".encode())
|
||
|
|
assert ref == "local://artifacts/seg_1.md"
|
||
|
|
assert storage.read("artifacts/seg_1.md") == "正文内容".encode()
|
||
|
|
assert storage.exists("artifacts/seg_1.md")
|
||
|
|
storage.delete("artifacts/seg_1.md")
|
||
|
|
assert not storage.exists("artifacts/seg_1.md")
|
||
|
|
|
||
|
|
|
||
|
|
def test_local_path_traversal_blocked(tmp_path):
|
||
|
|
storage = LocalStorage(root=tmp_path)
|
||
|
|
ref = storage.save("../../etc/passwd", b"x")
|
||
|
|
# key 被净化:引用不含 ..,文件落在 root 内而不是系统目录
|
||
|
|
assert ".." not in ref
|
||
|
|
assert "etc/passwd" in ref
|
||
|
|
# save 后文件真实存在于 root 内(.. 变成 _ 目录)
|
||
|
|
assert not ref.startswith("local://../")
|
||
|
|
saved = tmp_path / ref.removeprefix("local://")
|
||
|
|
assert saved.exists() and saved.parent.is_relative_to(tmp_path)
|
||
|
|
|
||
|
|
|
||
|
|
def test_parse_ref_schemes():
|
||
|
|
assert parse_ref("local://a/b.md") == ("local", "a/b.md")
|
||
|
|
assert parse_ref("s3://bucket/a/b.md") == ("s3", "bucket/a/b.md")
|
||
|
|
assert parse_ref("plain/path.md") == ("local", "plain/path.md")
|
||
|
|
assert parse_ref("") == ("local", "")
|
||
|
|
|
||
|
|
|
||
|
|
def test_resolve_display_path(tmp_path):
|
||
|
|
reset_storage()
|
||
|
|
local = resolve_display_path("local://artifacts/x.md")
|
||
|
|
assert local.endswith("artifacts/x.md")
|
||
|
|
assert resolve_display_path("s3://bucket/x.md") == "s3://bucket/x.md"
|
||
|
|
|
||
|
|
|
||
|
|
def test_get_storage_default_local(tmp_path, monkeypatch):
|
||
|
|
monkeypatch.delenv("YAWEN_STORAGE_BACKEND", raising=False)
|
||
|
|
reset_storage()
|
||
|
|
storage = get_storage()
|
||
|
|
assert isinstance(storage, LocalStorage)
|
||
|
|
|
||
|
|
|
||
|
|
def test_local_atomic_write(tmp_path):
|
||
|
|
storage = LocalStorage(root=tmp_path)
|
||
|
|
storage.save("k/a.txt", b"v1")
|
||
|
|
storage.save("k/a.txt", b"v2") # 覆盖写也走 tmp+rename
|
||
|
|
assert storage.read("k/a.txt") == b"v2"
|
||
|
|
leftovers = [p.name for p in (tmp_path / "k").iterdir()]
|
||
|
|
assert leftovers == ["a.txt"] # 无 .tmp 残留
|