Files
yawan-workbench/server/tests/test_storage.py
T

58 lines
2.2 KiB
Python
Raw Normal View History

"""对象存储适配器单元测试(仅 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 残留