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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:44 +08:00
commit bcbd1bdb22
5748 changed files with 562488 additions and 0 deletions
@@ -0,0 +1,31 @@
from mirage.resource.registry import REGISTRY, build_resource
from mirage.types import ResourceName
def test_chroma_resource_is_registered():
assert ResourceName.CHROMA == "chroma"
assert REGISTRY[
"chroma"].resource_path == "mirage.resource.chroma:ChromaResource"
assert REGISTRY[
"chroma"].config_path == "mirage.resource.chroma:ChromaConfig"
resource = build_resource("chroma", {"collection_name": "docs"})
assert resource.name == ResourceName.CHROMA
assert resource.caches_reads is False
assert resource.SUPPORTS_SNAPSHOT is False
assert resource.config.collection_name == "docs"
assert resource.config.slug_field == "page_slug"
assert resource.accessor.config is resource.config
def test_chroma_resource_registers_expected_commands_and_ops():
resource = build_resource("chroma", {"collection_name": "docs"})
commands = {item.name for item in resource.commands()}
ops = {item.name for item in resource.ops_list()}
assert {
"cat", "ls", "grep", "find", "head", "tail", "tree", "chroma-query"
}.issubset(commands)
assert {"read", "readdir", "stat", "grep", "search"}.issubset(ops)
@@ -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,99 @@
from mirage.accessor import databricks_volume as accessor_module
from mirage.accessor.databricks_volume import DatabricksVolumeAccessor
from mirage.resource.databricks_volume import DatabricksVolumeConfig
class FakeWorkspaceClient:
calls: list[dict] = []
def __init__(self, **kwargs) -> None:
self.calls.append(kwargs)
self.files = object()
class FakeWorkspaceConfig:
def __init__(self, **kwargs) -> None:
for key, value in kwargs.items():
setattr(self, key, value)
def test_accessor_passes_timeout_to_workspace_client(monkeypatch):
FakeWorkspaceClient.calls = []
monkeypatch.setattr(
accessor_module,
"WorkspaceClient",
FakeWorkspaceClient,
)
monkeypatch.setattr(
accessor_module,
"WorkspaceConfig",
FakeWorkspaceConfig,
)
config = DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
host="https://example.cloud.databricks.com",
token="secret",
timeout=17,
)
accessor = DatabricksVolumeAccessor(config)
assert accessor.files is not None
sdk_config = FakeWorkspaceClient.calls[0]["config"]
assert sdk_config.host == "https://example.cloud.databricks.com"
assert sdk_config.token == "secret"
assert sdk_config.http_timeout_seconds == 17
def test_accessor_sets_pat_auth_type_for_explicit_token(monkeypatch):
FakeWorkspaceClient.calls = []
monkeypatch.setattr(
accessor_module,
"WorkspaceClient",
FakeWorkspaceClient,
)
monkeypatch.setattr(
accessor_module,
"WorkspaceConfig",
FakeWorkspaceConfig,
)
config = DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
host="https://example.cloud.databricks.com",
token="request-token",
)
accessor = DatabricksVolumeAccessor(config)
assert accessor.files is not None
sdk_config = FakeWorkspaceClient.calls[0]["config"]
assert sdk_config.auth_type == "pat"
assert sdk_config.token == "request-token"
def test_accessor_leaves_auth_type_unset_for_profile(monkeypatch):
FakeWorkspaceClient.calls = []
monkeypatch.setattr(
accessor_module,
"WorkspaceClient",
FakeWorkspaceClient,
)
monkeypatch.setattr(
accessor_module,
"WorkspaceConfig",
FakeWorkspaceConfig,
)
config = DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
profile="default",
)
accessor = DatabricksVolumeAccessor(config)
assert accessor.files is not None
sdk_config = FakeWorkspaceClient.calls[0]["config"]
assert not hasattr(sdk_config, "auth_type")
assert sdk_config.profile == "default"
@@ -0,0 +1,725 @@
# ========= 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 posixpath
from io import BytesIO
from types import SimpleNamespace
from urllib.parse import unquote
import pytest
from pydantic import ValidationError
from mirage import MountMode, Workspace
from mirage.cache.index import IndexEntry, LookupStatus
from mirage.core.databricks_volume.path import backend_path
from mirage.resource.databricks_volume import (DatabricksVolumeConfig,
DatabricksVolumeResource)
from mirage.types import PathSpec, ResourceName
from mirage.utils.key_prefix import mount_key
class NotFoundError(Exception):
status_code = 404
class FakeDownload:
def __init__(self, data: bytes) -> None:
self.contents = BytesIO(data)
class FakeFiles:
def __init__(self) -> None:
self.downloads: dict[str, bytes] = {}
self.metadata: dict[str, object] = {}
self.directory_metadata: set[str] = set()
self.directories: dict[str, list[object]] = {}
self.upload_calls: list[tuple[str, bytes, bool]] = []
self.delete_calls: list[str] = []
self.create_directory_calls: list[str] = []
self.delete_directory_calls: list[str] = []
def download(self, path: str) -> FakeDownload:
if path not in self.downloads:
raise NotFoundError(path)
return FakeDownload(self.downloads[path])
def get_metadata(self, path: str) -> object:
if path not in self.metadata:
raise NotFoundError(path)
return self.metadata[path]
def get_directory_metadata(self, path: str) -> None:
if path not in self.directory_metadata:
raise NotFoundError(path)
def list_directory_contents(self, path: str) -> list[object]:
if path not in self.directories:
raise NotFoundError(path)
return self.directories[path]
def create_directory(self, path: str) -> None:
self.create_directory_calls.append(path)
cur = ""
for part in path.strip("/").split("/"):
cur = cur + "/" + part
if cur in self.directory_metadata:
continue
self.directory_metadata.add(cur)
self.metadata[cur] = SimpleNamespace(is_directory=True)
self.directories.setdefault(cur, [])
parent = posixpath.dirname(cur) or "/"
self._upsert_directory_entry(
parent, SimpleNamespace(path=cur, is_directory=True))
def delete_directory(self, path: str) -> None:
self.delete_directory_calls.append(path)
if path not in self.directory_metadata:
raise NotFoundError(path)
if self.directories.get(path):
raise OSError(f"directory not empty: {path}")
self.directory_metadata.discard(path)
self.metadata.pop(path, None)
self.directories.pop(path, None)
parent = posixpath.dirname(path.rstrip("/")) or "/"
self.directories[parent] = [
entry for entry in self.directories.get(parent, [])
if getattr(entry, "path", None) != path
]
def upload(self, path: str, contents, overwrite: bool = False) -> None:
data = contents.read()
self.upload_calls.append((path, data, overwrite))
parent = posixpath.dirname(path.rstrip("/")) or "/"
if parent not in self.directory_metadata:
if parent in self.metadata:
raise NotADirectoryError(parent)
raise NotFoundError(parent)
if path in self.directory_metadata:
raise IsADirectoryError(path)
self.downloads[path] = data
self.metadata[path] = SimpleNamespace(
content_length=len(data),
content_type=None,
last_modified=None,
)
self._upsert_directory_entry(
parent,
SimpleNamespace(
path=path,
is_directory=False,
file_size=len(data),
),
)
def delete(self, path: str) -> None:
self.delete_calls.append(path)
if path in self.directory_metadata:
raise IsADirectoryError(path)
if path not in self.metadata and path not in self.downloads:
raise NotFoundError(path)
self.metadata.pop(path, None)
self.downloads.pop(path, None)
parent = posixpath.dirname(path.rstrip("/")) or "/"
self.directories[parent] = [
entry for entry in self.directories.get(parent, [])
if getattr(entry, "path", None) != path
]
def _upsert_directory_entry(self, parent: str, entry: object) -> None:
entries = [
existing for existing in self.directories.get(parent, [])
if getattr(existing, "path", None) != getattr(entry, "path", None)
]
entries.append(entry)
self.directories[parent] = sorted(
entries, key=lambda item: getattr(item, "path", ""))
def _apply_range_header(data: bytes, range_header: str) -> bytes:
if not range_header.startswith("bytes="):
raise ValueError(f"unsupported range header: {range_header}")
start_text, end_text = range_header.removeprefix("bytes=").split("-", 1)
start = int(start_text) if start_text else 0
end = int(end_text) + 1 if end_text else None
return data[start:end]
class FakeApiClient:
def __init__(self, files: FakeFiles) -> None:
self.files = files
def do(
self,
method: str,
path: str | None = None,
url: str | None = None,
query: dict | None = None,
headers: dict | None = None,
body: dict | None = None,
raw: bool = False,
files: object = None,
data: object = None,
auth: object = None,
response_headers: list[str] | None = None,
) -> dict:
if method != "GET" or path is None:
raise ValueError(f"unsupported fake API call: {method} {path}")
remote_path = unquote(path.removeprefix("/api/2.0/fs/files"))
if remote_path not in self.files.downloads:
raise NotFoundError(remote_path)
payload = self.files.downloads[remote_path]
range_header = (headers or {}).get("Range")
if range_header is not None:
payload = _apply_range_header(payload, range_header)
return {
"contents": BytesIO(payload),
"content-length": str(len(payload)),
"accept-ranges": "bytes",
}
class FakeClient:
def __init__(self, files: FakeFiles) -> None:
self.files = files
self.api_client = FakeApiClient(files)
def make_resource(files: FakeFiles) -> DatabricksVolumeResource:
return DatabricksVolumeResource(
DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
root_path="/root",
token="secret",
),
client=FakeClient(files),
)
def seed_directory(files: FakeFiles, path: str) -> None:
files.directory_metadata.add(path)
files.metadata[path] = SimpleNamespace(is_directory=True)
files.directories.setdefault(path, [])
def seed_file(files: FakeFiles, path: str, data: bytes) -> None:
parent = path.rsplit("/", 1)[0]
files.downloads[path] = data
files.metadata[path] = SimpleNamespace(
content_length=len(data),
content_type=None,
last_modified=None,
)
files.directories.setdefault(parent, [])
files.directories[parent].append(
SimpleNamespace(
path=path,
is_directory=False,
file_size=len(data),
))
def test_config_validation_and_normalization():
config = DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
root_path="nested/path",
)
assert config.root_path == "/nested/path"
with pytest.raises(ValidationError):
DatabricksVolumeConfig(
catalog="main/other",
schema="default",
volume="agent_files",
)
def test_backend_path_uses_volume_root_and_strips_mount_prefix():
config = DatabricksVolumeConfig(
catalog="main",
schema="default",
volume="agent_files",
root_path="/root",
)
path = PathSpec(
resource_path=mount_key("/volume/reports/latest.md", "/volume"),
virtual="/volume/reports/latest.md",
directory="/volume/reports",
)
assert backend_path(
config,
path) == ("/Volumes/main/default/agent_files/root/reports/latest.md")
def test_resource_state_redacts_token():
resource = make_resource(FakeFiles())
state = resource.get_state()
assert state["type"] == ResourceName.DATABRICKS_VOLUME
assert state["needs_override"] is True
assert state["config"]["token"] == "<REDACTED>"
assert state["config"]["host"] is None
assert state["config"]["catalog"] == "main"
assert "token" in state["redacted_fields"]
def test_resource_registers_ops():
resource = make_resource(FakeFiles())
op_names = {op.name for op in resource.ops_list()}
assert {"read", "readdir", "stat", "write", "create", "unlink"} <= op_names
assert resource.name == "databricks_volume"
assert resource.caches_reads is True
def test_resource_registers_commands():
resource = make_resource(FakeFiles())
command_names = {command.name for command in resource.commands()}
assert {
"cat",
"find",
"grep",
"head",
"ls",
"rm",
"rg",
"stat",
"tail",
"touch",
"tree",
} <= command_names
@pytest.mark.asyncio
async def test_read_stat_readdir_range_stream_and_exists():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.downloads[f"{root}/reports/latest.md"] = b"abcdef"
files.metadata[f"{root}/reports/latest.md"] = SimpleNamespace(
content_length=6,
content_type=None,
last_modified="Tue, 14 Nov 2023 22:13:20 GMT",
)
files.metadata[f"{root}/reports"] = SimpleNamespace(is_directory=True)
files.directories[f"{root}/reports"] = [
SimpleNamespace(
path=f"{root}/reports/latest.md",
is_directory=False,
file_size=6,
)
]
resource = make_resource(files)
assert await resource.read_bytes(
PathSpec.from_str_path(
"/volume/reports/latest.md",
mount_key("/volume/reports/latest.md", "/volume"))) == b"abcdef"
assert await resource.range_read(
PathSpec.from_str_path(
"/volume/reports/latest.md",
mount_key("/volume/reports/latest.md", "/volume")), 1, 4) == b"bcd"
chunks = [
chunk async for chunk in resource.read_stream(
PathSpec.from_str_path(
"/volume/reports/latest.md",
mount_key("/volume/reports/latest.md", "/volume")),
chunk_size=2,
)
]
assert chunks == [b"ab", b"cd", b"ef"]
file_stat = await resource.stat(
PathSpec.from_str_path(
"/volume/reports/latest.md",
mount_key("/volume/reports/latest.md", "/volume")))
assert file_stat.name == "latest.md"
assert file_stat.size == 6
assert file_stat.modified == "2023-11-14T22:13:20+00:00"
assert await resource.exists(
PathSpec.from_str_path(
"/volume/reports/latest.md",
mount_key("/volume/reports/latest.md", "/volume")))
assert not await resource.exists(
PathSpec.from_str_path("/volume/missing.md",
mount_key("/volume/missing.md", "/volume")))
entries = await resource.readdir(
PathSpec.from_str_path("/volume/reports",
mount_key("/volume/reports", "/volume")),
resource.index,
)
assert entries == ["/volume/reports/latest.md"]
@pytest.mark.asyncio
async def test_workspace_read_mode_uses_registered_ops():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.downloads[f"{root}/latest.md"] = b"hello"
files.metadata[f"{root}/latest.md"] = SimpleNamespace(
content_length=5,
content_type=None,
last_modified=None,
)
ws = Workspace({"/volume": make_resource(files)}, mode=MountMode.READ)
assert await ws.ops.read("/volume/latest.md") == b"hello"
file_stat = await ws.ops.stat("/volume/latest.md")
assert file_stat.size == 5
@pytest.mark.asyncio
async def test_resource_exposes_file_write_ops():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
resource = make_resource(files)
await resource.write(
PathSpec.from_str_path("/volume/new.txt",
mount_key("/volume/new.txt", "/volume")),
b"hello",
resource.index,
)
await resource.create(
PathSpec.from_str_path("/volume/empty.txt",
mount_key("/volume/empty.txt", "/volume")),
resource.index,
)
await resource.unlink(
PathSpec.from_str_path("/volume/new.txt",
mount_key("/volume/new.txt", "/volume")),
resource.index,
)
assert files.downloads[f"{root}/empty.txt"] == b""
assert f"{root}/new.txt" not in files.downloads
@pytest.mark.asyncio
async def test_workspace_write_mode_uses_file_write_ops():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.WRITE)
await ws.ops.write("/dbx/new.txt", b"hello")
await ws.ops.create("/dbx/empty.txt")
await ws.ops.unlink("/dbx/new.txt")
assert f"{root}/new.txt" not in files.downloads
assert files.downloads[f"{root}/empty.txt"] == b""
@pytest.mark.asyncio
async def test_workspace_write_mode_invalidates_parent_directory_index():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
resource = make_resource(files)
ws = Workspace({"/dbx/": resource}, mode=MountMode.WRITE)
await resource.index.set_dir("/dbx", [("old.txt",
IndexEntry(
id="/dbx/old.txt",
name="old.txt",
resource_type="file",
))])
assert (await resource.index.list_dir("/dbx")).entries == ["/dbx/old.txt"]
await ws.ops.write("/dbx/new.txt", b"hello")
assert (await
resource.index.list_dir("/dbx")).status == (LookupStatus.NOT_FOUND)
await resource.index.set_dir("/dbx", [("new.txt",
IndexEntry(
id="/dbx/new.txt",
name="new.txt",
resource_type="file",
))])
await ws.ops.create("/dbx/empty.txt")
assert (await
resource.index.list_dir("/dbx")).status == (LookupStatus.NOT_FOUND)
await resource.index.set_dir("/dbx", [("empty.txt",
IndexEntry(
id="/dbx/empty.txt",
name="empty.txt",
resource_type="file",
))])
await ws.ops.unlink("/dbx/empty.txt")
assert (await
resource.index.list_dir("/dbx")).status == (LookupStatus.NOT_FOUND)
@pytest.mark.asyncio
async def test_read_only_mount_rejects_file_write_ops():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
with pytest.raises(PermissionError):
await ws.ops.write("/dbx/new.txt", b"hello")
with pytest.raises(PermissionError):
await ws.ops.create("/dbx/empty.txt")
with pytest.raises(PermissionError):
await ws.ops.unlink("/dbx/new.txt")
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_touch_and_rm():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.WRITE)
touch_io = await ws.execute("touch /dbx/created.txt")
rm_io = await ws.execute("rm /dbx/created.txt")
assert touch_io.exit_code == 0
assert touch_io.writes == {"/dbx/created.txt": b""}
assert files.delete_calls == [f"{root}/created.txt"]
assert f"{root}/created.txt" not in files.downloads
assert rm_io.exit_code == 0
assert rm_io.writes == {"/dbx/created.txt": b""}
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_rm_resolves_glob():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
seed_file(files, f"{root}/one.txt", b"one")
seed_file(files, f"{root}/two.txt", b"two")
seed_file(files, f"{root}/keep.md", b"keep")
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.WRITE)
io = await ws.execute("rm /dbx/*.txt")
assert io.exit_code == 0
assert files.delete_calls == [f"{root}/one.txt", f"{root}/two.txt"]
assert f"{root}/one.txt" not in files.downloads
assert f"{root}/two.txt" not in files.downloads
assert files.downloads[f"{root}/keep.md"] == b"keep"
assert io.writes == {
"/dbx/one.txt": b"",
"/dbx/two.txt": b"",
}
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_touch_resolves_glob():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
seed_file(files, f"{root}/existing.txt", b"existing")
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.WRITE)
io = await ws.execute("touch /dbx/*.txt")
assert io.exit_code == 0
assert files.upload_calls == []
assert f"{root}/*.txt" not in files.downloads
assert io.writes == {}
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_rm_rejects_directory():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
seed_directory(files, f"{root}/dir")
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.WRITE)
io = await ws.execute("rm /dbx/dir")
assert io.exit_code == 1
assert b"IsADirectoryError" in io.stderr or b"Is a directory" in io.stderr
@pytest.mark.asyncio
async def test_read_only_mount_rejects_file_write_commands():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
seed_directory(files, root)
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
touch_io = await ws.execute("touch /dbx/created.txt")
rm_io = await ws.execute("rm /dbx/created.txt")
assert touch_io.exit_code == 1
assert b"read-only mount" in touch_io.stderr
assert rm_io.exit_code == 1
assert b"read-only mount" in rm_io.stderr
@pytest.mark.asyncio
async def test_workspace_execute_uses_databricks_volume_mount_for_ls():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.directory_metadata.add(root)
files.metadata[f"{root}/debug_output.json"] = SimpleNamespace(
content_length=18,
content_type=None,
last_modified=None,
)
files.directories[root] = [
SimpleNamespace(
path=f"{root}/debug_output.json",
is_directory=False,
file_size=18,
)
]
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
io = await ws.execute("ls /dbx")
slash_io = await ws.execute("ls /dbx/")
assert io.exit_code == 0
assert b"debug_output.json" in io.stdout
assert slash_io.exit_code == 0
assert b"debug_output.json" in slash_io.stdout
mount = await ws._registry.resolve_mount(
"ls",
[PathSpec.from_str_path("/dbx", mount_key("/dbx", "/dbx"))],
"/",
)
assert mount is not None
assert mount.prefix == "/dbx/"
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_stat_and_cat():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.downloads[f"{root}/debug_output.json"] = b'{"ok": true}\nsecond\n'
files.metadata[f"{root}/debug_output.json"] = SimpleNamespace(
content_length=20,
content_type=None,
last_modified=None,
)
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
stat_io = await ws.execute("stat /dbx/debug_output.json")
cat_io = await ws.execute("cat /dbx/debug_output.json")
head_io = await ws.execute("head -n 1 /dbx/debug_output.json")
assert stat_io.exit_code == 0
assert b"name=debug_output.json" in stat_io.stdout
assert cat_io.exit_code == 0
assert b'{"ok": true}' in cat_io.stdout
assert head_io.exit_code == 0
assert head_io.stdout == b'{"ok": true}\n'
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_find_files():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.directory_metadata.update({root, f"{root}/nested"})
files.metadata[f"{root}/nested"] = SimpleNamespace(is_directory=True)
files.metadata[f"{root}/debug_output.json"] = SimpleNamespace(
content_length=2,
content_type=None,
last_modified=None,
)
files.metadata[f"{root}/nested/result.txt"] = SimpleNamespace(
content_length=2,
content_type=None,
last_modified=None,
)
files.directories[root] = [
SimpleNamespace(
path=f"{root}/debug_output.json",
is_directory=False,
file_size=2,
),
SimpleNamespace(path=f"{root}/nested", is_directory=True),
]
files.directories[f"{root}/nested"] = [
SimpleNamespace(
path=f"{root}/nested/result.txt",
is_directory=False,
file_size=2,
)
]
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
io = await ws.execute("find /dbx -maxdepth 2 -type f")
assert io.exit_code == 0
assert b"/dbx/debug_output.json" in io.stdout
assert b"/dbx/nested/result.txt" in io.stdout
@pytest.mark.asyncio
async def test_workspace_execute_databricks_volume_recursive_grep_and_rg():
files = FakeFiles()
root = "/Volumes/main/default/agent_files/root"
files.directory_metadata.update({
root,
f"{root}/nested",
f"{root}/nested/deeper",
})
files.metadata[f"{root}/nested"] = SimpleNamespace(is_directory=True)
files.metadata[f"{root}/nested/deeper"] = SimpleNamespace(
is_directory=True)
files.metadata[f"{root}/nested/info.txt"] = SimpleNamespace(
content_length=17,
content_type=None,
last_modified=None,
)
files.metadata[f"{root}/nested/deeper/notes.md"] = SimpleNamespace(
content_length=26,
content_type=None,
last_modified=None,
)
files.downloads[f"{root}/nested/info.txt"] = b"alpha debug line\n"
files.downloads[f"{root}/nested/deeper/notes.md"] = (
b"# Notes\nbeta debug detail\n")
files.directories[root] = [
SimpleNamespace(path=f"{root}/nested", is_directory=True),
]
files.directories[f"{root}/nested"] = [
SimpleNamespace(
path=f"{root}/nested/info.txt",
is_directory=False,
file_size=17,
),
SimpleNamespace(path=f"{root}/nested/deeper", is_directory=True),
]
files.directories[f"{root}/nested/deeper"] = [
SimpleNamespace(
path=f"{root}/nested/deeper/notes.md",
is_directory=False,
file_size=26,
)
]
ws = Workspace({"/dbx/": make_resource(files)}, mode=MountMode.READ)
grep_io = await ws.execute("grep -R -n debug /dbx/nested")
rg_io = await ws.execute("rg debug /dbx/nested")
assert grep_io.exit_code == 0
assert b"/dbx/nested/info.txt:1:alpha debug line" in grep_io.stdout
assert b"/dbx/nested/deeper/notes.md:2:beta debug detail" in (
grep_io.stdout)
assert not grep_io.stderr
assert rg_io.exit_code == 0
assert b"/dbx/nested/info.txt:alpha debug line" in rg_io.stdout
assert b"/dbx/nested/deeper/notes.md:beta debug detail" in rg_io.stdout
assert not rg_io.stderr
@@ -0,0 +1,65 @@
from mirage.resource.registry import REGISTRY, build_resource
from mirage.types import ResourceName
def test_dify_resource_is_registered_and_redacts_api_key():
assert ResourceName.DIFY == "dify"
assert REGISTRY[
"dify"].resource_path == "mirage.resource.dify:DifyResource"
assert REGISTRY["dify"].config_path == "mirage.resource.dify:DifyConfig"
resource = build_resource(
"dify",
{
"api_key": "dataset-secret",
"base_url": "https://api.dify.ai/v1/",
"dataset_id": "dataset-1",
},
)
assert resource.name == ResourceName.DIFY
assert resource.caches_reads is True
assert resource.SUPPORTS_SNAPSHOT is False
assert resource.config.base_url == "https://api.dify.ai/v1"
assert resource.config.slug_metadata_name == "slug"
assert resource.accessor.config is resource.config
state = resource.get_state()
assert state["type"] == ResourceName.DIFY
assert state["needs_override"] is True
assert state["redacted_fields"] == ["api_key"]
assert state["config"]["api_key"] == "<REDACTED>"
assert state["config"]["dataset_id"] == "dataset-1"
assert state["config"]["slug_metadata_name"] == "slug"
def test_dify_resource_accepts_configured_slug_metadata_name():
resource = build_resource(
"dify",
{
"api_key": "dataset-secret",
"base_url": "https://api.dify.ai/v1",
"dataset_id": "dataset-1",
"slug_metadata_name": "path",
},
)
assert resource.config.slug_metadata_name == "path"
def test_dify_resource_registers_expected_commands_and_ops():
resource = build_resource(
"dify",
{
"api_key": "dataset-secret",
"base_url": "https://api.dify.ai/v1",
"dataset_id": "dataset-1",
},
)
commands = {item.name for item in resource.commands()}
ops = {item.name for item in resource.ops_list()}
assert {"cat", "ls", "grep", "find", "head", "tail",
"wc"}.issubset(commands)
assert {"read", "readdir", "stat", "grep"}.issubset(ops)
+13
View File
@@ -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,48 @@
# ========= 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 pytest
from mirage.resource.discord.config import DiscordConfig
from mirage.resource.discord.discord import DiscordResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return DiscordConfig(token="test-bot-token")
def test_resource_init(config):
resource = DiscordResource(config)
assert resource.caches_reads is True
def test_resource_name(config):
resource = DiscordResource(config)
assert resource.name == ResourceName.DISCORD
def test_resource_accessor(config):
resource = DiscordResource(config)
assert resource.accessor is not None
assert resource.accessor.config is config
assert resource.index is not None
def test_resource_commands(config):
resource = DiscordResource(config)
# 50 native (generic factory read set incl. find and sed + bespoke
# grep/rg/head + discord_* writers) + 9 filetype cmds x 7 columnar exts
assert len(resource._commands) == 50 + 9 * 7
+85
View File
@@ -0,0 +1,85 @@
# ========= 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 pytest
from mirage import MountMode, Workspace
from mirage.resource.disk.disk import DiskResource
@pytest.fixture
def local_backend(tmp_path):
return DiskResource(str(tmp_path))
@pytest.fixture
def ws(tmp_path):
resource = DiskResource(str(tmp_path))
return Workspace({"/data": resource}, mode=MountMode.WRITE)
@pytest.mark.asyncio
async def test_create_and_cat(ws):
await ws.execute('echo "hello" | tee /data/hello.txt')
result = await ws.execute("cat /data/hello.txt")
assert b"hello" in result.stdout
@pytest.mark.asyncio
async def test_mkdir_and_ls(ws):
await ws.execute("mkdir /data/mydir")
result = await ws.execute("ls /data/")
assert b"mydir" in result.stdout
@pytest.mark.asyncio
async def test_rm(ws):
await ws.execute('echo "x" | tee /data/del.txt')
await ws.execute("rm /data/del.txt")
result = await ws.execute("stat /data/del.txt")
assert result.exit_code != 0
@pytest.mark.asyncio
async def test_stat_file(ws):
await ws.execute('echo "hello" | tee /data/f.txt')
result = await ws.execute("stat /data/f.txt")
assert result.exit_code == 0
assert b"name=f.txt" in result.stdout
@pytest.mark.asyncio
async def test_stat_directory(ws):
await ws.execute("mkdir /data/mydir")
result = await ws.execute("stat /data/mydir")
assert result.exit_code == 0
assert b"directory" in result.stdout
@pytest.mark.asyncio
async def test_stat_missing_raises(ws):
result = await ws.execute("stat /data/missing.txt")
assert result.exit_code != 0
@pytest.mark.asyncio
async def test_path_traversal_raises(local_backend):
from mirage.core.disk.stat import stat as core_stat
from mirage.types import PathSpec # noqa: F811
with pytest.raises(ValueError):
await core_stat(
local_backend.accessor,
PathSpec(resource_path="../etc/passwd",
virtual="/../etc/passwd",
directory="/../etc/passwd"))
+152
View File
@@ -0,0 +1,152 @@
# ========= 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 asyncio
import hashlib
import pytest
from mirage.types import FileType
def _cat_sync(backend, path):
async def _collect():
return b"".join([c async for c in backend.read_stream(path)])
return asyncio.run(_collect())
def test_write_and_read(memory_backend):
asyncio.run(memory_backend.write("/f.txt", data=b"hello"))
assert _cat_sync(memory_backend, "/f.txt") == b"hello"
def test_create_and_write(memory_backend):
asyncio.run(memory_backend.write("/a.txt", data=b"data"))
assert _cat_sync(memory_backend, "/a.txt") == b"data"
def test_read_partial_with_offset(memory_backend):
asyncio.run(memory_backend.write("/f.txt", data=b"abcdef"))
assert _cat_sync(memory_backend, "/f.txt")[2:5] == b"cde"
def test_read_missing_raises(memory_backend):
with pytest.raises(FileNotFoundError):
_cat_sync(memory_backend, "/missing.txt")
def test_mkdir_and_readdir(memory_backend):
asyncio.run(memory_backend.mkdir("/mydir"))
entries = asyncio.run(
memory_backend.readdir("/mydir", memory_backend.index))
assert entries == []
def test_mkdir_missing_parent_raises(memory_backend):
with pytest.raises(FileNotFoundError):
asyncio.run(memory_backend.mkdir("/a/b/c"))
def test_rmdir_empty(memory_backend):
asyncio.run(memory_backend.mkdir("/emptydir"))
asyncio.run(memory_backend.rmdir("/emptydir"))
with pytest.raises(FileNotFoundError):
asyncio.run(memory_backend.stat("/emptydir"))
def test_rmdir_nonempty_raises(memory_backend):
asyncio.run(memory_backend.mkdir("/dir"))
asyncio.run(memory_backend.write("/dir/file.txt", data=b""))
with pytest.raises(OSError):
asyncio.run(memory_backend.rmdir("/dir"))
def test_unlink(memory_backend):
asyncio.run(memory_backend.write("/del.txt", data=b""))
asyncio.run(memory_backend.unlink("/del.txt"))
with pytest.raises(FileNotFoundError):
_cat_sync(memory_backend, "/del.txt")
def test_unlink_missing_raises(memory_backend):
with pytest.raises(FileNotFoundError):
asyncio.run(memory_backend.unlink("/nope.txt"))
def test_rename_file(memory_backend):
asyncio.run(memory_backend.write("/old.txt", data=b"content"))
asyncio.run(memory_backend.rename("/old.txt", "/new.txt"))
assert _cat_sync(memory_backend, "/new.txt") == b"content"
with pytest.raises(FileNotFoundError):
_cat_sync(memory_backend, "/old.txt")
def test_stat_file(memory_backend):
asyncio.run(memory_backend.write("/f.txt", data=b"hello"))
s = asyncio.run(memory_backend.stat("/f.txt"))
assert s.name == "f.txt"
assert s.size == 5
assert s.type == "text"
def test_stat_directory(memory_backend):
asyncio.run(memory_backend.mkdir("/mydir"))
s = asyncio.run(memory_backend.stat("/mydir"))
assert s.type == FileType.DIRECTORY
assert s.size is None
def test_stat_missing_raises(memory_backend):
with pytest.raises(FileNotFoundError):
asyncio.run(memory_backend.stat("/missing.txt"))
def test_exists_true(memory_backend):
asyncio.run(memory_backend.write("/f.txt", data=b""))
try:
asyncio.run(memory_backend.stat("/f.txt"))
exists = True
except FileNotFoundError:
exists = False
assert exists
def test_exists_false(memory_backend):
try:
asyncio.run(memory_backend.stat("/nope.txt"))
exists = True
except FileNotFoundError:
exists = False
assert not exists
def test_checksum_deterministic(memory_backend):
asyncio.run(memory_backend.write("/f.txt", data=b"data"))
raw = asyncio.run(memory_backend.read_bytes("/f.txt"))
c1 = hashlib.md5(raw).hexdigest()
c2 = hashlib.md5(raw).hexdigest()
assert c1 == c2
assert len(c1) == 32
def test_readdir_lists_direct_children(memory_backend):
asyncio.run(memory_backend.mkdir("/parent"))
asyncio.run(memory_backend.mkdir("/parent/child1"))
asyncio.run(memory_backend.write("/parent/file.txt", data=b""))
entries = asyncio.run(
memory_backend.readdir("/parent", memory_backend.index))
assert any("child1" in e for e in entries)
assert any("file.txt" in e for e in entries)
+13
View File
@@ -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,68 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.accessor.email import EmailAccessor
from mirage.resource.email.config import EmailConfig
from mirage.types import ResourceName
def test_email_resource_name_exists():
assert ResourceName.EMAIL == "email"
def test_config_creation():
config = EmailConfig(
imap_host="imap.fastmail.com",
smtp_host="smtp.fastmail.com",
username="test@fastmail.com",
password="test-password",
)
assert config.imap_host == "imap.fastmail.com"
assert config.imap_port == 993
assert config.smtp_port == 587
assert config.use_ssl is True
def test_config_requires_credentials():
with pytest.raises(ValidationError):
EmailConfig()
def test_config_custom_ports():
config = EmailConfig(
imap_host="imap.example.com",
imap_port=143,
smtp_host="smtp.example.com",
smtp_port=465,
username="user",
password="pass",
use_ssl=False,
)
assert config.imap_port == 143
assert config.smtp_port == 465
assert config.use_ssl is False
def test_accessor_creation():
config = EmailConfig(
imap_host="imap.fastmail.com",
smtp_host="smtp.fastmail.com",
username="test@fastmail.com",
password="test-password",
)
accessor = EmailAccessor(config)
assert accessor.config is config
@@ -0,0 +1,60 @@
# ========= 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 pytest
from mirage.resource.email.config import EmailConfig
from mirage.resource.email.email import EmailResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return EmailConfig(
imap_host="imap.test.com",
smtp_host="smtp.test.com",
username="user@test.com",
password="pass",
)
def test_resource_init(config):
resource = EmailResource(config=config)
assert resource.name == ResourceName.EMAIL
assert resource.caches_reads is True
def test_resource_accessor(config):
resource = EmailResource(config=config)
assert resource.accessor is not None
assert resource.accessor.config is config
def test_resource_commands_registered(config):
resource = EmailResource(config=config)
cmds = resource.commands()
assert len(cmds) >= 6
def test_resource_ops_registered(config):
resource = EmailResource(config=config)
ops = resource.ops_list()
assert len(ops) == 3
@pytest.mark.asyncio
async def test_resource_fingerprint_not_found(config):
resource = EmailResource(config=config)
fp = await resource.fingerprint("/email/nonexistent")
assert fp is None
+13
View File
@@ -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,27 @@
# ========= 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. =========
from mirage.resource.gdocs.config import GDocsConfig
from mirage.resource.secrets import reveal_secret
def test_config_creation():
config = GDocsConfig(
client_id="xxx.apps.googleusercontent.com",
client_secret="GOCSPx-xxx",
refresh_token="1//0abc",
)
assert config.client_id == "xxx.apps.googleusercontent.com"
assert reveal_secret(config.client_secret) == "GOCSPx-xxx"
assert reveal_secret(config.refresh_token) == "1//0abc"
@@ -0,0 +1,83 @@
# ========= 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. =========
from mirage.resource.gdocs.doc_entry import (DocEntry, make_filename,
sanitize_title)
TITLE_MAX = 100
def test_doc_entry_creation():
entry = DocEntry(
id="abc123",
name="My Doc",
modified_time="2026-04-01T12:00:00.000Z",
created_time="2026-03-01T12:00:00.000Z",
owner="user@gmail.com",
owned_by_me=True,
can_edit=True,
filename="My_Doc__abc123.gdoc.json",
)
assert entry.id == "abc123"
assert entry.name == "My Doc"
assert entry.owned_by_me is True
assert entry.can_edit is True
assert entry.filename == "My_Doc__abc123.gdoc.json"
def test_sanitize_title_basic():
assert sanitize_title("Hello World") == "Hello_World"
def test_sanitize_title_special_chars():
assert sanitize_title("My/Doc: A\\Test") == "My_Doc_A_Test"
def test_sanitize_title_truncation():
long_title = "A" * 150
result = sanitize_title(long_title)
assert len(result) <= TITLE_MAX
assert result.endswith("...")
def test_sanitize_title_consecutive_underscores():
assert sanitize_title("Hello // World") == "Hello_World"
def test_sanitize_title_empty():
assert sanitize_title("") == "Untitled"
def test_make_filename_without_date():
assert make_filename("My Doc", "abc123") == "My_Doc__abc123.gdoc.json"
def test_make_filename_with_date():
result = make_filename("My Doc", "abc123", "2026-04-01T12:00:00.000Z")
assert result == "2026-04-01_My_Doc__abc123.gdoc.json"
def test_make_filename_long_title():
long_title = "A" * 150
filename = make_filename(long_title, "abc123", "2026-04-01T00:00:00.000Z")
assert filename.endswith("__abc123.gdoc.json")
assert filename.startswith("2026-04-01_")
def test_make_filename_duplicate_titles_different_dates():
f1 = make_filename("My Doc", "abc123", "2026-04-01T00:00:00.000Z")
f2 = make_filename("My Doc", "def456", "2026-03-15T00:00:00.000Z")
assert f1 != f2
assert f1 == "2026-04-01_My_Doc__abc123.gdoc.json"
assert f2 == "2026-03-15_My_Doc__def456.gdoc.json"
@@ -0,0 +1,42 @@
# ========= 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. =========
from mirage.resource.gdocs.prompt import PROMPT, WRITE_PROMPT
def test_prompt_includes_buckets_and_structure():
rendered = PROMPT.format(prefix="/gdocs")
assert "owned/" in rendered
assert "shared/" in rendered
assert "shared with you by others" in rendered
assert "still in owned/" in rendered
assert "gdoc.json structure" in rendered
assert ".body.content[].paragraph.elements[].textRun.content" in rendered
def test_write_prompt_examples_match_actual_signatures():
assert "gws-docs-write" in WRITE_PROMPT
assert "--document" in WRITE_PROMPT
assert "--text" in WRITE_PROMPT
assert "gws-docs-documents-create" in WRITE_PROMPT
assert "--json" in WRITE_PROMPT
assert '{"title":' in WRITE_PROMPT
assert "gws-docs-documents-batchUpdate" in WRITE_PROMPT
assert "documentId" in WRITE_PROMPT
def test_write_prompt_documents_rm_and_newline_gotcha():
assert "rm " in WRITE_PROMPT
assert ".gdoc.json" in WRITE_PROMPT
assert "$'" in WRITE_PROMPT
@@ -0,0 +1,54 @@
# ========= 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 pytest
from mirage.resource.gdocs.config import GDocsConfig
from mirage.resource.gdocs.gdocs import GDocsResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return GDocsConfig(
client_id="test-id",
client_secret="test-secret",
refresh_token="test-refresh",
)
def test_resource_init(config):
resource = GDocsResource(config=config)
assert resource.name == ResourceName.GDOCS
assert resource.caches_reads is True
def test_resource_accessor(config):
resource = GDocsResource(config=config)
assert resource.accessor is not None
assert resource.accessor.config is config
assert resource.accessor.token_manager is not None
def test_resource_commands_registered(config):
resource = GDocsResource(config=config)
cmds = resource.commands()
assert len(cmds) > 15
@pytest.mark.asyncio
async def test_resource_fingerprint_not_found(config):
resource = GDocsResource(config=config)
fp = await resource.fingerprint("owned/Nonexistent__xyz.gdoc.json")
assert fp is None
+13
View File
@@ -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,28 @@
# ========= 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. =========
from mirage.resource.gdrive.prompt import PROMPT
def test_prompt_cross_references_per_service_shapes():
rendered = PROMPT.format(prefix="/gdrive")
assert ".gdoc.json" in rendered
assert ".gsheet.json" in rendered
assert ".gslide.json" in rendered
assert "/gdocs prompt" in rendered
assert "/gsheets prompt" in rendered
assert "/gslides prompt" in rendered
assert "remote mount" in rendered
assert "modifiedTime range" in rendered
assert "No owned/ vs shared/" in rendered
@@ -0,0 +1,51 @@
# ========= 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. =========
from unittest.mock import patch
import pytest
from mirage.resource.gdrive.config import GoogleDriveConfig
from mirage.resource.gdrive.gdrive import GoogleDriveResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return GoogleDriveConfig(
client_id="test-id",
client_secret="test-secret",
refresh_token="test-refresh",
)
def test_resource_init(config):
with patch("mirage.core.google._client.refresh_access_token", ):
resource = GoogleDriveResource(config)
assert resource.name == ResourceName.GDRIVE
assert resource.caches_reads is True
def test_resource_accessor(config):
with patch("mirage.core.google._client.refresh_access_token", ):
resource = GoogleDriveResource(config)
assert resource.accessor is not None
assert resource.accessor.config is config
assert resource.accessor.token_manager is not None
def test_resource_commands_registered(config):
with patch("mirage.core.google._client.refresh_access_token", ):
resource = GoogleDriveResource(config)
assert len(resource._commands) >= 50
+13
View File
@@ -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,60 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.core.github.config import GitHubConfig
from mirage.core.github.tree_entry import TreeEntry
from mirage.resource.secrets import reveal_secret
def test_github_config_requires_token():
with pytest.raises(ValidationError):
GitHubConfig()
def test_github_config_with_token():
cfg = GitHubConfig(token="ghp_abc123")
assert reveal_secret(cfg.token) == "ghp_abc123"
def test_github_config_owner_repo_ref_default():
cfg = GitHubConfig(token="ghp_abc123")
assert cfg.owner is None
assert cfg.repo is None
assert cfg.ref == "main"
def test_github_config_accepts_owner_repo_ref():
cfg = GitHubConfig(token="ghp_abc123",
owner="strukto-ai",
repo="mirage",
ref="dev")
assert cfg.owner == "strukto-ai"
assert cfg.repo == "mirage"
assert cfg.ref == "dev"
def test_tree_entry_fields():
entry = TreeEntry(path="src/main.py", type="blob", sha="abc123", size=1024)
assert entry.path == "src/main.py"
assert entry.type == "blob"
assert entry.sha == "abc123"
assert entry.size == 1024
def test_tree_entry_size_none():
entry = TreeEntry(path="src", type="tree", sha="def456", size=None)
assert entry.size is None
+153
View File
@@ -0,0 +1,153 @@
# ========= 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. =========
from unittest.mock import patch
import pytest
from mirage.core.github.config import GitHubConfig
from mirage.core.github.tree_entry import TreeEntry
from mirage.resource.github.github import GitHubResource
from mirage.types import ResourceName
CONFIG = GitHubConfig(token="test-token")
OWNER = "test-owner"
REPO = "test-repo"
def _make_resource(ref: str = "main",
default_branch: str = "main",
tree: dict | None = None,
truncated: bool = False) -> GitHubResource:
if tree is None:
tree = {}
with patch("mirage.resource.github.github.fetch_default_branch_sync",
return_value=default_branch), \
patch("mirage.resource.github.github.fetch_tree_sync",
return_value=(tree, truncated)):
return GitHubResource(
config=CONFIG,
owner=OWNER,
repo=REPO,
ref=ref,
)
def test_name() -> None:
resource = _make_resource()
assert resource.name == ResourceName.GITHUB
def test_caches_reads() -> None:
resource = _make_resource()
assert resource.caches_reads is True
def test_bind_args() -> None:
resource = _make_resource()
assert resource.accessor.config is CONFIG
assert resource.accessor.owner == OWNER
assert resource.accessor.repo == REPO
assert resource.accessor.ref == "main"
def test_owner_repo_ref_fall_back_to_config() -> None:
config = GitHubConfig(token="test-token",
owner="cfg-owner",
repo="cfg-repo",
ref="cfg-ref")
with patch("mirage.resource.github.github.fetch_default_branch_sync",
return_value="main"), \
patch("mirage.resource.github.github.fetch_tree_sync",
return_value=({}, False)):
resource = GitHubResource(config=config)
assert resource.accessor.owner == "cfg-owner"
assert resource.accessor.repo == "cfg-repo"
assert resource.accessor.ref == "cfg-ref"
def test_kwargs_take_precedence_over_config() -> None:
config = GitHubConfig(token="test-token",
owner="cfg-owner",
repo="cfg-repo",
ref="cfg-ref")
with patch("mirage.resource.github.github.fetch_default_branch_sync",
return_value="main"), \
patch("mirage.resource.github.github.fetch_tree_sync",
return_value=({}, False)):
resource = GitHubResource(config=config,
owner="kw-owner",
repo="kw-repo",
ref="kw-ref")
assert resource.accessor.owner == "kw-owner"
assert resource.accessor.repo == "kw-repo"
assert resource.accessor.ref == "kw-ref"
def test_missing_owner_repo_raises() -> None:
with pytest.raises(ValueError, match="requires owner and repo"):
GitHubResource(config=GitHubConfig(token="test-token"))
def test_is_default_branch_true() -> None:
resource = _make_resource(ref="main", default_branch="main")
assert resource.is_default_branch is True
def test_is_default_branch_false() -> None:
resource = _make_resource(ref="feature-branch", default_branch="main")
assert resource.is_default_branch is False
@pytest.mark.asyncio
async def test_fingerprint_returns_sha() -> None:
tree = {
"src/main.py":
TreeEntry(path="src/main.py", type="blob", sha="abc123", size=100),
}
resource = _make_resource(tree=tree)
result = await resource.fingerprint("src/main.py")
assert result == "abc123"
@pytest.mark.asyncio
async def test_fingerprint_strips_slash() -> None:
tree = {
"src/main.py":
TreeEntry(path="src/main.py", type="blob", sha="abc123", size=100),
}
resource = _make_resource(tree=tree)
result = await resource.fingerprint("/src/main.py")
assert result == "abc123"
@pytest.mark.asyncio
async def test_fingerprint_returns_none_when_path_not_in_tree() -> None:
resource = _make_resource()
result = await resource.fingerprint("nonexistent.py")
assert result is None
@patch("mirage.resource.github.github.fetch_tree_sync")
@patch("mirage.resource.github.github.fetch_default_branch_sync")
def test_init_fetches_default_branch(mock_fetch_branch,
mock_fetch_tree) -> None:
mock_fetch_branch.return_value = "develop"
mock_fetch_tree.return_value = ({}, False)
resource = GitHubResource(config=CONFIG,
owner=OWNER,
repo=REPO,
ref="main")
assert resource.accessor.default_branch == "develop"
mock_fetch_branch.assert_called_once_with(CONFIG, OWNER, REPO)
+13
View File
@@ -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,27 @@
# ========= 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. =========
from mirage.resource.gmail.config import GmailConfig
from mirage.resource.secrets import reveal_secret
def test_config_creation():
config = GmailConfig(
client_id="xxx.apps.googleusercontent.com",
client_secret="GOCSPx-xxx",
refresh_token="1//0abc",
)
assert config.client_id == "xxx.apps.googleusercontent.com"
assert reveal_secret(config.client_secret) == "GOCSPx-xxx"
assert reveal_secret(config.refresh_token) == "1//0abc"
@@ -0,0 +1,58 @@
# ========= 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. =========
from mirage.resource.gmail.prompt import PROMPT, WRITE_PROMPT
def test_prompt_includes_path_anatomy_and_processed_shape():
rendered = PROMPT.format(prefix="/gmail")
assert "<label>" in rendered
assert "INBOX" in rendered
assert "after:/before:" in rendered
assert "mirage-processed" in rendered
assert ".body_text" in rendered
assert "gws-gmail-read" in rendered
assert "gws-gmail-triage" in rendered
def test_prompt_documents_file_per_message_layout():
rendered = PROMPT.format(prefix="/gmail")
assert "<subject>__<message-id>.gmail.json" in rendered
assert "<subject>__<message-id>/" in rendered
assert "attachments dir" in rendered
assert ".attachments[]" in rendered
def test_prompt_mentions_grep_skips_binary_attachments():
rendered = PROMPT.format(prefix="/gmail")
assert "grep" in rendered
assert "binary" in rendered.lower()
def test_write_prompt_examples_match_actual_signatures():
assert "gws-gmail-send" in WRITE_PROMPT
assert "--to" in WRITE_PROMPT
assert "--subject" in WRITE_PROMPT
assert "--body" in WRITE_PROMPT
assert "gws-gmail-reply" in WRITE_PROMPT
assert "gws-gmail-reply-all" in WRITE_PROMPT
assert "gws-gmail-forward" in WRITE_PROMPT
assert "--message-id" in WRITE_PROMPT
def test_write_prompt_documents_rm_and_newline_gotcha():
assert "rm " in WRITE_PROMPT
assert ".gmail.json" in WRITE_PROMPT
assert "Trash" in WRITE_PROMPT
assert "$'" in WRITE_PROMPT
@@ -0,0 +1,54 @@
# ========= 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 pytest
from mirage.resource.gmail.config import GmailConfig
from mirage.resource.gmail.gmail import GmailResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return GmailConfig(
client_id="test-id",
client_secret="test-secret",
refresh_token="test-refresh",
)
def test_resource_init(config):
resource = GmailResource(config=config)
assert resource.name == ResourceName.GMAIL
assert resource.caches_reads is True
def test_resource_accessor(config):
resource = GmailResource(config=config)
assert resource.accessor is not None
assert resource.accessor.config is config
assert resource.accessor.token_manager is not None
def test_resource_commands_registered(config):
resource = GmailResource(config=config)
cmds = resource.commands()
assert len(cmds) > 15
@pytest.mark.asyncio
async def test_resource_fingerprint_not_found(config):
resource = GmailResource(config=config)
fp = await resource.fingerprint("/gmail/nonexistent")
assert fp is None
+13
View File
@@ -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,48 @@
# ========= 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. =========
from mirage.resource.gsheets.prompt import PROMPT, WRITE_PROMPT
def test_prompt_includes_buckets_and_structure():
rendered = PROMPT.format(prefix="/gsheets")
assert "owned/" in rendered
assert "shared/" in rendered
assert "shared with you by others" in rendered
assert "still in owned/" in rendered
assert "gsheet.json structure" in rendered
assert ".sheets[].properties.title" in rendered
assert "gws-sheets-read" in rendered
def test_write_prompt_examples_match_actual_signatures():
assert "gws-sheets-write" in WRITE_PROMPT
assert "--params" in WRITE_PROMPT
assert "--json" in WRITE_PROMPT
assert "spreadsheetId" in WRITE_PROMPT
assert "valueInputOption" in WRITE_PROMPT
assert "gws-sheets-append" in WRITE_PROMPT
assert "--spreadsheet" in WRITE_PROMPT
assert "--range" in WRITE_PROMPT
assert "--values" in WRITE_PROMPT
assert "--json-values" in WRITE_PROMPT
assert "gws-sheets-spreadsheets-create" in WRITE_PROMPT
assert '{"properties": {"title":' in WRITE_PROMPT
assert "gws-sheets-spreadsheets-batchUpdate" in WRITE_PROMPT
assert "spreadsheetId" in WRITE_PROMPT
def test_write_prompt_documents_rm():
assert "rm " in WRITE_PROMPT
assert ".gsheet.json" in WRITE_PROMPT
+13
View File
@@ -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,40 @@
# ========= 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. =========
from mirage.resource.gslides.prompt import PROMPT, WRITE_PROMPT
def test_prompt_includes_buckets_and_structure():
rendered = PROMPT.format(prefix="/gslides")
assert "owned/" in rendered
assert "shared/" in rendered
assert "shared with you by others" in rendered
assert "still in owned/" in rendered
assert "gslide.json structure" in rendered
text_path = (".slides[].pageElements[].shape.text"
".textElements[].textRun.content")
assert text_path in rendered
def test_write_prompt_examples_match_actual_signatures():
assert "gws-slides-presentations-create" in WRITE_PROMPT
assert "--json" in WRITE_PROMPT
assert '{"title":' in WRITE_PROMPT
assert "gws-slides-presentations-batchUpdate" in WRITE_PROMPT
assert "presentationId" in WRITE_PROMPT
def test_write_prompt_documents_rm():
assert "rm " in WRITE_PROMPT
assert ".gslide.json" in WRITE_PROMPT
@@ -0,0 +1,88 @@
# ========= 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 asyncio
import pytest
from mirage.observe.log_entry import EVENT_COMMAND, LogEntry
from mirage.observe.observer import Observer
from mirage.resource.history import HistoryViewResource
from mirage.types import FileType, MountMode
from mirage.workspace.mount.registry import MountRegistry
def _observer_with(commands: list[tuple[str, str]]) -> Observer:
obs = Observer()
for i, (cmd, session) in enumerate(commands):
entry = LogEntry(
type=EVENT_COMMAND,
agent="a",
session=session,
timestamp=(i + 1) * 1000,
command=cmd,
exit_code=0,
)
asyncio.run(obs._log(entry))
return obs
def _mounted(obs: Observer):
registry = MountRegistry()
registry.mount("/.bash_history", HistoryViewResource(obs), MountMode.READ)
return registry.mount_for("/.bash_history")
def test_read_op_renders_gnu_file():
mount = _mounted(_observer_with([("ls /data", "s1"), ("pwd", "s2")]))
data = asyncio.run(mount.execute_op("read", "/.bash_history"))
assert data == b"#1\nls /data\n#2\npwd\n"
def test_read_reflects_new_events_without_invalidation():
obs = _observer_with([("ls /a", "s1")])
mount = _mounted(obs)
first = asyncio.run(mount.execute_op("read", "/.bash_history"))
asyncio.run(
obs._log(
LogEntry(
type=EVENT_COMMAND,
agent="a",
session="s1",
timestamp=2000,
command="pwd",
exit_code=0,
)))
second = asyncio.run(mount.execute_op("read", "/.bash_history"))
assert first != second
assert second.endswith(b"#2\npwd\n")
def test_stat_op_reports_file():
mount = _mounted(_observer_with([("ls /a", "s1")]))
st = asyncio.run(mount.execute_op("stat", "/.bash_history"))
assert st.type != FileType.DIRECTORY
assert st.size == len(b"#1\nls /a\n")
def test_write_op_not_registered():
mount = _mounted(_observer_with([]))
with pytest.raises(AttributeError):
asyncio.run(mount.execute_op("write", "/.bash_history", b"x"))
def test_other_paths_not_found():
mount = _mounted(_observer_with([("ls /a", "s1")]))
with pytest.raises(FileNotFoundError):
asyncio.run(mount.execute_op("read", "/.bash_history/nope"))
@@ -0,0 +1,68 @@
# ========= 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. =========
from mirage.resource.lancedb import LanceDBConfig, LanceDBResource
from mirage.resource.registry import REGISTRY, build_resource
def _resource(**kw) -> LanceDBResource:
base = dict(uri="/tmp/db", group_by=["label"], id_column="id")
base.update(kw)
return LanceDBResource(LanceDBConfig(**base))
def test_resource_name_local_is_not_remote():
res = _resource()
assert res.name == "lancedb"
assert res.caches_reads is False
def test_resource_remote_uri_caches_reads():
res = _resource(uri="s3://bucket/db")
assert res.caches_reads is True
def test_resource_registers_ops():
res = _resource()
assert {"read", "readdir", "stat"} <= {o.name for o in res.ops_list()}
def test_resource_registers_commands():
res = _resource()
expected = {
"cat", "find", "grep", "head", "ls", "rg", "stat", "tail", "tree", "wc"
}
assert expected <= {c.name for c in res.commands()}
def test_resource_in_registry():
assert "lancedb" in REGISTRY
res = build_resource("lancedb", {"uri": "/tmp/db"})
assert res.name == "lancedb"
def test_resource_get_state_redacts_api_key():
res = _resource(api_key="secret-value")
state = res.get_state()
assert "secret-value" not in str(state)
def test_cloud_config_fields_and_remote():
res = _resource(uri="db://my-db",
api_key="sk-xxx",
region="us-west-2",
host_override="https://my-db.region.api.lancedb.com")
assert res.caches_reads is True
assert res.config.region == "us-west-2"
assert res.config.host_override == "https://my-db.region.api.lancedb.com"
@@ -0,0 +1,50 @@
# ========= 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. =========
from mirage.resource.linear.prompt import PROMPT, WRITE_PROMPT
def test_prompt_includes_path_anatomy_and_normalized_shapes():
rendered = PROMPT.format(prefix="/linear")
assert "team.json:" in rendered
assert "issue.json:" in rendered
assert "comments.jsonl:" in rendered
assert "project.json:" in rendered
assert "cycle.json:" in rendered
assert "user.json:" in rendered
assert "mirage-normalized" in rendered
assert ".issue_key" in rendered
assert ".label_names[]" in rendered
assert "linear-search" in rendered
assert "0=none 1=urgent" in rendered
def test_write_prompt_examples_match_actual_signatures():
assert "linear-issue-create" in WRITE_PROMPT
assert "linear-issue-update" in WRITE_PROMPT
assert "linear-issue-assign" in WRITE_PROMPT
assert "linear-issue-transition" in WRITE_PROMPT
assert "linear-issue-set-priority" in WRITE_PROMPT
assert "linear-issue-set-project" in WRITE_PROMPT
assert "linear-issue-add-label" in WRITE_PROMPT
assert "linear-issue-comment-add" in WRITE_PROMPT
assert "linear-issue-comment-update" in WRITE_PROMPT
assert "--team_id" in WRITE_PROMPT
assert "--issue_key" in WRITE_PROMPT
assert "--assignee_email" in WRITE_PROMPT
assert "--state_name" in WRITE_PROMPT
assert "--priority" in WRITE_PROMPT
assert "--body_file" in WRITE_PROMPT
assert "--description_file" in WRITE_PROMPT
assert "UNDERSCORES" in WRITE_PROMPT
@@ -0,0 +1,51 @@
# ========= 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 pytest
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.resource.linear.config import LinearConfig
from mirage.resource.linear.linear import LinearResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return LinearConfig(api_key="lin_api_test")
def test_resource_init(config):
resource = LinearResource(config)
assert resource.caches_reads is True
def test_resource_name(config):
resource = LinearResource(config)
assert resource.name == ResourceName.LINEAR
def test_resource_accessor(config):
resource = LinearResource(config)
assert resource.accessor is not None
assert resource.accessor.config is config
def test_resource_has_index(config):
resource = LinearResource(config)
assert isinstance(resource._index, RAMIndexCacheStore)
def test_resource_commands_registered(config):
resource = LinearResource(config)
assert len(resource._commands) >= 10
@@ -0,0 +1,88 @@
import pytest
from pydantic import ValidationError
from mirage.resource.nextcloud import NextcloudConfig, NextcloudResource
def test_nextcloudconfig_defaults():
c = NextcloudConfig(
url="https://cloud.example.com/remote.php/dav/files/user/")
assert c.username is None
assert c.password is None
assert c.verify_ssl is True
assert c.timeout == 30
def test_nextcloudconfig_immutable():
c = NextcloudConfig(
url="https://cloud.example.com/remote.php/dav/files/user/")
with pytest.raises(ValidationError):
c.url = "https://other.example.com/"
def test_nextcloudconfig_with_credentials():
c = NextcloudConfig(
url="https://cloud.example.com/remote.php/dav/files/user/",
username="alice",
password="secret",
verify_ssl=False,
)
assert c.username == "alice"
assert c.password == "secret"
assert c.verify_ssl is False
def test_nextcloud_write_commands_tagged():
from mirage.commands.builtin.nextcloud import COMMANDS
write_names = {
"cp", "csplit", "gunzip", "gzip", "iconv", "ln", "mkdir", "mktemp",
"mv", "patch", "rm", "split", "tar", "tee", "touch", "unzip", "zip"
}
for fn in COMMANDS:
for rc in fn._registered_commands:
if rc.name in write_names:
assert rc.write is True, f"{rc.name} should be write=True"
else:
assert rc.write is False, f"{rc.name} should be write=False"
def test_nextcloud_write_ops_tagged():
from mirage.ops.nextcloud import OPS
write_op_names = {
"write", "unlink", "rmdir", "mkdir", "create", "truncate", "rename"
}
for fn in OPS:
for ro in fn._registered_ops:
if ro.name in write_op_names:
assert ro.write is True, f"op {ro.name} should be write=True"
else:
assert ro.write is False, f"op {ro.name} should be write=False"
def test_nextcloud_resource_registers_commands():
config = NextcloudConfig(
url="https://cloud.example.com/remote.php/dav/files/user/")
resource = NextcloudResource(config)
command_names = {rc.name for rc in resource._commands}
assert "ls" in command_names
assert "cat" in command_names
assert "grep" in command_names
assert "find" in command_names
assert "mkdir" in command_names
assert "rm" in command_names
def test_nextcloud_resource_get_state():
config = NextcloudConfig(
url="https://cloud.example.com/remote.php/dav/files/user/",
username="alice",
password="secret",
)
resource = NextcloudResource(config)
state = resource.get_state()
assert state["type"] == "nextcloud"
assert state["needs_override"] is True
assert state["config"]["password"] == "<REDACTED>"
assert state["config"]["username"] == "alice"
assert state["config"][
"url"] == "https://cloud.example.com/remote.php/dav/files/user/"
@@ -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 pytest
from pydantic import ValidationError
from mirage.resource.aliyun import AliyunConfig, AliyunResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_aliyun_regional_endpoint():
config = AliyunConfig(bucket="b",
region="cn-hangzhou",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == (
"https://s3.oss-cn-hangzhou.aliyuncs.com")
def test_aliyun_requires_region():
with pytest.raises(ValidationError):
AliyunConfig(bucket="b", access_key_id="k", secret_access_key="s")
def test_aliyun_custom_endpoint_override():
config = AliyunConfig(bucket="b",
region="cn-beijing",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_aliyun_to_s3_config():
config = AliyunConfig(bucket="b",
region="us-west-1",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://s3.oss-us-west-1.aliyuncs.com"
assert reveal_secret(s3.aws_secret_access_key) == "secret"
def test_aliyun_resource_uses_s3_resource_type():
resource = AliyunResource(
AliyunConfig(bucket="b",
region="cn-hangzhou",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -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 pytest
from pydantic import ValidationError
from mirage.resource.backblaze import BackblazeConfig, BackblazeResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_backblaze_regional_endpoint():
config = BackblazeConfig(bucket="b",
region="us-west-002",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == (
"https://s3.us-west-002.backblazeb2.com")
def test_backblaze_requires_region():
with pytest.raises(ValidationError):
BackblazeConfig(bucket="b", access_key_id="k", secret_access_key="s")
def test_backblaze_custom_endpoint_override():
config = BackblazeConfig(bucket="b",
region="us-west-002",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_backblaze_to_s3_config():
config = BackblazeConfig(bucket="b",
region="eu-central-003",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://s3.eu-central-003.backblazeb2.com"
assert reveal_secret(s3.aws_secret_access_key) == "secret"
def test_backblaze_resource_uses_s3_resource_type():
resource = BackblazeResource(
BackblazeConfig(bucket="b",
region="us-west-002",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -0,0 +1,71 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.ceph import CephConfig, CephResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_ceph_config_defaults():
config = CephConfig(
bucket="my-bucket",
endpoint_url="https://ceph.example.com",
access_key_id="k",
secret_access_key="s",
)
assert config.region == "us-east-1"
assert config.path_style is True
def test_ceph_config_immutable():
config = CephConfig(
bucket="my-bucket",
endpoint_url="https://ceph.example.com",
access_key_id="k",
secret_access_key="s",
)
with pytest.raises(ValidationError):
config.bucket = "other"
def test_ceph_config_to_s3_config():
config = CephConfig(
bucket="my-bucket",
endpoint_url="https://ceph.example.com",
access_key_id="key",
secret_access_key="secret",
)
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://ceph.example.com"
assert s3.path_style is True
assert reveal_secret(s3.aws_access_key_id) == "key"
assert reveal_secret(s3.aws_secret_access_key) == "secret"
def test_ceph_resource_uses_s3_resource_type():
resource = CephResource(
CephConfig(
bucket="my-bucket",
endpoint_url="https://ceph.example.com",
access_key_id="k",
secret_access_key="s",
))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
assert resource.ceph_config.endpoint_url == "https://ceph.example.com"
@@ -0,0 +1,68 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.digitalocean import (DigitalOceanConfig,
DigitalOceanResource)
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_do_regional_endpoint():
config = DigitalOceanConfig(bucket="b",
region="nyc3",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == (
"https://nyc3.digitaloceanspaces.com")
def test_do_requires_region():
with pytest.raises(ValidationError):
DigitalOceanConfig(bucket="b",
access_key_id="k",
secret_access_key="s")
def test_do_custom_endpoint_override():
config = DigitalOceanConfig(bucket="b",
region="nyc3",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_do_to_s3_config():
config = DigitalOceanConfig(bucket="b",
region="fra1",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://fra1.digitaloceanspaces.com"
assert reveal_secret(s3.aws_access_key_id) == "key"
def test_do_resource_uses_s3_resource_type():
resource = DigitalOceanResource(
DigitalOceanConfig(bucket="b",
region="sfo3",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -0,0 +1,78 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.gcs import GCSConfig, GCSResource
from mirage.resource.secrets import reveal_secret
def test_gcs_config_defaults():
c = GCSConfig(
bucket="my-bucket",
access_key_id="GOOG123",
secret_access_key="secret",
)
assert c.endpoint_url == "https://storage.googleapis.com"
assert c.region == "auto"
assert c.timeout == 30
assert c.proxy is None
def test_gcs_config_immutable():
c = GCSConfig(
bucket="x",
access_key_id="GOOG123",
secret_access_key="secret",
)
with pytest.raises(ValidationError):
c.bucket = "y"
def test_gcs_config_custom_endpoint():
c = GCSConfig(
bucket="my-bucket",
access_key_id="GOOG123",
secret_access_key="secret",
endpoint_url="https://custom.endpoint.com",
)
assert c.endpoint_url == "https://custom.endpoint.com"
def test_gcs_to_s3_config():
c = GCSConfig(
bucket="my-bucket",
access_key_id="GOOG123",
secret_access_key="secret",
)
s3c = c.to_s3_config()
assert s3c.bucket == "my-bucket"
assert reveal_secret(s3c.aws_access_key_id) == "GOOG123"
assert reveal_secret(s3c.aws_secret_access_key) == "secret"
assert s3c.endpoint_url == "https://storage.googleapis.com"
assert s3c.region == "auto"
def test_gcs_resource_creates():
c = GCSConfig(
bucket="my-bucket",
access_key_id="GOOG123",
secret_access_key="secret",
)
resource = GCSResource(c)
assert resource.gcs_config is c
assert resource.accessor.config.bucket == "my-bucket"
expected = "https://storage.googleapis.com"
assert resource.accessor.config.endpoint_url == expected
@@ -0,0 +1,44 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.hf_buckets import HfBucketsConfig, HfBucketsResource
from mirage.types import ResourceName
def test_resource_name():
r = HfBucketsResource(HfBucketsConfig(bucket="o/b"))
assert r.name == ResourceName.HF_BUCKETS
assert r.caches_reads is True
assert r.SUPPORTS_SNAPSHOT is True
def test_config_immutable():
cfg = HfBucketsConfig(bucket="o/b")
with pytest.raises(ValidationError):
cfg.bucket = "other/other"
def test_resource_registers_ops():
r = HfBucketsResource(HfBucketsConfig(bucket="o/b"))
op_names = {o.name for o in r.ops_list()}
assert {"read", "readdir", "stat", "write", "create", "unlink"} <= op_names
def test_resource_registers_commands():
r = HfBucketsResource(HfBucketsConfig(bucket="o/b"))
cmd_names = {c.name for c in r.commands()}
assert {"cat", "ls", "grep", "stat", "touch", "rm"} <= cmd_names
@@ -0,0 +1,44 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.hf_datasets import HfDatasetsConfig, HfDatasetsResource
from mirage.types import ResourceName
def test_resource_name():
r = HfDatasetsResource(HfDatasetsConfig(repo_id="org/dataset"))
assert r.name == ResourceName.HF_DATASETS
assert r.caches_reads is True
assert r.SUPPORTS_SNAPSHOT is True
def test_config_immutable():
cfg = HfDatasetsConfig(repo_id="org/dataset")
with pytest.raises(ValidationError):
cfg.repo_id = "other/other"
def test_resource_registers_ops():
r = HfDatasetsResource(HfDatasetsConfig(repo_id="org/dataset"))
op_names = {o.name for o in r.ops_list()}
assert {"read", "readdir", "stat", "write", "create", "unlink"} <= op_names
def test_resource_registers_commands():
r = HfDatasetsResource(HfDatasetsConfig(repo_id="org/dataset"))
cmd_names = {c.name for c in r.commands()}
assert {"cat", "ls", "grep", "stat", "touch", "rm"} <= cmd_names
@@ -0,0 +1,39 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.hf_models import HfModelsConfig, HfModelsResource
from mirage.types import ResourceName
def test_resource_name():
r = HfModelsResource(HfModelsConfig(repo_id="org/model"))
assert r.name == ResourceName.HF_MODELS
assert r.caches_reads is True
def test_config_immutable():
cfg = HfModelsConfig(repo_id="org/model")
with pytest.raises(ValidationError):
cfg.repo_id = "other/other"
def test_resource_registers_ops_and_commands():
r = HfModelsResource(HfModelsConfig(repo_id="org/model"))
op_names = {o.name for o in r.ops_list()}
cmd_names = {c.name for c in r.commands()}
assert {"read", "readdir", "stat"} <= op_names
assert {"cat", "ls", "stat"} <= cmd_names
@@ -0,0 +1,39 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.hf_spaces import HfSpacesConfig, HfSpacesResource
from mirage.types import ResourceName
def test_resource_name():
r = HfSpacesResource(HfSpacesConfig(repo_id="org/space"))
assert r.name == ResourceName.HF_SPACES
assert r.caches_reads is True
def test_config_immutable():
cfg = HfSpacesConfig(repo_id="org/space")
with pytest.raises(ValidationError):
cfg.repo_id = "other/other"
def test_resource_registers_ops_and_commands():
r = HfSpacesResource(HfSpacesConfig(repo_id="org/space"))
op_names = {o.name for o in r.ops_list()}
cmd_names = {c.name for c in r.commands()}
assert {"read", "readdir", "stat"} <= op_names
assert {"cat", "ls", "stat"} <= cmd_names
@@ -0,0 +1,97 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.minio import MinIOConfig, MinIOResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_minio_config_defaults():
config = MinIOConfig(
bucket="my-bucket",
endpoint_url="http://localhost:9000",
access_key_id="minioadmin",
secret_access_key="minioadmin",
)
assert config.region == "us-east-1"
assert config.path_style is True
assert config.timeout == 30
def test_minio_config_immutable():
config = MinIOConfig(
bucket="my-bucket",
endpoint_url="http://localhost:9000",
access_key_id="k",
secret_access_key="s",
)
with pytest.raises(ValidationError):
config.bucket = "other"
def test_minio_config_to_s3_config():
config = MinIOConfig(
bucket="my-bucket",
endpoint_url="http://localhost:9000",
access_key_id="minioadmin",
secret_access_key="minioadmin",
proxy="http://localhost:8080",
)
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.bucket == "my-bucket"
assert s3.endpoint_url == "http://localhost:9000"
assert s3.path_style is True
assert reveal_secret(s3.aws_access_key_id) == "minioadmin"
assert reveal_secret(s3.aws_secret_access_key) == "minioadmin"
assert s3.proxy == "http://localhost:8080"
def test_minio_config_path_style_override():
config = MinIOConfig(
bucket="my-bucket",
endpoint_url="https://minio.example.com",
access_key_id="k",
secret_access_key="s",
path_style=False,
)
assert config.to_s3_config().path_style is False
def test_minio_resource_uses_s3_resource_type():
resource = MinIOResource(
MinIOConfig(
bucket="my-bucket",
endpoint_url="http://localhost:9000",
access_key_id="k",
secret_access_key="s",
))
assert resource.name == ResourceName.S3
assert resource.caches_reads is True
assert isinstance(resource.config, S3Config)
def test_minio_resource_preserves_original_config():
config = MinIOConfig(
bucket="my-bucket",
endpoint_url="http://localhost:9000",
access_key_id="k",
secret_access_key="s",
)
resource = MinIOResource(config)
assert resource.minio_config is config
@@ -0,0 +1,108 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.oci import OCIConfig, OCIResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_oci_config_defaults():
config = OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
access_key_id="access-key",
secret_access_key="secret-key",
)
assert config.timeout == 30
assert config.resolved_endpoint_url() == (
"https://my-namespace.compat.objectstorage."
"us-ashburn-1.oci.customer-oci.com")
def test_oci_config_immutable():
config = OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
access_key_id="access-key",
secret_access_key="secret-key",
)
with pytest.raises(ValidationError):
config.bucket = "other-bucket"
def test_oci_config_to_s3_config():
config = OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
access_key_id="access-key",
secret_access_key="secret-key",
proxy="http://localhost:8080",
)
s3_config = config.to_s3_config()
assert isinstance(s3_config, S3Config)
assert s3_config.bucket == "my-bucket"
assert s3_config.region == "us-ashburn-1"
assert s3_config.endpoint_url == (
"https://my-namespace.compat.objectstorage."
"us-ashburn-1.oci.customer-oci.com")
assert reveal_secret(s3_config.aws_access_key_id) == "access-key"
assert reveal_secret(s3_config.aws_secret_access_key) == "secret-key"
assert s3_config.path_style is True
assert s3_config.proxy == "http://localhost:8080"
def test_oci_config_custom_endpoint():
config = OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
endpoint_url="https://custom.example.com",
access_key_id="access-key",
secret_access_key="secret-key",
)
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_oci_resource_uses_s3_resource_type():
resource = OCIResource(
OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
access_key_id="access-key",
secret_access_key="secret-key",
))
assert resource.name == ResourceName.S3
assert resource.caches_reads is True
assert isinstance(resource.config, S3Config)
assert resource.config.path_style is True
def test_oci_resource_preserves_original_config():
config = OCIConfig(
bucket="my-bucket",
namespace="my-namespace",
region="us-ashburn-1",
access_key_id="access-key",
secret_access_key="secret-key",
)
resource = OCIResource(config)
assert resource.oci_config is config
@@ -0,0 +1,64 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.qingstor import QingStorConfig, QingStorResource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_qingstor_zone_endpoint():
config = QingStorConfig(bucket="b",
region="pek3b",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://s3.pek3b.qingstor.com"
def test_qingstor_requires_region():
with pytest.raises(ValidationError):
QingStorConfig(bucket="b", access_key_id="k", secret_access_key="s")
def test_qingstor_custom_endpoint_override():
config = QingStorConfig(bucket="b",
region="sh1a",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_qingstor_to_s3_config():
config = QingStorConfig(bucket="b",
region="gd2a",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://s3.gd2a.qingstor.com"
assert reveal_secret(s3.aws_access_key_id) == "key"
def test_qingstor_resource_uses_s3_resource_type():
resource = QingStorResource(
QingStorConfig(bucket="b",
region="pek3b",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -0,0 +1,84 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.r2 import R2Config, R2Resource
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_r2config_defaults():
config = R2Config(bucket="my-bucket", account_id="account-123")
assert config.region == "auto"
assert config.timeout == 30
assert config.resolved_endpoint_url() == (
"https://account-123.r2.cloudflarestorage.com")
def test_r2config_immutable():
config = R2Config(bucket="my-bucket", account_id="account-123")
with pytest.raises(ValidationError):
config.bucket = "other-bucket"
def test_r2config_to_s3_config():
config = R2Config(
bucket="my-bucket",
account_id="account-123",
access_key_id="access-key",
secret_access_key="secret-key",
proxy="http://localhost:8080",
)
s3_config = config.to_s3_config()
assert isinstance(s3_config, S3Config)
assert s3_config.bucket == "my-bucket"
assert s3_config.region == "auto"
assert s3_config.endpoint_url == (
"https://account-123.r2.cloudflarestorage.com")
assert reveal_secret(s3_config.aws_access_key_id) == "access-key"
assert reveal_secret(s3_config.aws_secret_access_key) == "secret-key"
assert s3_config.proxy == "http://localhost:8080"
def test_r2config_custom_endpoint():
config = R2Config(
bucket="my-bucket",
endpoint_url="https://custom.example.com",
)
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_r2config_requires_account_id_or_endpoint():
config = R2Config(bucket="my-bucket")
with pytest.raises(ValueError):
config.resolved_endpoint_url()
def test_r2resource_uses_s3_resource_type():
resource = R2Resource(
R2Config(bucket="my-bucket", account_id="account-123"))
assert resource.name == ResourceName.S3
assert resource.caches_reads is True
assert isinstance(resource.config, S3Config)
assert resource.config.endpoint_url == (
"https://account-123.r2.cloudflarestorage.com")
def test_r2resource_preserves_original_config():
config = R2Config(bucket="my-bucket", account_id="account-123")
resource = R2Resource(config)
assert resource.r2_config is config
@@ -0,0 +1,79 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.s3 import S3Config
def test_s3config_defaults():
c = S3Config(bucket="my-bucket")
assert c.region is None
assert c.timeout == 30
def test_s3config_immutable():
c = S3Config(bucket="x")
with pytest.raises(ValidationError):
c.bucket = "y"
def test_s3_write_commands_tagged():
from mirage.commands.builtin.s3 import COMMANDS
write_names = {
"rm",
"mkdir",
"touch",
"cp",
"mv",
"ln",
"tee",
"mktemp",
"split",
"csplit",
"gzip",
"gunzip",
"zip",
"unzip",
"tar",
"patch",
"iconv",
}
for fn in COMMANDS:
for rc in fn._registered_commands:
if rc.name in write_names:
assert rc.write is True, (f"{rc.name} should be write=True")
else:
assert rc.write is False, (f"{rc.name} should be write=False")
def test_s3_write_ops_tagged():
from mirage.ops.s3 import OPS
write_op_names = {
"write",
"unlink",
"rmdir",
"mkdir",
"create",
"truncate",
"rename",
}
for fn in OPS:
for ro in fn._registered_ops:
if ro.name in write_op_names:
assert ro.write is True, (f"op {ro.name} should be write=True")
else:
assert ro.write is False, (
f"op {ro.name} should be write=False")
@@ -0,0 +1,72 @@
# ========= 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 re
from io import BytesIO
from unittest.mock import MagicMock
from mirage.resource.s3 import S3Config, S3Resource
def _mock_backend(data: bytes) -> S3Resource:
config = S3Config(
bucket="test-bucket",
region="us-east-1",
aws_access_key_id="fake",
aws_secret_access_key="fake",
)
backend = S3Resource.__new__(S3Resource)
backend.config = config
backend._prefix = ""
backend._event_handlers = []
mock_client = MagicMock()
body = BytesIO(data)
mock_client.get_object.return_value = {"Body": body}
backend._client = mock_client
return backend
def test_grep_file_uses_streaming():
from mirage.commands.builtin.grep_helper import grep_lines
data = b"hello world\nfoo bar\nhello again\n"
compiled = re.compile("hello")
results = grep_lines("test.txt",
data.decode(errors="replace").splitlines(),
compiled,
invert=False,
line_numbers=False,
count_only=False,
files_only=False,
only_matching=False,
max_count=None)
assert len(results) == 2
assert "hello world" in results[0]
def test_grep_file_max_count():
from mirage.commands.builtin.grep_helper import grep_lines
lines = [f"match line {i}".encode() for i in range(100)]
data = b"\n".join(lines) + b"\n"
compiled = re.compile("match")
results = grep_lines("test.txt",
data.decode(errors="replace").splitlines(),
compiled,
invert=False,
line_numbers=False,
count_only=False,
files_only=False,
only_matching=False,
max_count=3)
assert len(results) == 3
@@ -0,0 +1,293 @@
# ========= 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. =========
from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from mirage.accessor.s3 import S3Accessor
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.s3.readdir import readdir
from mirage.core.s3.stat import stat
from mirage.resource.s3 import S3Config
from mirage.types import FileType, PathSpec
from mirage.utils.key_prefix import mount_key
@pytest.fixture
def s3_config():
return S3Config(bucket="test-bucket", region="us-east-1")
@pytest.fixture
def s3_accessor(s3_config):
return S3Accessor(s3_config)
@pytest.fixture
def index():
return RAMIndexCacheStore()
def _mock_client_ctx(mock_client):
mock_ctx = AsyncMock()
mock_ctx.__aenter__.return_value = mock_client
return mock_ctx
class TestStatDirectoryFallback:
@pytest.mark.asyncio
async def test_stat_file_returns_filestat(self, s3_accessor):
with patch("mirage.core.s3.stat.async_session") as mock_session:
mock_client = AsyncMock()
mock_client.head_object.return_value = {
"ContentLength": 1024,
"LastModified": datetime(2026, 1, 1, tzinfo=timezone.utc),
"ETag": '"abc123"',
}
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/data/file.txt",
"/s3"),
virtual="/s3/data/file.txt",
directory="/s3/data/")
result = await stat(s3_accessor, path)
assert result.name == "file.txt"
assert result.size == 1024
assert result.type == FileType.TEXT
@pytest.mark.asyncio
async def test_stat_directory_prefix_fallback(self, s3_accessor):
with patch("mirage.core.s3.stat.async_session") as mock_session:
mock_client = AsyncMock()
err = Exception("not found")
err.response = {"Error": {"Code": "404"}}
mock_client.head_object.side_effect = err
mock_client.list_objects_v2.return_value = {
"CommonPrefixes": [{
"Prefix": "data/subdir/"
}],
}
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/data", "/s3"),
virtual="/s3/data",
directory="/s3/")
result = await stat(s3_accessor, path)
assert result.name == "data"
assert result.type == FileType.DIRECTORY
@pytest.mark.asyncio
async def test_stat_directory_prefix_with_contents(self, s3_accessor):
with patch("mirage.core.s3.stat.async_session") as mock_session:
mock_client = AsyncMock()
err = Exception("not found")
err.response = {"Error": {"Code": "NoSuchKey"}}
mock_client.head_object.side_effect = err
mock_client.list_objects_v2.return_value = {
"Contents": [{
"Key": "data/file.txt"
}],
}
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/data", "/s3"),
virtual="/s3/data",
directory="/s3/")
result = await stat(s3_accessor, path)
assert result.name == "data"
assert result.type == FileType.DIRECTORY
@pytest.mark.asyncio
async def test_stat_nonexistent_raises(self, s3_accessor):
with patch("mirage.core.s3.stat.async_session") as mock_session:
mock_client = AsyncMock()
err = Exception("not found")
err.response = {"Error": {"Code": "404"}}
mock_client.head_object.side_effect = err
mock_client.list_objects_v2.return_value = {}
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/nope", "/s3"),
virtual="/s3/nope",
directory="/s3/")
with pytest.raises(FileNotFoundError):
await stat(s3_accessor, path)
@pytest.mark.asyncio
async def test_stat_root_returns_directory(self, s3_accessor):
path = PathSpec(resource_path=mount_key("/s3/", "/s3"),
virtual="/s3/",
directory="/s3/")
result = await stat(s3_accessor, path)
assert result.name == "/"
assert result.type == FileType.DIRECTORY
class TestStatIndexCache:
@pytest.mark.asyncio
async def test_stat_uses_index_for_folder(self, s3_accessor, index):
await index.put(
"/s3/data",
IndexEntry(id="/data", name="data", resource_type="folder"))
path = PathSpec(resource_path=mount_key("/s3/data", "/s3"),
virtual="/s3/data",
directory="/s3/")
result = await stat(s3_accessor, path, index)
assert result.name == "data"
assert result.type == FileType.DIRECTORY
@pytest.mark.asyncio
async def test_stat_uses_index_for_file(self, s3_accessor, index):
await index.put(
"/s3/data/file.txt",
IndexEntry(id="/data/file.txt",
name="file.txt",
resource_type="file",
size=2048))
path = PathSpec(resource_path=mount_key("/s3/data/file.txt", "/s3"),
virtual="/s3/data/file.txt",
directory="/s3/data/")
result = await stat(s3_accessor, path, index)
assert result.name == "file.txt"
assert result.size == 2048
assert result.type == FileType.TEXT
class TestReaddirIndexEntries:
@pytest.mark.asyncio
async def test_readdir_stores_folder_type(self, s3_accessor, index):
with patch("mirage.core.s3.readdir.async_session") as mock_session:
mock_client = AsyncMock()
page_data = {
"CommonPrefixes": [{
"Prefix": "subdir/data/"
}],
"Contents": [{
"Key": "subdir/readme.txt",
"Size": 100
}],
}
async def _paginate(**kwargs):
yield page_data
mock_paginator = MagicMock()
mock_paginator.paginate = _paginate
mock_client.get_paginator = MagicMock(return_value=mock_paginator)
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/subdir", "/s3"),
virtual="/s3/subdir",
directory="/s3/")
result = await readdir(s3_accessor, path, index)
assert "/s3/subdir/data" in result
assert "/s3/subdir/readme.txt" in result
lookup = await index.get("/s3/subdir/data")
assert lookup.entry is not None
assert lookup.entry.resource_type == "folder"
assert lookup.entry.name == "data"
lookup = await index.get("/s3/subdir/readme.txt")
assert lookup.entry is not None
assert lookup.entry.resource_type == "file"
assert lookup.entry.size == 100
@pytest.mark.asyncio
async def test_readdir_cache_hit(self, s3_accessor, index):
with patch("mirage.core.s3.readdir.async_session") as mock_session:
mock_client = AsyncMock()
page_data = {
"CommonPrefixes": [{
"Prefix": "subdir/nested/"
}],
"Contents": [],
}
async def _paginate(**kwargs):
yield page_data
mock_paginator = MagicMock()
mock_paginator.paginate = _paginate
mock_client.get_paginator = MagicMock(return_value=mock_paginator)
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/subdir", "/s3"),
virtual="/s3/subdir",
directory="/s3/")
r1 = await readdir(s3_accessor, path, index)
r2 = await readdir(s3_accessor, path, index)
assert r1 == r2
assert mock_client.get_paginator.call_count == 1
class TestStatAfterReaddir:
@pytest.mark.asyncio
async def test_stat_hits_cache_after_readdir(self, s3_accessor, index):
with patch("mirage.core.s3.readdir.async_session") as mock_session:
mock_client = AsyncMock()
page_data = {
"CommonPrefixes": [{
"Prefix": "subdir/nested/"
}],
"Contents": [{
"Key": "subdir/readme.txt",
"Size": 500
}],
}
async def _paginate(**kwargs):
yield page_data
mock_paginator = MagicMock()
mock_paginator.paginate = _paginate
mock_client.get_paginator = MagicMock(return_value=mock_paginator)
mock_session.return_value.client.return_value = _mock_client_ctx(
mock_client)
path = PathSpec(resource_path=mount_key("/s3/subdir", "/s3"),
virtual="/s3/subdir",
directory="/s3/")
await readdir(s3_accessor, path, index)
result = await stat(
s3_accessor,
PathSpec(resource_path=mount_key("/s3/subdir/nested", "/s3"),
virtual="/s3/subdir/nested",
directory="/s3/subdir/"), index)
assert result.name == "nested"
assert result.type == FileType.DIRECTORY
result = await stat(
s3_accessor,
PathSpec(resource_path=mount_key("/s3/subdir/readme.txt", "/s3"),
virtual="/s3/subdir/readme.txt",
directory="/s3/subdir/"), index)
assert result.name == "readme.txt"
assert result.size == 500
@@ -0,0 +1,101 @@
# ========= 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. =========
from unittest.mock import AsyncMock, patch
import pytest
from mirage.accessor.s3 import S3Accessor
from mirage.resource.s3 import S3Config, S3Resource
from mirage.types import PathSpec
@pytest.fixture
def s3_config():
return S3Config(bucket="test-bucket", region="us-east-1")
@pytest.fixture
def s3_accessor(s3_config):
return S3Accessor(s3_config)
@pytest.fixture
def s3_backend(s3_config):
return S3Resource(s3_config)
@pytest.mark.asyncio
async def test_get_bytes(s3_accessor):
from mirage.core.s3.read import read_bytes
with patch("mirage.core.s3.read.async_session") as mock_session:
mock_client = AsyncMock()
mock_body = AsyncMock()
mock_body.read.return_value = b"hello world\nfoo bar\nbaz"
mock_client.get_object.return_value = {"Body": mock_body}
mock_ctx = AsyncMock()
mock_ctx.__aenter__.return_value = mock_client
mock_session.return_value.client.return_value = mock_ctx
data = await read_bytes(
s3_accessor,
PathSpec(resource_path="data/file.txt",
virtual="/data/file.txt",
directory="/data/file.txt"))
assert data == b"hello world\nfoo bar\nbaz"
@pytest.mark.asyncio
async def test_range_get(s3_accessor):
from mirage.core.s3.read import read_bytes
with patch("mirage.core.s3.read.async_session") as mock_session:
mock_client = AsyncMock()
mock_body = AsyncMock()
mock_body.read.return_value = b"hello"
mock_client.get_object.return_value = {"Body": mock_body}
mock_ctx = AsyncMock()
mock_ctx.__aenter__.return_value = mock_client
mock_session.return_value.client.return_value = mock_ctx
data = await read_bytes(s3_accessor,
PathSpec(resource_path="data/file.txt",
virtual="/data/file.txt",
directory="/data/file.txt"),
offset=0,
size=5)
assert data == b"hello"
mock_client.get_object.assert_called_once()
call_kwargs = mock_client.get_object.call_args[1]
assert "Range" in call_kwargs
assert call_kwargs["Range"] == "bytes=0-4"
@pytest.mark.asyncio
async def test_put_bytes(s3_accessor):
from mirage.core.s3.write import write_bytes
with patch("mirage.core.s3.write.async_session") as mock_session:
mock_client = AsyncMock()
mock_ctx = AsyncMock()
mock_ctx.__aenter__.return_value = mock_client
mock_session.return_value.client.return_value = mock_ctx
await write_bytes(
s3_accessor,
PathSpec(resource_path="data/out.txt",
virtual="/data/out.txt",
directory="/data/out.txt"), b"hello")
mock_client.put_object.assert_called_once()
call_kwargs = mock_client.put_object.call_args[1]
assert call_kwargs["Body"] == b"hello"
assert call_kwargs["Key"] == "data/out.txt"
@@ -0,0 +1,98 @@
# ========= 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 asyncio
from io import BytesIO
from unittest.mock import AsyncMock, MagicMock, patch
from mirage.accessor.s3 import S3Accessor
from mirage.resource.s3 import S3Config
def _config():
return S3Accessor(
S3Config(
bucket="test-bucket",
region="us-east-1",
aws_access_key_id="fake",
aws_secret_access_key="fake",
))
def _mock_session(data: bytes, chunk_size: int = 8192):
mock_client = AsyncMock()
body_mock = AsyncMock()
body_mock.read = AsyncMock(return_value=data)
async def _iter_chunks(size=chunk_size):
buf = BytesIO(data)
while True:
chunk = buf.read(size)
if not chunk:
break
yield chunk
body_mock.iter_chunks = _iter_chunks
mock_client.get_object = AsyncMock(return_value={"Body": body_mock})
mock_session = MagicMock()
ctx = AsyncMock()
ctx.__aenter__ = AsyncMock(return_value=mock_client)
ctx.__aexit__ = AsyncMock(return_value=False)
mock_session.client = MagicMock(return_value=ctx)
return mock_session
def test_read_stream_returns_async_iterator():
from mirage.core.s3.stream import read_stream
config = _config()
session = _mock_session(b"hello world")
async def _run():
chunks = []
async for chunk in read_stream(config, "test.txt"):
chunks.append(chunk)
return chunks
with patch("mirage.core.s3.stream.async_session", return_value=session):
chunks = asyncio.run(_run())
assert b"".join(chunks) == b"hello world"
def test_read_stream_yields_chunks():
from mirage.core.s3.stream import read_stream
config = _config()
session = _mock_session(b"a" * 100, chunk_size=30)
async def _run():
chunks = []
async for chunk in read_stream(config, "test.txt", chunk_size=30):
chunks.append(chunk)
return chunks
with patch("mirage.core.s3.stream.async_session", return_value=session):
chunks = asyncio.run(_run())
assert b"".join(chunks) == b"a" * 100
assert len(chunks) >= 2
def test_read_bytes_returns_bytes():
from mirage.core.s3.read import read_bytes
config = _config()
session = _mock_session(b"file content here")
with patch("mirage.core.s3.read.async_session", return_value=session):
result = asyncio.run(read_bytes(config, "test.txt"))
assert isinstance(result, bytes)
assert result == b"file content here"
@@ -0,0 +1,64 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.s3 import S3Config
from mirage.resource.scaleway import ScalewayConfig, ScalewayResource
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_scaleway_regional_endpoint():
config = ScalewayConfig(bucket="b",
region="fr-par",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://s3.fr-par.scw.cloud"
def test_scaleway_requires_region():
with pytest.raises(ValidationError):
ScalewayConfig(bucket="b", access_key_id="k", secret_access_key="s")
def test_scaleway_custom_endpoint_override():
config = ScalewayConfig(bucket="b",
region="nl-ams",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_scaleway_to_s3_config():
config = ScalewayConfig(bucket="b",
region="pl-waw",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://s3.pl-waw.scw.cloud"
assert reveal_secret(s3.aws_access_key_id) == "key"
def test_scaleway_resource_uses_s3_resource_type():
resource = ScalewayResource(
ScalewayConfig(bucket="b",
region="fr-par",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -0,0 +1,97 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.s3 import S3Config
from mirage.resource.seaweedfs import SeaweedFSConfig, SeaweedFSResource
from mirage.resource.secrets import reveal_secret
from mirage.types import ResourceName
def test_seaweedfs_config_defaults():
config = SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="http://localhost:8333",
access_key_id="weed",
secret_access_key="weed",
)
assert config.region == "us-east-1"
assert config.path_style is True
assert config.timeout == 30
def test_seaweedfs_config_immutable():
config = SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="http://localhost:8333",
access_key_id="k",
secret_access_key="s",
)
with pytest.raises(ValidationError):
config.bucket = "other"
def test_seaweedfs_config_to_s3_config():
config = SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="http://localhost:8333",
access_key_id="weed",
secret_access_key="weed",
proxy="http://localhost:8080",
)
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.bucket == "my-bucket"
assert s3.endpoint_url == "http://localhost:8333"
assert s3.path_style is True
assert reveal_secret(s3.aws_access_key_id) == "weed"
assert reveal_secret(s3.aws_secret_access_key) == "weed"
assert s3.proxy == "http://localhost:8080"
def test_seaweedfs_config_path_style_override():
config = SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="https://seaweedfs.example.com",
access_key_id="k",
secret_access_key="s",
path_style=False,
)
assert config.to_s3_config().path_style is False
def test_seaweedfs_resource_uses_s3_resource_type():
resource = SeaweedFSResource(
SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="http://localhost:8333",
access_key_id="k",
secret_access_key="s",
))
assert resource.name == ResourceName.S3
assert resource.caches_reads is True
assert isinstance(resource.config, S3Config)
def test_seaweedfs_resource_preserves_original_config():
config = SeaweedFSConfig(
bucket="my-bucket",
endpoint_url="http://localhost:8333",
access_key_id="k",
secret_access_key="s",
)
resource = SeaweedFSResource(config)
assert resource.seaweedfs_config is config
@@ -0,0 +1,123 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.core.s3._client import _client_kwargs
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.resource.supabase import SupabaseConfig, SupabaseResource
from mirage.types import ResourceName
def test_supabase_config_defaults():
config = SupabaseConfig(
bucket="my-bucket",
project_ref="project-123",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
)
assert config.timeout == 30
assert config.resolved_endpoint_url() == (
"https://project-123.storage.supabase.co/storage/v1/s3")
def test_supabase_config_immutable():
config = SupabaseConfig(
bucket="my-bucket",
project_ref="project-123",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
)
with pytest.raises(ValidationError):
config.bucket = "other-bucket"
def test_supabase_config_to_s3_config():
config = SupabaseConfig(
bucket="my-bucket",
endpoint_url="https://example.supabase.co/storage/v1/s3",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
session_token="session-token",
proxy="http://localhost:8080",
)
s3_config = config.to_s3_config()
assert isinstance(s3_config, S3Config)
assert s3_config.bucket == "my-bucket"
assert s3_config.region == "us-west-2"
assert (
s3_config.endpoint_url == "https://example.supabase.co/storage/v1/s3")
assert reveal_secret(s3_config.aws_access_key_id) == "access-key"
assert reveal_secret(s3_config.aws_secret_access_key) == "secret-key"
assert reveal_secret(s3_config.aws_session_token) == "session-token"
assert s3_config.path_style is True
assert s3_config.proxy == "http://localhost:8080"
def test_supabase_config_requires_project_ref_or_endpoint():
config = SupabaseConfig(
bucket="my-bucket",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
)
with pytest.raises(ValueError):
config.resolved_endpoint_url()
def test_supabase_resource_uses_s3_resource_type():
resource = SupabaseResource(
SupabaseConfig(
bucket="my-bucket",
project_ref="project-123",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
))
assert resource.name == ResourceName.S3
assert resource.caches_reads is True
assert isinstance(resource.config, S3Config)
assert resource.config.path_style is True
def test_supabase_resource_preserves_original_config():
config = SupabaseConfig(
bucket="my-bucket",
project_ref="project-123",
region="us-west-2",
access_key_id="access-key",
secret_access_key="secret-key",
)
resource = SupabaseResource(config)
assert resource.supabase_config is config
def test_s3_client_kwargs_support_path_style_and_session_token():
config = S3Config(
bucket="my-bucket",
region="us-west-2",
endpoint_url="https://example.com",
aws_access_key_id="access-key",
aws_secret_access_key="secret-key",
aws_session_token="session-token",
path_style=True,
)
kwargs = _client_kwargs(config)
assert kwargs["aws_session_token"] == "session-token"
assert kwargs["config"].s3 == {"addressing_style": "path"}
@@ -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 pytest
from pydantic import ValidationError
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.resource.tencent import TencentConfig, TencentResource
from mirage.types import ResourceName
def test_tencent_regional_endpoint():
config = TencentConfig(bucket="b-1250",
region="ap-guangzhou",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == (
"https://cos.ap-guangzhou.myqcloud.com")
def test_tencent_requires_region():
with pytest.raises(ValidationError):
TencentConfig(bucket="b", access_key_id="k", secret_access_key="s")
def test_tencent_custom_endpoint_override():
config = TencentConfig(bucket="b",
region="ap-beijing",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_tencent_to_s3_config():
config = TencentConfig(bucket="b-1250",
region="ap-singapore",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://cos.ap-singapore.myqcloud.com"
assert reveal_secret(s3.aws_access_key_id) == "key"
def test_tencent_resource_uses_s3_resource_type():
resource = TencentResource(
TencentConfig(bucket="b-1250",
region="ap-guangzhou",
access_key_id="k",
secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -0,0 +1,69 @@
# ========= 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 pytest
from pydantic import ValidationError
from mirage.resource.s3 import S3Config
from mirage.resource.secrets import reveal_secret
from mirage.resource.wasabi import WasabiConfig, WasabiResource
from mirage.types import ResourceName
def test_wasabi_default_region_endpoint():
config = WasabiConfig(bucket="b", access_key_id="k", secret_access_key="s")
assert config.region == "us-east-1"
assert config.resolved_endpoint_url() == "https://s3.wasabisys.com"
def test_wasabi_regional_endpoint():
config = WasabiConfig(bucket="b",
region="us-west-1",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == (
"https://s3.us-west-1.wasabisys.com")
def test_wasabi_custom_endpoint_override():
config = WasabiConfig(bucket="b",
endpoint_url="https://custom.example.com",
access_key_id="k",
secret_access_key="s")
assert config.resolved_endpoint_url() == "https://custom.example.com"
def test_wasabi_config_immutable():
config = WasabiConfig(bucket="b", access_key_id="k", secret_access_key="s")
with pytest.raises(ValidationError):
config.bucket = "other"
def test_wasabi_to_s3_config():
config = WasabiConfig(bucket="b",
region="us-east-2",
access_key_id="key",
secret_access_key="secret")
s3 = config.to_s3_config()
assert isinstance(s3, S3Config)
assert s3.endpoint_url == "https://s3.us-east-2.wasabisys.com"
assert s3.path_style is False
assert reveal_secret(s3.aws_access_key_id) == "key"
def test_wasabi_resource_uses_s3_resource_type():
resource = WasabiResource(
WasabiConfig(bucket="b", access_key_id="k", secret_access_key="s"))
assert resource.name == ResourceName.S3
assert isinstance(resource.config, S3Config)
@@ -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,23 @@
# ========= 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. =========
from mirage.accessor.postgres import PostgresAccessor
from mirage.resource.postgres.config import PostgresConfig
def test_accessor_holds_config():
cfg = PostgresConfig(dsn="postgres://localhost/db")
a = PostgresAccessor(cfg)
assert a.config is cfg
assert a._pool is None
@@ -0,0 +1,31 @@
# ========= 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. =========
from mirage.resource.postgres.config import PostgresConfig
from mirage.resource.secrets import reveal_secret
def test_defaults():
cfg = PostgresConfig(dsn="postgres://localhost/db")
assert reveal_secret(cfg.dsn) == "postgres://localhost/db"
assert cfg.schemas is None
assert cfg.default_row_limit == 1000
assert cfg.max_read_rows == 10_000
assert cfg.max_read_bytes == 10 * 1024 * 1024
assert cfg.default_search_limit == 100
def test_schema_filter():
cfg = PostgresConfig(dsn="postgres://localhost/db", schemas=["public"])
assert cfg.schemas == ["public"]
@@ -0,0 +1,58 @@
# ========= 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. =========
from mirage.resource.postgres import PostgresConfig, PostgresResource
def test_resource_name():
res = PostgresResource(PostgresConfig(dsn="postgres://localhost/db"))
assert res.name == "postgres"
assert res.caches_reads is False
def test_resource_registers_three_ops():
res = PostgresResource(PostgresConfig(dsn="postgres://localhost/db"))
op_names = {ro.name for ro in res.ops_list()}
assert {"read", "readdir", "stat"} <= op_names
def test_resource_registers_commands():
res = PostgresResource(PostgresConfig(dsn="postgres://localhost/db"))
cmd_names = {rc.name for rc in res.commands()}
expected = {
"cat", "find", "head", "jq", "ls", "stat", "tail", "tree", "wc",
"grep", "rg"
}
assert expected <= cmd_names
def test_resource_in_registry():
from mirage.resource.registry import REGISTRY, build_resource
assert "postgres" in REGISTRY
res = build_resource("postgres", config={"dsn": "postgres://localhost/db"})
assert res.name == "postgres"
def test_resource_get_state_redacts_dsn():
res = PostgresResource(PostgresConfig(dsn="postgres://user:pw@host/db"))
state = res.get_state()
assert state["type"] == "postgres"
assert state["config"]["dsn"] == "<REDACTED>"
assert "redacted_fields" not in state
def test_resource_load_state_noop():
res = PostgresResource(PostgresConfig(dsn="postgres://localhost/db"))
res.load_state({"type": "postgres"})
@@ -0,0 +1,41 @@
from mirage.resource.qdrant import QdrantConfig, QdrantResource
from mirage.resource.registry import REGISTRY, build_resource
def _resource(**kw) -> QdrantResource:
base = dict(collection="animals", group_by=["label"], id_field="id")
base.update(kw)
return QdrantResource(QdrantConfig(**base))
def test_resource_name_and_remote():
res = _resource()
assert res.name == "qdrant"
assert res.is_remote is True
assert res.SUPPORTS_SNAPSHOT is False
def test_resource_registers_ops():
res = _resource()
assert {"read", "readdir", "stat"} <= {o.name for o in res.ops_list()}
def test_resource_registers_commands():
res = _resource()
expected = {
"cat", "find", "grep", "head", "ls", "rg", "search", "stat", "tail",
"tree", "wc"
}
assert expected <= {c.name for c in res.commands()}
def test_resource_in_registry():
assert "qdrant" in REGISTRY
res = build_resource("qdrant", {"collection": "docs"})
assert res.name == "qdrant"
def test_resource_get_state_redacts_api_key():
res = _resource(api_key="secret-value")
state = res.get_state()
assert "secret-value" not in str(state)
+13
View File
@@ -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,193 @@
# ========= 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.resource.redis import RedisResource
from mirage.types import MountMode
from mirage.workspace import Workspace
REDIS_URL = os.environ.get("REDIS_URL", "")
pytestmark = pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
@pytest_asyncio.fixture()
async def ws():
resource = RedisResource(url=REDIS_URL, key_prefix="test:integ:")
await resource._store.clear()
await resource._store.add_dir("/")
w = Workspace(
{"/data": (resource, MountMode.WRITE)},
mode=MountMode.WRITE,
)
yield w
await resource._store.clear()
await resource._store.close()
async def _run(ws, cmd, stdin=None):
io = await ws.execute(cmd, stdin=stdin)
stdout = io.stdout
if stdout is None:
return ""
if isinstance(stdout, bytes):
return stdout.decode(errors="replace")
chunks = [chunk async for chunk in stdout]
return b"".join(chunks).decode(errors="replace")
@pytest.mark.asyncio
async def test_echo_and_cat(ws):
await _run(ws, "echo hello > /data/hello.txt")
result = await _run(ws, "cat /data/hello.txt")
assert result == "hello\n"
@pytest.mark.asyncio
async def test_ls(ws):
await _run(ws, "echo a > /data/a.txt")
await _run(ws, "echo b > /data/b.txt")
result = await _run(ws, "ls /data/")
assert "a.txt" in result
assert "b.txt" in result
@pytest.mark.asyncio
async def test_stat(ws):
await _run(ws, "echo hello > /data/f.txt")
result = await _run(ws, "stat /data/f.txt")
assert "name=f.txt" in result
assert "size=" in result
@pytest.mark.asyncio
async def test_head(ws):
await _run(ws, "echo 'line1\nline2\nline3' > /data/f.txt")
result = await _run(ws, "head -n 2 /data/f.txt")
assert "line1" in result
assert "line2" in result
@pytest.mark.asyncio
async def test_grep(ws):
await _run(
ws,
"echo 'hello world\nfoo bar\nhello again' "
"> /data/f.txt",
)
result = await _run(ws, "grep hello /data/f.txt")
assert "hello world" in result
assert "hello again" in result
assert "foo bar" not in result
@pytest.mark.asyncio
async def test_wc(ws):
await _run(ws, "echo 'hello world' > /data/f.txt")
result = await _run(ws, "wc /data/f.txt")
assert "1" in result
@pytest.mark.asyncio
async def test_mkdir(ws):
await _run(ws, "mkdir /data/sub")
result = await _run(ws, "ls /data/")
assert "sub" in result
@pytest.mark.asyncio
async def test_mkdir_p(ws):
await _run(ws, "mkdir -p /data/a/b/c")
result = await _run(ws, "stat /data/a/b/c")
assert "directory" in result
@pytest.mark.asyncio
async def test_cp(ws):
await _run(ws, "echo data > /data/src.txt")
await _run(ws, "cp /data/src.txt /data/dst.txt")
result = await _run(ws, "cat /data/dst.txt")
assert "data" in result
@pytest.mark.asyncio
async def test_mv(ws):
await _run(ws, "echo data > /data/old.txt")
await _run(ws, "mv /data/old.txt /data/new.txt")
result = await _run(ws, "cat /data/new.txt")
assert "data" in result
ls_result = await _run(ws, "ls /data/")
assert "old.txt" not in ls_result
@pytest.mark.asyncio
async def test_rm(ws):
await _run(ws, "echo data > /data/f.txt")
await _run(ws, "rm /data/f.txt")
result = await _run(ws, "ls /data/")
assert "f.txt" not in result
@pytest.mark.asyncio
async def test_tree(ws):
await _run(ws, "echo a > /data/a.txt")
await _run(ws, "mkdir /data/sub")
await _run(ws, "echo b > /data/sub/b.txt")
result = await _run(ws, "tree /data/")
assert "a.txt" in result
assert "sub" in result
assert "b.txt" in result
@pytest.mark.asyncio
async def test_find(ws):
await _run(ws, "echo a > /data/a.txt")
await _run(ws, "mkdir /data/sub")
await _run(ws, "echo b > /data/sub/b.py")
result = await _run(ws, "find /data/ -name '*.py'")
assert "b.py" in result
assert "a.txt" not in result
@pytest.mark.asyncio
async def test_du(ws):
await _run(ws, "echo hello > /data/f.txt")
result = await _run(ws, "du /data/")
assert result.strip() != ""
@pytest.mark.asyncio
async def test_pipe(ws):
await _run(ws, "echo 'a\nb\na\nc\na' > /data/f.txt")
result = await _run(ws, "grep a /data/f.txt | wc -l")
assert result.strip() == "3"
@pytest.mark.asyncio
async def test_cd_and_pwd(ws):
await _run(ws, "echo hi > /data/f.txt")
await _run(ws, "cd /data/")
result = await _run(ws, "pwd")
assert "/data" in result
@pytest.mark.asyncio
async def test_data_persists_across_commands(ws):
await _run(ws, "echo persistent > /data/p.txt")
result1 = await _run(ws, "cat /data/p.txt")
result2 = await _run(ws, "cat /data/p.txt")
assert result1 == result2 == "persistent\n"
@@ -0,0 +1,138 @@
# ========= 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.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:store:")
await s.clear()
await s.add_dir("/")
yield s
await s.clear()
await s.close()
@pytest.mark.asyncio
async def test_get_set_file(store):
await store.set_file("/a.txt", b"hello")
assert await store.get_file("/a.txt") == b"hello"
@pytest.mark.asyncio
async def test_get_file_missing(store):
assert await store.get_file("/nope") is None
@pytest.mark.asyncio
async def test_del_file(store):
await store.set_file("/a.txt", b"data")
await store.del_file("/a.txt")
assert await store.get_file("/a.txt") is None
@pytest.mark.asyncio
async def test_has_file(store):
assert await store.has_file("/a.txt") is False
await store.set_file("/a.txt", b"x")
assert await store.has_file("/a.txt") is True
@pytest.mark.asyncio
async def test_list_files(store):
await store.set_file("/a.txt", b"a")
await store.set_file("/b.txt", b"b")
await store.set_file("/sub/c.txt", b"c")
files = await store.list_files()
assert "/a.txt" in files
assert "/b.txt" in files
assert "/sub/c.txt" in files
@pytest.mark.asyncio
async def test_list_files_prefix(store):
await store.set_file("/a.txt", b"a")
await store.set_file("/sub/b.txt", b"b")
files = await store.list_files("/sub/")
assert "/sub/b.txt" in files
assert "/a.txt" not in files
@pytest.mark.asyncio
async def test_file_len(store):
await store.set_file("/a.txt", b"hello")
assert await store.file_len("/a.txt") == 5
@pytest.mark.asyncio
async def test_has_dir(store):
assert await store.has_dir("/") is True
assert await store.has_dir("/sub") is False
@pytest.mark.asyncio
async def test_add_remove_dir(store):
await store.add_dir("/sub")
assert await store.has_dir("/sub") is True
await store.remove_dir("/sub")
assert await store.has_dir("/sub") is False
@pytest.mark.asyncio
async def test_list_dirs(store):
await store.add_dir("/a")
await store.add_dir("/b")
dirs = await store.list_dirs()
assert "/" in dirs
assert "/a" in dirs
assert "/b" in dirs
@pytest.mark.asyncio
async def test_get_set_modified(store):
await store.set_modified("/a.txt", "2026-01-01T00:00:00")
result = await store.get_modified("/a.txt")
assert result == "2026-01-01T00:00:00"
@pytest.mark.asyncio
async def test_get_modified_missing(store):
assert await store.get_modified("/nope") is None
@pytest.mark.asyncio
async def test_del_modified(store):
await store.set_modified("/a.txt", "2026-01-01")
await store.del_modified("/a.txt")
assert await store.get_modified("/a.txt") is None
@pytest.mark.asyncio
async def test_clear(store):
await store.set_file("/a.txt", b"data")
await store.add_dir("/sub")
await store.set_modified("/a.txt", "2026-01-01")
await store.clear()
assert await store.get_file("/a.txt") is None
assert await store.has_dir("/sub") is False
assert await store.get_modified("/a.txt") is None
+13
View File
@@ -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,47 @@
# ========= 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 pytest
from mirage.resource.slack.config import SlackConfig
from mirage.resource.slack.slack import SlackResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return SlackConfig(token="xoxb-test-token")
def test_resource_init(config):
resource = SlackResource(config)
assert resource.caches_reads is True
def test_resource_name(config):
resource = SlackResource(config)
assert resource.name == ResourceName.SLACK
def test_resource_accessor(config):
resource = SlackResource(config)
assert resource.accessor is not None
assert resource.accessor.config is config
def test_resource_commands_registered(config):
resource = SlackResource(config)
# 52 native (generic factory read set incl. find and sed + bespoke
# grep/rg + slack_* writers) + 9 filetype cmds x 7 columnar exts
assert len(resource._commands) == 52 + 9 * 7
+13
View File
@@ -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,59 @@
# ========= 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 pytest
from mirage.resource.ssh import SSHConfig, SSHResource
from mirage.types import ResourceName
def test_ssh_config_frozen():
cfg = SSHConfig(host="dev", root="/home/ubuntu")
assert cfg.host == "dev"
assert cfg.root == "/home/ubuntu"
with pytest.raises(Exception):
cfg.host = "other"
def test_ssh_resource_attributes():
cfg = SSHConfig(host="dev")
resource = SSHResource(cfg)
assert resource.name == ResourceName.SSH
assert resource.caches_reads is True
assert len(resource.commands()) > 0
assert len(resource.ops_list()) > 0
def test_ssh_resource_command_count():
cfg = SSHConfig(host="dev")
resource = SSHResource(cfg)
cmd_names = {c.name for c in resource.commands()}
assert "cat" in cmd_names
assert "ls" in cmd_names
assert "grep" in cmd_names
assert "find" in cmd_names
assert "cp" in cmd_names
assert "rm" in cmd_names
def test_ssh_resource_ops_count():
cfg = SSHConfig(host="dev")
resource = SSHResource(cfg)
op_names = {o.name for o in resource.ops_list()}
assert "read" in op_names
assert "write" in op_names
assert "stat" in op_names
assert "readdir" in op_names
assert "mkdir" in op_names
assert "unlink" in op_names
@@ -0,0 +1,39 @@
# ========= 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. =========
from mirage.resource.base import BaseResource
from mirage.resource.disk.disk import DiskResource
from mirage.resource.ram import RAMResource
from mirage.resource.s3 import S3Resource
from mirage.types import ResourceName
def test_base_resource_name():
assert BaseResource.name == "base"
def test_disk_resource_name():
assert DiskResource.name == "disk"
def test_memory_resource_name():
assert RAMResource.name == "ram"
def test_s3_resource_name():
assert S3Resource.name == "s3"
def test_hf_buckets_resource_name():
assert ResourceName.HF_BUCKETS.value == "hf_buckets"
+35
View File
@@ -0,0 +1,35 @@
# ========= 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. =========
from mirage.cache.index import (RAMIndexCacheStore, RedisIndexCacheStore,
RedisIndexConfig)
from mirage.resource.ram import RAMResource
def test_default_index_is_ram():
r = RAMResource()
assert isinstance(r.index, RAMIndexCacheStore)
def test_set_index_redis():
r = RAMResource()
r.set_index(RedisIndexConfig(url="redis://localhost:6379/0"))
assert isinstance(r.index, RedisIndexCacheStore)
def test_set_index_none_resets_to_ram():
r = RAMResource()
r.set_index(RedisIndexConfig(url="redis://localhost:6379/0"))
r.set_index(None)
assert isinstance(r.index, RAMIndexCacheStore)
@@ -0,0 +1,37 @@
# ========= 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. =========
from mirage.accessor.base import Accessor
from mirage.accessor.disk import DiskAccessor
from mirage.accessor.ram import RAMAccessor
from mirage.resource.base import BaseResource
from mirage.resource.disk.disk import DiskResource
from mirage.resource.ram import RAMResource
def test_base_command_args():
b = BaseResource()
assert isinstance(b.accessor, Accessor)
def test_local_command_args():
b = DiskResource("/tmp")
assert isinstance(b.accessor, DiskAccessor)
assert b.accessor.root is not None
def test_memory_command_args():
b = RAMResource()
assert isinstance(b.accessor, RAMAccessor)
assert b.accessor.store is not None
+30
View File
@@ -0,0 +1,30 @@
# ========= 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 pytest
from mirage.resource.base import BaseResource
from mirage.resource.ram import RAMResource
@pytest.mark.asyncio
async def test_base_backend_fingerprint_returns_none():
backend = BaseResource()
assert await backend.fingerprint("/any") is None
@pytest.mark.asyncio
async def test_memory_backend_fingerprint_returns_none():
backend = RAMResource()
assert await backend.fingerprint("/any") is None
+60
View File
@@ -0,0 +1,60 @@
# ========= 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 asyncio
import pytest
from mirage.resource.loader import load_backend_class
def _cat_sync(backend, path):
async def _collect():
return b"".join([c async for c in backend.read_stream(path)])
return asyncio.run(_collect())
def test_load_from_module():
cls = load_backend_class("mirage.resource.ram.ram:RAMResource")
from mirage.resource.ram import RAMResource
assert cls is RAMResource
def test_load_from_script_file(tmp_path):
script = tmp_path / "custom_backend.py"
script.write_text("from mirage.resource.ram import RAMResource\n"
"class CustomBackend(RAMResource):\n"
" pass\n")
cls = load_backend_class(f"{script}:CustomBackend")
assert cls.__name__ == "CustomBackend"
instance = cls()
asyncio.run(instance.write("/test.txt", data=b"hello"))
assert _cat_sync(instance, "/test.txt") == b"hello"
def test_load_invalid_spec_no_colon():
with pytest.raises(ValueError, match="invalid backend spec"):
load_backend_class("mirage.resource.ram")
def test_load_missing_file():
with pytest.raises(ValueError, match="cannot load script"):
load_backend_class("/nonexistent/path.py:Cls")
def test_load_missing_class():
with pytest.raises(ValueError, match="not found"):
load_backend_class("mirage.resource.ram.ram:DoesNotExist")
@@ -0,0 +1,47 @@
# ========= 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. =========
from mirage.ops.registry import RegisteredOp, op
from mirage.resource.base import BaseResource
from mirage.resource.ram import RAMResource
def test_resource_register_op():
@op("read", resource="test", filetype=".custom")
async def read_custom(store, path):
return b"custom"
resource = BaseResource()
resource.register_op(read_custom)
ops = resource.ops_list()
assert len(ops) == 1
assert isinstance(ops[0], RegisteredOp)
assert ops[0].name == "read"
assert ops[0].filetype == ".custom"
def test_resource_register_op_empty():
resource = BaseResource()
assert resource.ops_list() == []
def test_ram_resource_registers_ops():
resource = RAMResource()
ops = resource.ops_list()
assert len(ops) > 0
names = {ro.name for ro in ops}
assert "read" in names
assert "write" in names
assert "stat" in names
+55
View File
@@ -0,0 +1,55 @@
# ========= 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 asyncio
from mirage.resource.ram import RAMResource
def _make_backend():
b = RAMResource()
b._store.dirs.add("/data")
b._store.files["/data/file.txt"] = b"hello world"
return b
def test_cat_updates_stats():
b = _make_backend()
data = asyncio.run(b.read_bytes("/data/file.txt"))
assert data == b"hello world"
def test_tee_updates_stats():
b = _make_backend()
asyncio.run(b.write("/data/out.txt", data=b"test data"))
assert b._store.files["/data/out.txt"] == b"test data"
def test_callback_receives_events():
b = _make_backend()
data = asyncio.run(b.read_bytes("/data/file.txt"))
assert data == b"hello world"
def test_write_callback_receives_events():
b = _make_backend()
asyncio.run(b.write("/data/out.txt", data=b"hello"))
assert b._store.files["/data/out.txt"] == b"hello"
def test_stats_accumulate():
b = _make_backend()
asyncio.run(b.read_bytes("/data/file.txt"))
asyncio.run(b.read_bytes("/data/file.txt"))
assert b._store.files["/data/file.txt"] == b"hello world"
+31
View File
@@ -0,0 +1,31 @@
# ========= 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. =========
from mirage.resource.ram.store import RAMStore
def test_memory_store_holds_state():
store = RAMStore()
assert store.files == {}
assert store.dirs == {"/"}
assert store.modified == {}
def test_memory_store_custom_state():
files = {"/a": b"hello"}
dirs = {"/", "/dir"}
modified = {"/a": "2024-01-01"}
store = RAMStore(files=files, dirs=dirs, modified=modified)
assert store.files["/a"] == b"hello"
assert "/dir" in store.dirs
+136
View File
@@ -0,0 +1,136 @@
# ========= 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 pytest
from mirage.resource.hf_buckets import HfBucketsResource
from mirage.resource.registry import REGISTRY, build_resource
EXPECTED_RESOURCES = {
"ram",
"disk",
"redis",
"s3",
"r2",
"oci",
"supabase",
"gcs",
"minio",
"ceph",
"seaweedfs",
"wasabi",
"backblaze",
"digitalocean",
"tencent",
"aliyun",
"scaleway",
"qingstor",
"github",
"github_ci",
"linear",
"gdocs",
"gsheets",
"gslides",
"gdrive",
"slack",
"discord",
"gmail",
"trello",
"mongodb",
"postgres",
"lancedb",
"qdrant",
"notion",
"langfuse",
"ssh",
"email",
"databricks_volume",
"hf_buckets",
"hf_datasets",
"hf_models",
"hf_spaces",
"nextcloud",
"dify",
"chroma",
"onedrive",
}
def test_registry_covers_all_known_resources():
assert set(REGISTRY) == EXPECTED_RESOURCES
def test_build_ram_returns_ram_resource():
from mirage.resource.ram import RAMResource
p = build_resource("ram")
assert isinstance(p, RAMResource)
def test_build_disk_takes_raw_kwargs(tmp_path):
from mirage.resource.disk import DiskResource
p = build_resource("disk", {"root": str(tmp_path)})
assert isinstance(p, DiskResource)
def test_build_s3_uses_config_class():
from mirage.resource.s3 import S3Resource
p = build_resource(
"s3", {
"bucket": "b",
"region": "us-east-1",
"aws_access_key_id": "k",
"aws_secret_access_key": "s",
})
assert isinstance(p, S3Resource)
assert p.config.bucket == "b"
assert p.config.region == "us-east-1"
def test_build_r2_uses_r2_config():
from mirage.resource.r2 import R2Resource
p = build_resource(
"r2", {
"bucket": "b",
"account_id": "acct",
"access_key_id": "k",
"secret_access_key": "s",
})
assert isinstance(p, R2Resource)
def test_build_redis_takes_raw_kwargs():
from mirage.resource.redis import RedisResource
p = build_resource("redis", {
"url": "redis://localhost:6379/15",
"key_prefix": "test:",
})
assert isinstance(p, RedisResource)
def test_unknown_resource_raises_keyerror():
with pytest.raises(KeyError, match="unknown resource 'nonsense'"):
build_resource("nonsense")
def test_registry_module_import_is_free_of_resource_deps():
import importlib
import sys
if "mirage.resource.registry" in sys.modules:
del sys.modules["mirage.resource.registry"]
importlib.import_module("mirage.resource.registry")
def test_build_hf_buckets_resource():
r = build_resource("hf_buckets", {"bucket": "o/b"})
assert isinstance(r, HfBucketsResource)
@@ -0,0 +1,304 @@
# ========= 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 uuid
import pytest
from mirage.resource.disk import DiskResource
from mirage.resource.ram import RAMResource
from mirage.resource.redis import RedisResource
from mirage.resource.s3 import S3Config, S3Resource
REDIS_URL = os.environ.get("REDIS_URL", "")
# ── RAM ────────────────────────────────────────────────────────────────
def test_ram_get_state_shape():
p = RAMResource()
p._store.files["/a.txt"] = b"hello"
p._store.dirs.add("/sub")
state = p.get_state()
assert state["type"] == "ram"
assert "redacted_fields" not in state
assert state["files"] == {"/a.txt": b"hello"}
assert "/sub" in state["dirs"]
def test_ram_round_trip():
src = RAMResource()
src._store.files["/a.txt"] = b"hello"
src._store.files["/sub/b.txt"] = b"world"
src._store.dirs.add("/sub")
state = src.get_state()
dst = RAMResource()
dst.load_state(state)
assert dst._store.files == {"/a.txt": b"hello", "/sub/b.txt": b"world"}
assert "/sub" in dst._store.dirs
# ── Disk ───────────────────────────────────────────────────────────────
def test_disk_get_state_walks_tree(tmp_path):
root = tmp_path / "src"
root.mkdir()
(root / "a.txt").write_bytes(b"hello")
(root / "sub").mkdir()
(root / "sub" / "b.txt").write_bytes(b"world")
p = DiskResource(root=str(root))
state = p.get_state()
assert state["type"] == "disk"
assert "redacted_fields" not in state
assert state["files"] == {"a.txt": b"hello", "sub/b.txt": b"world"}
def test_disk_round_trip(tmp_path):
src_root = tmp_path / "src"
src_root.mkdir()
(src_root / "a.txt").write_bytes(b"hello")
(src_root / "sub").mkdir()
(src_root / "sub" / "b.txt").write_bytes(b"world")
state = DiskResource(root=str(src_root)).get_state()
dst_root = tmp_path / "dst"
dst_root.mkdir()
DiskResource(root=str(dst_root)).load_state(state)
assert (dst_root / "a.txt").read_bytes() == b"hello"
assert (dst_root / "sub" / "b.txt").read_bytes() == b"world"
# ── Redis ──────────────────────────────────────────────────────────────
@pytest.mark.skipif(not REDIS_URL, reason="REDIS_URL not set")
def test_redis_round_trip():
import redis as sync_redis
src_prefix = f"mirage:test:src:{uuid.uuid4().hex}:"
dst_prefix = f"mirage:test:dst:{uuid.uuid4().hex}:"
src = RedisResource(url=REDIS_URL, key_prefix=src_prefix)
dst = RedisResource(url=REDIS_URL, key_prefix=dst_prefix)
sc = sync_redis.Redis.from_url(REDIS_URL)
sc.set(f"{src_prefix}file:/a.txt", b"hello")
sc.set(f"{src_prefix}file:/sub/b.txt", b"world")
sc.sadd(f"{src_prefix}dir", "/sub")
sc.close()
state = src.get_state()
assert state["type"] == "redis"
assert state["config"]["url"] == "<REDACTED>"
assert state["config"]["key_prefix"] == src_prefix
assert state["files"] == {"/a.txt": b"hello", "/sub/b.txt": b"world"}
assert "/sub" in state["dirs"]
dst.load_state(state)
sc = sync_redis.Redis.from_url(REDIS_URL)
try:
assert sc.get(f"{dst_prefix}file:/a.txt") == b"hello"
assert sc.get(f"{dst_prefix}file:/sub/b.txt") == b"world"
assert sc.sismember(f"{dst_prefix}dir", "/sub")
finally:
# Cleanup
for prefix in (src_prefix, dst_prefix):
for key in sc.scan_iter(f"{prefix}*"):
sc.delete(key)
sc.close()
# ── S3 ─────────────────────────────────────────────────────────────────
def test_s3_get_state_redacts_creds():
cfg = S3Config(
bucket="my-bucket",
region="us-east-1",
aws_access_key_id="AKIA-REAL-KEY-FOR-TEST",
aws_secret_access_key="REAL-SECRET-KEY-CHARS",
)
p = S3Resource(cfg)
state = p.get_state()
assert state["type"] == "s3"
assert state["config"]["bucket"] == "my-bucket"
assert state["config"]["aws_access_key_id"] == "<REDACTED>"
assert state["config"]["aws_secret_access_key"] == "<REDACTED>"
assert "redacted_fields" not in state
def test_s3_no_real_creds_in_state():
secret = "TOPSECRET-VALUE-XYZ"
cfg = S3Config(
bucket="b",
region="us-east-1",
aws_access_key_id="AKIA-OBVIOUS",
aws_secret_access_key=secret,
)
state = S3Resource(cfg).get_state()
blob = repr(state)
assert secret not in blob
assert "AKIA-OBVIOUS" not in blob
assert "<REDACTED>" in blob
def test_s3_get_state_without_inline_creds_has_no_redactions():
cfg = S3Config(bucket="b", region="us-east-1", aws_profile="dev")
state = S3Resource(cfg).get_state()
assert "<REDACTED>" not in repr(state)
assert "redacted_fields" not in state
assert state["config"]["aws_profile"] == "dev"
def test_s3_load_state_is_noop():
cfg = S3Config(bucket="b", region="us-east-1")
p = S3Resource(cfg)
p.load_state({"some": "state"})
# ── all remote/token resources: cred redaction matrix ──────────────────
def _build(mod_path, cls_name, cfg_cls_name, **cfg_kwargs):
import importlib
mod = importlib.import_module(mod_path)
cfg = getattr(mod, cfg_cls_name)(**cfg_kwargs)
return getattr(mod, cls_name)(cfg)
REDACTION_CASES = [
("mirage.resource.r2", "R2Resource", "R2Config",
dict(bucket="b",
account_id="acc",
access_key_id="AKIA-R2-LEAK",
secret_access_key="R2-SECRET-LEAK"),
["AKIA-R2-LEAK", "R2-SECRET-LEAK"]),
("mirage.resource.oci", "OCIResource", "OCIConfig",
dict(bucket="b",
namespace="ns",
region="us-ashburn-1",
access_key_id="OCI-AKIA-LEAK",
secret_access_key="OCI-SECRET-LEAK"),
["OCI-AKIA-LEAK", "OCI-SECRET-LEAK"]),
("mirage.resource.supabase", "SupabaseResource", "SupabaseConfig",
dict(bucket="b",
region="us-east-1",
project_ref="ref",
access_key_id="SUPA-AKIA-LEAK",
secret_access_key="SUPA-SECRET-LEAK",
session_token="SUPA-TOKEN-LEAK"),
["SUPA-AKIA-LEAK", "SUPA-SECRET-LEAK", "SUPA-TOKEN-LEAK"]),
("mirage.resource.gcs", "GCSResource", "GCSConfig",
dict(bucket="b",
access_key_id="GCS-AKIA-LEAK",
secret_access_key="GCS-SECRET-LEAK"),
["GCS-AKIA-LEAK", "GCS-SECRET-LEAK"]),
("mirage.resource.gdrive", "GoogleDriveResource", "GoogleDriveConfig",
dict(client_id="id",
client_secret="GD-SECRET-LEAK",
refresh_token="GD-REFRESH-LEAK"),
["GD-SECRET-LEAK", "GD-REFRESH-LEAK"]),
("mirage.resource.gmail", "GmailResource", "GmailConfig",
dict(client_id="id",
client_secret="GM-SECRET-LEAK",
refresh_token="GM-REFRESH-LEAK"),
["GM-SECRET-LEAK", "GM-REFRESH-LEAK"]),
("mirage.resource.gdocs", "GDocsResource", "GDocsConfig",
dict(client_id="id",
client_secret="GDOC-SECRET-LEAK",
refresh_token="GDOC-REFRESH-LEAK"),
["GDOC-SECRET-LEAK", "GDOC-REFRESH-LEAK"]),
("mirage.resource.gsheets", "GSheetsResource", "GSheetsConfig",
dict(client_id="id",
client_secret="GSH-SECRET-LEAK",
refresh_token="GSH-REFRESH-LEAK"),
["GSH-SECRET-LEAK", "GSH-REFRESH-LEAK"]),
("mirage.resource.gslides", "GSlidesResource", "GSlidesConfig",
dict(client_id="id",
client_secret="GSL-SECRET-LEAK",
refresh_token="GSL-REFRESH-LEAK"),
["GSL-SECRET-LEAK", "GSL-REFRESH-LEAK"]),
("mirage.resource.slack", "SlackResource", "SlackConfig",
dict(token="SLACK-TOKEN-LEAK", search_token="SLACK-SEARCH-LEAK"),
["SLACK-TOKEN-LEAK", "SLACK-SEARCH-LEAK"]),
("mirage.resource.discord", "DiscordResource", "DiscordConfig",
dict(token="DISCORD-TOKEN-LEAK"), ["DISCORD-TOKEN-LEAK"]),
("mirage.resource.notion", "NotionResource", "NotionConfig",
dict(api_key="NOTION-KEY-LEAK"), ["NOTION-KEY-LEAK"]),
("mirage.resource.linear", "LinearResource", "LinearConfig",
dict(api_key="LINEAR-KEY-LEAK"), ["LINEAR-KEY-LEAK"]),
("mirage.resource.trello", "TrelloResource", "TrelloConfig",
dict(api_key="TRELLO-KEY-LEAK", api_token="TRELLO-TOKEN-LEAK"),
["TRELLO-KEY-LEAK", "TRELLO-TOKEN-LEAK"]),
("mirage.resource.github_ci", "GitHubCIResource", "GitHubCIConfig",
dict(token="GHCI-TOKEN-LEAK", owner="o", repo="r"), ["GHCI-TOKEN-LEAK"]),
("mirage.resource.email", "EmailResource", "EmailConfig",
dict(imap_host="h",
smtp_host="h",
username="u",
password="EMAIL-PWD-LEAK"), ["EMAIL-PWD-LEAK"]),
("mirage.resource.langfuse", "LangfuseResource", "LangfuseConfig",
dict(public_key="LF-PUB",
secret_key="LF-SECRET-LEAK"), ["LF-SECRET-LEAK"]),
("mirage.resource.mongodb", "MongoDBResource", "MongoDBConfig",
dict(uri="mongodb://user:pwd@h:27017/db"),
["mongodb://user:pwd@h:27017/db"]),
]
@pytest.mark.parametrize("mod,cls,cfg_cls,kwargs,leaks",
REDACTION_CASES,
ids=[c[1] for c in REDACTION_CASES])
def test_resource_get_state_redacts(mod, cls, cfg_cls, kwargs, leaks):
p = _build(mod, cls, cfg_cls, **kwargs)
state = p.get_state()
assert "redacted_fields" not in state
blob = repr(state)
for leaked in leaks:
assert leaked not in blob, (f"{cls}: leaked {leaked!r} in state")
assert "<REDACTED>" in blob
def test_github_resource_get_state_redacts(monkeypatch):
from mirage.resource.github import GitHubConfig, GitHubResource
monkeypatch.setattr(
"mirage.resource.github.github.fetch_default_branch_sync",
lambda *a, **k: "main")
monkeypatch.setattr("mirage.resource.github.github.fetch_tree_sync",
lambda *a, **k: ({}, False))
cfg = GitHubConfig(token="GH-TOKEN-LEAK")
p = GitHubResource(cfg, owner="o", repo="r", ref="main")
state = p.get_state()
assert state["config"]["token"] == "<REDACTED>"
assert "redacted_fields" not in state
blob = repr(state)
assert "GH-TOKEN-LEAK" not in blob
assert "<REDACTED>" in blob
assert state["owner"] == "o"
assert state["repo"] == "r"
assert state["ref"] == "main"
def test_ssh_no_redaction_no_override():
from mirage.resource.ssh import SSHConfig, SSHResource
cfg = SSHConfig(host="example.com", username="me")
p = SSHResource(cfg)
state = p.get_state()
assert "redacted_fields" not in state
assert "<REDACTED>" not in repr(state)
# Plain config preserved
assert state["config"]["host"] == "example.com"
assert state["config"]["username"] == "me"
+13
View File
@@ -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,51 @@
# ========= 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 pytest
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.resource.trello.config import TrelloConfig
from mirage.resource.trello.trello import TrelloResource
from mirage.types import ResourceName
@pytest.fixture
def config():
return TrelloConfig(api_key="test_key", api_token="test_token")
def test_resource_init(config):
resource = TrelloResource(config)
assert resource.caches_reads is True
def test_resource_name(config):
resource = TrelloResource(config)
assert resource.name == ResourceName.TRELLO
def test_resource_accessor(config):
resource = TrelloResource(config)
assert resource.accessor is not None
assert resource.accessor.config is config
def test_resource_index(config):
resource = TrelloResource(config)
assert isinstance(resource._index, RAMIndexCacheStore)
def test_resource_commands_registered(config):
resource = TrelloResource(config)
assert len(resource._commands) >= 10