chore: import upstream snapshot with attribution
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
@@ -0,0 +1,41 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.cache.index import RAMIndexCacheStore
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def store():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:core:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore(ttl=600)
|
||||
@@ -0,0 +1,65 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.create import create
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def mk_store():
|
||||
stores = []
|
||||
|
||||
async def _make(prefix):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix=prefix)
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
stores.append(s)
|
||||
return RedisAccessor(s)
|
||||
|
||||
yield _make
|
||||
for s in stores:
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create(mk_store):
|
||||
a = await mk_store("test:create:1:")
|
||||
await create(a, "/new.txt")
|
||||
assert await a.store.get_file("/new.txt") == b""
|
||||
assert await a.store.get_modified("/new.txt") is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_overwrites_existing(mk_store):
|
||||
a = await mk_store("test:create:2:")
|
||||
await a.store.set_file("/existing.txt", b"old data")
|
||||
await create(a, "/existing.txt")
|
||||
assert await a.store.get_file("/existing.txt") == b""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_normalizes_path(mk_store):
|
||||
a = await mk_store("test:create:3:")
|
||||
await create(a, "file.txt")
|
||||
assert await a.store.has_file("/file.txt")
|
||||
@@ -0,0 +1,96 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.du import du, du_all
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:du:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/sub")
|
||||
await s.set_file("/a.txt", b"hello")
|
||||
await s.set_file("/sub/b.txt", b"world!")
|
||||
await s.set_file("/sub/c.txt", b"data")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_root(accessor):
|
||||
total = await du(accessor,
|
||||
PathSpec(resource_path="", virtual="/", directory="/"))
|
||||
assert total == 5 + 6 + 4
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_subdir(accessor):
|
||||
total = await du(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub", virtual="/sub", directory="/sub"))
|
||||
assert total == 6 + 4
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_single_file(accessor):
|
||||
total = await du(
|
||||
accessor,
|
||||
PathSpec(resource_path="a.txt", virtual="/a.txt", directory="/a.txt"))
|
||||
assert total == 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_empty():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:du:e:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
total = await du(a, PathSpec(resource_path="", virtual="/", directory="/"))
|
||||
assert total == 0
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_all_root(accessor):
|
||||
entries, total = await du_all(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"))
|
||||
assert total == 15
|
||||
paths = [e[0] for e in entries]
|
||||
assert "/a.txt" in paths
|
||||
assert "/sub/b.txt" in paths
|
||||
assert "/sub/c.txt" in paths
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_du_all_subdir(accessor):
|
||||
entries, total = await du_all(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub", virtual="/sub", directory="/sub"))
|
||||
assert total == 10
|
||||
assert len(entries) == 2
|
||||
@@ -0,0 +1,297 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.append import append_bytes
|
||||
from mirage.core.redis.copy import copy
|
||||
from mirage.core.redis.exists import exists
|
||||
from mirage.core.redis.rename import rename
|
||||
from mirage.core.redis.rm import rm_r
|
||||
from mirage.core.redis.rmdir import rmdir
|
||||
from mirage.core.redis.truncate import truncate
|
||||
from mirage.core.redis.unlink import unlink
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:fops:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/dir")
|
||||
await s.set_file("/file.txt", b"hello")
|
||||
await s.set_file("/dir/child.txt", b"child")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def mk_store():
|
||||
stores = []
|
||||
|
||||
async def _make(prefix):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix=prefix)
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
stores.append(s)
|
||||
return RedisAccessor(s)
|
||||
|
||||
yield _make
|
||||
for s in stores:
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_copy(accessor):
|
||||
await copy(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"),
|
||||
PathSpec(resource_path="copy.txt",
|
||||
virtual="/copy.txt",
|
||||
directory="/copy.txt"))
|
||||
assert await accessor.store.get_file("/copy.txt") == b"hello"
|
||||
assert await accessor.store.get_file("/file.txt") == b"hello"
|
||||
assert await accessor.store.get_modified("/copy.txt") is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_copy_not_found(mk_store):
|
||||
a = await mk_store("test:fops:cp:")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await copy(
|
||||
a,
|
||||
PathSpec(resource_path="nope.txt",
|
||||
virtual="/nope.txt",
|
||||
directory="/nope.txt"),
|
||||
PathSpec(resource_path="dst.txt",
|
||||
virtual="/dst.txt",
|
||||
directory="/dst.txt"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rename_file(accessor):
|
||||
await rename(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"),
|
||||
PathSpec(resource_path="renamed.txt",
|
||||
virtual="/renamed.txt",
|
||||
directory="/renamed.txt"))
|
||||
assert await accessor.store.has_file("/renamed.txt")
|
||||
assert not await accessor.store.has_file("/file.txt")
|
||||
assert await accessor.store.get_file("/renamed.txt") == b"hello"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rename_directory(accessor):
|
||||
await rename(
|
||||
accessor,
|
||||
PathSpec(resource_path="dir", virtual="/dir", directory="/dir"),
|
||||
PathSpec(resource_path="newdir",
|
||||
virtual="/newdir",
|
||||
directory="/newdir"))
|
||||
assert await accessor.store.has_dir("/newdir")
|
||||
assert not await accessor.store.has_dir("/dir")
|
||||
assert await accessor.store.has_file("/newdir/child.txt")
|
||||
assert not await accessor.store.has_file("/dir/child.txt")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rename_not_found(mk_store):
|
||||
a = await mk_store("test:fops:rn:")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await rename(
|
||||
a,
|
||||
PathSpec(resource_path="nope", virtual="/nope", directory="/nope"),
|
||||
PathSpec(resource_path="dst", virtual="/dst", directory="/dst"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rm_r_file(accessor):
|
||||
await rm_r(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"))
|
||||
assert not await accessor.store.has_file("/file.txt")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rm_r_directory(accessor):
|
||||
await rm_r(accessor,
|
||||
PathSpec(resource_path="dir", virtual="/dir", directory="/dir"))
|
||||
assert not await accessor.store.has_dir("/dir")
|
||||
assert not await accessor.store.has_file("/dir/child.txt")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rmdir_empty(mk_store):
|
||||
a = await mk_store("test:fops:rd:")
|
||||
await a.store.add_dir("/empty")
|
||||
await rmdir(
|
||||
a, PathSpec(resource_path="empty",
|
||||
virtual="/empty",
|
||||
directory="/empty"))
|
||||
assert not await a.store.has_dir("/empty")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rmdir_not_empty(accessor):
|
||||
with pytest.raises(OSError, match="directory not empty"):
|
||||
await rmdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="dir", virtual="/dir", directory="/dir"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rmdir_not_found(mk_store):
|
||||
a = await mk_store("test:fops:rd2:")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await rmdir(
|
||||
a,
|
||||
PathSpec(resource_path="nope", virtual="/nope", directory="/nope"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unlink(accessor):
|
||||
await unlink(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"))
|
||||
assert not await accessor.store.has_file("/file.txt")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unlink_not_found(mk_store):
|
||||
a = await mk_store("test:fops:ul:")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await unlink(
|
||||
a,
|
||||
PathSpec(resource_path="nope.txt",
|
||||
virtual="/nope.txt",
|
||||
directory="/nope.txt"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_truncate_shorter(accessor):
|
||||
await truncate(accessor, "/file.txt", 3)
|
||||
assert await accessor.store.get_file("/file.txt") == b"hel"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_truncate_longer(accessor):
|
||||
await truncate(accessor, "/file.txt", 8)
|
||||
data = await accessor.store.get_file("/file.txt")
|
||||
assert data == b"hello\x00\x00\x00"
|
||||
assert len(data) == 8
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_truncate_nonexistent(mk_store):
|
||||
a = await mk_store("test:fops:tr:")
|
||||
await truncate(a, "/new.txt", 5)
|
||||
assert await a.store.get_file("/new.txt") == b"\x00\x00\x00\x00\x00"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_truncate_to_zero(accessor):
|
||||
await truncate(accessor, "/file.txt", 0)
|
||||
assert await accessor.store.get_file("/file.txt") == b""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_append_to_existing(accessor):
|
||||
await append_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"), b" world")
|
||||
assert await accessor.store.get_file("/file.txt") == b"hello world"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_append_to_new(mk_store):
|
||||
a = await mk_store("test:fops:ap:")
|
||||
await append_bytes(
|
||||
a,
|
||||
PathSpec(resource_path="new.txt",
|
||||
virtual="/new.txt",
|
||||
directory="/new.txt"), b"data")
|
||||
assert await a.store.get_file("/new.txt") == b"data"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_append_multiple(mk_store):
|
||||
a = await mk_store("test:fops:ap2:")
|
||||
await append_bytes(
|
||||
a, PathSpec(resource_path="f.txt",
|
||||
virtual="/f.txt",
|
||||
directory="/f.txt"), b"a")
|
||||
await append_bytes(
|
||||
a, PathSpec(resource_path="f.txt",
|
||||
virtual="/f.txt",
|
||||
directory="/f.txt"), b"b")
|
||||
await append_bytes(
|
||||
a, PathSpec(resource_path="f.txt",
|
||||
virtual="/f.txt",
|
||||
directory="/f.txt"), b"c")
|
||||
assert await a.store.get_file("/f.txt") == b"abc"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exists_file(accessor):
|
||||
assert await exists(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt")) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exists_dir(accessor):
|
||||
assert await exists(
|
||||
accessor,
|
||||
PathSpec(resource_path="dir", virtual="/dir",
|
||||
directory="/dir")) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exists_root(accessor):
|
||||
assert await exists(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/")) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exists_missing(mk_store):
|
||||
a = await mk_store("test:fops:ex:")
|
||||
assert await exists(
|
||||
a, PathSpec(resource_path="nope", virtual="/nope",
|
||||
directory="/nope")) is False
|
||||
@@ -0,0 +1,189 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.find import find
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:find:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/src")
|
||||
await s.add_dir("/src/lib")
|
||||
await s.set_file("/readme.md", b"readme")
|
||||
await s.set_file("/src/main.py", b"print('hi')")
|
||||
await s.set_file("/src/util.py", b"def f(): pass")
|
||||
await s.set_file("/src/lib/helper.py", b"helper")
|
||||
await s.set_file("/src/lib/data.json", b"{}")
|
||||
await s.set_file("/big.bin", b"x" * 1000)
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_all(accessor):
|
||||
results = await find(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"))
|
||||
assert "/readme.md" in results
|
||||
assert "/src/main.py" in results
|
||||
assert "/src/lib/helper.py" in results
|
||||
assert "/src/lib/data.json" in results
|
||||
assert "/big.bin" in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_by_name(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
name="*.py")
|
||||
assert results == [
|
||||
"/src/lib/helper.py",
|
||||
"/src/main.py",
|
||||
"/src/util.py",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_by_type_file(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="src",
|
||||
virtual="/src",
|
||||
directory="/src"),
|
||||
type="f")
|
||||
assert "/src/main.py" in results
|
||||
assert "/src/lib" not in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_by_type_dir(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
type="d")
|
||||
assert "/src" in results
|
||||
assert "/src/lib" in results
|
||||
assert "/readme.md" not in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_maxdepth(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
maxdepth=1,
|
||||
type="f")
|
||||
assert "/readme.md" in results
|
||||
assert "/big.bin" in results
|
||||
assert "/src/main.py" not in results
|
||||
assert "/src/lib/helper.py" not in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_mindepth(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
mindepth=2,
|
||||
type="f")
|
||||
assert "/readme.md" not in results
|
||||
assert "/src/main.py" in results
|
||||
assert "/src/lib/helper.py" in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_min_size(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
min_size=100,
|
||||
type="f")
|
||||
assert results == ["/big.bin"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_max_size(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
max_size=10,
|
||||
type="f")
|
||||
assert "/readme.md" in results
|
||||
assert "/src/lib/data.json" in results
|
||||
assert "/big.bin" not in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_name_exclude(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="src",
|
||||
virtual="/src",
|
||||
directory="/src"),
|
||||
name="*.py",
|
||||
name_exclude="util*")
|
||||
assert "/src/util.py" not in results
|
||||
assert "/src/main.py" in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_or_names(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
or_names=["*.py", "*.json"])
|
||||
assert "/src/main.py" in results
|
||||
assert "/src/lib/data.json" in results
|
||||
assert "/readme.md" not in results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_subdir(accessor):
|
||||
results = await find(accessor,
|
||||
PathSpec(resource_path="src/lib",
|
||||
virtual="/src/lib",
|
||||
directory="/src/lib"),
|
||||
type="f")
|
||||
assert results == [
|
||||
"/src/lib/data.json",
|
||||
"/src/lib/helper.py",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_empty_result():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:find:e:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
results = await find(a,
|
||||
PathSpec(resource_path="", virtual="/",
|
||||
directory="/"),
|
||||
name="*.xyz")
|
||||
assert results == []
|
||||
await s.clear()
|
||||
await s.close()
|
||||
@@ -0,0 +1,113 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.cache.index import RAMIndexCacheStore
|
||||
from mirage.core.redis.glob import resolve_glob
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:glob:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/src")
|
||||
await s.set_file("/src/main.py", b"main")
|
||||
await s.set_file("/src/util.py", b"util")
|
||||
await s.set_file("/src/data.json", b"{}")
|
||||
await s.set_file("/readme.md", b"readme")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore(ttl=600)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_file_scope(accessor, index):
|
||||
scopes = [
|
||||
PathSpec(resource_path="readme.md",
|
||||
virtual="/readme.md",
|
||||
directory="/",
|
||||
resolved=True)
|
||||
]
|
||||
result = await resolve_glob(accessor, scopes, index)
|
||||
assert result[0].virtual == "/readme.md"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_pattern(accessor, index):
|
||||
scopes = [
|
||||
PathSpec(
|
||||
resource_path="src/*.py",
|
||||
virtual="/src/*.py",
|
||||
directory="/src",
|
||||
pattern="*.py",
|
||||
resolved=False,
|
||||
)
|
||||
]
|
||||
result = await resolve_glob(accessor, scopes, index)
|
||||
originals = [r.virtual for r in result]
|
||||
assert any(o == "/src/main.py" for o in originals)
|
||||
assert any(o == "/src/util.py" for o in originals)
|
||||
assert not any(o == "/src/data.json" for o in originals)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_directory_scope(accessor, index):
|
||||
scopes = [
|
||||
PathSpec(
|
||||
resource_path="src",
|
||||
virtual="/src",
|
||||
directory="/src",
|
||||
pattern=None,
|
||||
resolved=False,
|
||||
)
|
||||
]
|
||||
result = await resolve_glob(accessor, scopes, index)
|
||||
assert result[0].virtual == "/src"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_glob_multiple_scopes(accessor, index):
|
||||
scopes = [
|
||||
PathSpec(resource_path="readme.md",
|
||||
virtual="/readme.md",
|
||||
directory="/",
|
||||
resolved=True),
|
||||
PathSpec(
|
||||
resource_path="src/*.py",
|
||||
virtual="/src/*.py",
|
||||
directory="/src",
|
||||
pattern="*.py",
|
||||
resolved=False,
|
||||
),
|
||||
]
|
||||
result = await resolve_glob(accessor, scopes, index)
|
||||
assert result[0].virtual == "/readme.md"
|
||||
assert len(result) == 3
|
||||
@@ -0,0 +1,120 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.mkdir import mkdir
|
||||
from mirage.core.redis.mkdir_p import mkdir_p
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def mk_store():
|
||||
stores = []
|
||||
|
||||
async def _make(prefix):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix=prefix)
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
stores.append(s)
|
||||
return RedisAccessor(s)
|
||||
|
||||
yield _make
|
||||
for s in stores:
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir(mk_store):
|
||||
a = await mk_store("test:mkdir:1:")
|
||||
await mkdir(
|
||||
a,
|
||||
PathSpec(resource_path="newdir",
|
||||
virtual="/newdir",
|
||||
directory="/newdir"))
|
||||
assert await a.store.has_dir("/newdir")
|
||||
assert await a.store.get_modified("/newdir") is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_parent_not_found(mk_store):
|
||||
a = await mk_store("test:mkdir:2:")
|
||||
with pytest.raises(
|
||||
FileNotFoundError,
|
||||
match="parent directory does not exist",
|
||||
):
|
||||
await mkdir(
|
||||
a,
|
||||
PathSpec(resource_path="no/parent",
|
||||
virtual="/no/parent",
|
||||
directory="/no/parent"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_already_exists(mk_store):
|
||||
a = await mk_store("test:mkdir:3:")
|
||||
await mkdir(
|
||||
a, PathSpec(resource_path="dir", virtual="/dir", directory="/dir"))
|
||||
await mkdir(
|
||||
a, PathSpec(resource_path="dir", virtual="/dir", directory="/dir"))
|
||||
assert await a.store.has_dir("/dir")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_with_parents(mk_store):
|
||||
a = await mk_store("test:mkdir:4:")
|
||||
await mkdir(a,
|
||||
PathSpec(resource_path="a/b/c",
|
||||
virtual="/a/b/c",
|
||||
directory="/a/b/c"),
|
||||
parents=True)
|
||||
assert await a.store.has_dir("/a")
|
||||
assert await a.store.has_dir("/a/b")
|
||||
assert await a.store.has_dir("/a/b/c")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_p(mk_store):
|
||||
a = await mk_store("test:mkdir:5:")
|
||||
await mkdir_p(a, "/x/y/z")
|
||||
assert await a.store.has_dir("/x")
|
||||
assert await a.store.has_dir("/x/y")
|
||||
assert await a.store.has_dir("/x/y/z")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_p_existing_parent(mk_store):
|
||||
a = await mk_store("test:mkdir:6:")
|
||||
await a.store.add_dir("/existing")
|
||||
await mkdir_p(a, "/existing/child/grandchild")
|
||||
assert await a.store.has_dir("/existing/child")
|
||||
assert await a.store.has_dir("/existing/child/grandchild")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mkdir_p_does_not_overwrite_modified(mk_store):
|
||||
a = await mk_store("test:mkdir:7:")
|
||||
await mkdir_p(a, "/a")
|
||||
original = await a.store.get_modified("/a")
|
||||
await mkdir_p(a, "/a/b")
|
||||
assert await a.store.get_modified("/a") == original
|
||||
@@ -0,0 +1,115 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.read import read_bytes
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:read:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/sub")
|
||||
await s.set_file("/hello.txt", b"hello world")
|
||||
await s.set_file("/sub/nested.txt", b"nested")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes(accessor):
|
||||
result = await read_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="hello.txt",
|
||||
virtual="/hello.txt",
|
||||
directory="/hello.txt"))
|
||||
assert result == b"hello world"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes_nested(accessor):
|
||||
result = await read_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub/nested.txt",
|
||||
virtual="/sub/nested.txt",
|
||||
directory="/sub/nested.txt"))
|
||||
assert result == b"nested"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes_not_found(accessor):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await read_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="nope.txt",
|
||||
virtual="/nope.txt",
|
||||
directory="/nope.txt"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes_empty_file():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:read:e:")
|
||||
await s.clear()
|
||||
await s.set_file("/empty", b"")
|
||||
a = RedisAccessor(s)
|
||||
result = await read_bytes(
|
||||
a, PathSpec(resource_path="empty",
|
||||
virtual="/empty",
|
||||
directory="/empty"))
|
||||
assert result == b""
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes_binary_data():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:read:b:")
|
||||
await s.clear()
|
||||
data = bytes(range(256))
|
||||
await s.set_file("/bin", data)
|
||||
a = RedisAccessor(s)
|
||||
result = await read_bytes(
|
||||
a, PathSpec(resource_path="bin", virtual="/bin", directory="/bin"))
|
||||
assert result == data
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_bytes_normalizes_path():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:read:n:")
|
||||
await s.clear()
|
||||
await s.set_file("/file.txt", b"data")
|
||||
a = RedisAccessor(s)
|
||||
result = await read_bytes(
|
||||
a,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="file.txt",
|
||||
directory="file.txt"))
|
||||
assert result == b"data"
|
||||
await s.clear()
|
||||
await s.close()
|
||||
@@ -0,0 +1,115 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.cache.index import RAMIndexCacheStore
|
||||
from mirage.core.redis.readdir import readdir
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:readdir:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/sub")
|
||||
await s.add_dir("/sub/deep")
|
||||
await s.set_file("/a.txt", b"a")
|
||||
await s.set_file("/b.txt", b"b")
|
||||
await s.set_file("/sub/c.txt", b"c")
|
||||
await s.set_file("/sub/d.txt", b"d")
|
||||
await s.set_file("/sub/deep/e.txt", b"e")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index():
|
||||
return RAMIndexCacheStore(ttl=600)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_root(accessor, index):
|
||||
entries = await readdir(
|
||||
accessor, PathSpec(resource_path="", virtual="/", directory="/"),
|
||||
index)
|
||||
assert "/a.txt" in entries
|
||||
assert "/b.txt" in entries
|
||||
assert "/sub" in entries
|
||||
assert len(entries) == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_subdir(accessor, index):
|
||||
entries = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub", virtual="/sub", directory="/sub"), index)
|
||||
assert "/sub/c.txt" in entries
|
||||
assert "/sub/d.txt" in entries
|
||||
assert "/sub/deep" in entries
|
||||
assert len(entries) == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_empty_dir(index):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:readdir:e:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/empty")
|
||||
a = RedisAccessor(s)
|
||||
entries = await readdir(
|
||||
a, PathSpec(resource_path="empty",
|
||||
virtual="/empty",
|
||||
directory="/empty"), index)
|
||||
assert entries == []
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_not_found(index):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:readdir:n:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await readdir(
|
||||
a,
|
||||
PathSpec(resource_path="nonexistent",
|
||||
virtual="/nonexistent",
|
||||
directory="/nonexistent"), index)
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readdir_deep(accessor, index):
|
||||
entries = await readdir(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub/deep",
|
||||
virtual="/sub/deep",
|
||||
directory="/sub/deep"), index)
|
||||
assert "/sub/deep/e.txt" in entries
|
||||
assert len(entries) == 1
|
||||
@@ -0,0 +1,100 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.stat import stat
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import FileType, PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:stat:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/sub")
|
||||
await s.set_file("/hello.txt", b"hello world")
|
||||
await s.set_file("/data.json", b'{"key": "value"}')
|
||||
await s.set_file("/img.png", b"\x89PNG")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_root(accessor):
|
||||
result = await stat(accessor,
|
||||
PathSpec(resource_path="", virtual="/", directory="/"))
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.name == "/"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_file(accessor):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="hello.txt",
|
||||
virtual="/hello.txt",
|
||||
directory="/hello.txt"))
|
||||
assert result.name == "hello.txt"
|
||||
assert result.size == 11
|
||||
assert result.type == FileType.TEXT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_directory(accessor):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub", virtual="/sub", directory="/sub"))
|
||||
assert result.type == FileType.DIRECTORY
|
||||
assert result.name == "sub"
|
||||
assert result.size is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_not_found(accessor):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="nope", virtual="/nope", directory="/nope"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_json_file(accessor):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="data.json",
|
||||
virtual="/data.json",
|
||||
directory="/data.json"))
|
||||
assert result.type == FileType.JSON
|
||||
assert result.size == 16
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stat_image_file(accessor):
|
||||
result = await stat(
|
||||
accessor,
|
||||
PathSpec(resource_path="img.png",
|
||||
virtual="/img.png",
|
||||
directory="/img.png"))
|
||||
assert result.type == FileType.IMAGE_PNG
|
||||
@@ -0,0 +1,80 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.stream import stream
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def mk_store():
|
||||
stores = []
|
||||
|
||||
async def _make(prefix, files=None):
|
||||
s = RedisStore(url=REDIS_URL, key_prefix=prefix)
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
if files:
|
||||
for path, data in files.items():
|
||||
await s.set_file(path, data)
|
||||
stores.append(s)
|
||||
return RedisAccessor(s)
|
||||
|
||||
yield _make
|
||||
for s in stores:
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_reads_content(mk_store):
|
||||
a = await mk_store("test:stream:1:", {"/file.txt": b"hello world"})
|
||||
chunks = []
|
||||
async for chunk in stream(a, "/file.txt"):
|
||||
chunks.append(chunk)
|
||||
assert b"".join(chunks) == b"hello world"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_single_chunk(mk_store):
|
||||
a = await mk_store("test:stream:2:", {"/file.txt": b"data"})
|
||||
chunks = []
|
||||
async for chunk in stream(a, "/file.txt"):
|
||||
chunks.append(chunk)
|
||||
assert len(chunks) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_not_found(mk_store):
|
||||
a = await mk_store("test:stream:3:")
|
||||
with pytest.raises(FileNotFoundError):
|
||||
async for _ in stream(a, "/nope.txt"):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_empty_file(mk_store):
|
||||
a = await mk_store("test:stream:4:", {"/empty": b""})
|
||||
chunks = []
|
||||
async for chunk in stream(a, "/empty"):
|
||||
chunks.append(chunk)
|
||||
assert b"".join(chunks) == b""
|
||||
@@ -0,0 +1,132 @@
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from mirage.accessor.redis import RedisAccessor
|
||||
from mirage.core.redis.write import write_bytes
|
||||
from mirage.resource.redis.store import RedisStore
|
||||
from mirage.types import PathSpec
|
||||
|
||||
REDIS_URL = os.environ.get("REDIS_URL", "")
|
||||
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def accessor():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:write:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
await s.add_dir("/sub")
|
||||
a = RedisAccessor(s)
|
||||
yield a
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes(accessor):
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="hello.txt",
|
||||
virtual="/hello.txt",
|
||||
directory="/hello.txt"), b"hello")
|
||||
assert await accessor.store.get_file("/hello.txt") == b"hello"
|
||||
assert await accessor.store.get_modified("/hello.txt") is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_overwrite(accessor):
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"), b"first")
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"), b"second")
|
||||
assert await accessor.store.get_file("/file.txt") == b"second"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_parent_not_found():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:write:p:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
with pytest.raises(
|
||||
FileNotFoundError,
|
||||
match="parent directory does not exist",
|
||||
):
|
||||
await write_bytes(
|
||||
a,
|
||||
PathSpec(resource_path="no/parent/file.txt",
|
||||
virtual="/no/parent/file.txt",
|
||||
directory="/no/parent/file.txt"), b"data")
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_to_subdir(accessor):
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="sub/file.txt",
|
||||
virtual="/sub/file.txt",
|
||||
directory="/sub/file.txt"), b"nested data")
|
||||
assert await accessor.store.get_file("/sub/file.txt") == b"nested data"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_root_parent():
|
||||
s = RedisStore(url=REDIS_URL, key_prefix="test:write:r:")
|
||||
await s.clear()
|
||||
await s.add_dir("/")
|
||||
a = RedisAccessor(s)
|
||||
await write_bytes(
|
||||
a,
|
||||
PathSpec(resource_path="root_file.txt",
|
||||
virtual="/root_file.txt",
|
||||
directory="/root_file.txt"), b"root")
|
||||
assert await s.get_file("/root_file.txt") == b"root"
|
||||
await s.clear()
|
||||
await s.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_sets_modified(accessor):
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"), b"data")
|
||||
assert await accessor.store.get_modified("/file.txt") is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_bytes_modified_uses_z_suffix(accessor):
|
||||
await write_bytes(
|
||||
accessor,
|
||||
PathSpec(resource_path="file.txt",
|
||||
virtual="/file.txt",
|
||||
directory="/file.txt"), b"data")
|
||||
modified = await accessor.store.get_modified("/file.txt")
|
||||
assert modified is not None
|
||||
assert modified.endswith("Z")
|
||||
assert "+00:00" not in modified
|
||||
Reference in New Issue
Block a user