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
+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. =========
+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.core.github._client import github_headers, github_url
def test_github_headers_contains_auth():
headers = github_headers("ghp_test123")
assert headers["Authorization"] == "Bearer ghp_test123"
assert headers["Accept"] == "application/vnd.github+json"
assert "X-GitHub-Api-Version" in headers
def test_github_url_simple():
url = github_url("/repos/{owner}/{repo}/git/trees/{sha}",
owner="acme",
repo="proj",
sha="abc123")
assert url == "https://api.github.com/repos/acme/proj/git/trees/abc123"
def test_github_url_no_params():
url = github_url("/rate_limit")
assert url == "https://api.github.com/rate_limit"
+126
View File
@@ -0,0 +1,126 @@
# ========= 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 import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.github.find import find
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
def _index() -> RAMIndexCacheStore:
index = RAMIndexCacheStore()
index._entries.update({
"/src":
IndexEntry(id="a", name="src", resource_type="folder", size=None),
"/src/main.py":
IndexEntry(id="b", name="main.py", resource_type="file", size=120),
"/src/utils":
IndexEntry(id="c", name="utils", resource_type="folder", size=None),
"/src/utils/helpers.py":
IndexEntry(id="d", name="helpers.py", resource_type="file", size=80),
"/README.md":
IndexEntry(id="e", name="README.md", resource_type="file", size=50),
})
return index
def _spec(path: str, prefix: str = "") -> PathSpec:
return PathSpec(resource_path=mount_key(path, prefix),
virtual=path,
directory=path)
@pytest.mark.asyncio
async def test_find_all_from_root():
results = await find(None, _spec("/"), index=_index())
assert results == [
"/", "/README.md", "/src", "/src/main.py", "/src/utils",
"/src/utils/helpers.py"
]
@pytest.mark.asyncio
async def test_find_name_pattern():
results = await find(None, _spec("/"), name="*.py", index=_index())
assert results == ["/src/main.py", "/src/utils/helpers.py"]
@pytest.mark.asyncio
async def test_find_type_directory():
results = await find(None, _spec("/src"), type="d", index=_index())
assert results == ["/src", "/src/utils"]
@pytest.mark.asyncio
async def test_find_type_file_under_subdir():
results = await find(None, _spec("/src"), type="f", index=_index())
assert results == ["/src/main.py", "/src/utils/helpers.py"]
@pytest.mark.asyncio
async def test_find_maxdepth():
results = await find(None, _spec("/src"), maxdepth=1, index=_index())
assert results == ["/src", "/src/main.py", "/src/utils"]
@pytest.mark.asyncio
async def test_find_mindepth():
results = await find(None, _spec("/src"), mindepth=2, index=_index())
assert results == ["/src/utils/helpers.py"]
@pytest.mark.asyncio
async def test_find_strips_mount_prefix():
results = await find(None,
_spec("/github/src", prefix="/github"),
type="f",
index=_index())
assert results == ["/src/main.py", "/src/utils/helpers.py"]
@pytest.mark.asyncio
async def test_find_size_filters():
# Directories contribute size 0 to -size, so the root is excluded
# under a positive minimum (#318).
results = await find(None, _spec("/"), min_size=100, index=_index())
assert results == ["/src/main.py"]
@pytest.mark.asyncio
async def test_find_file_start_path():
results = await find(None, _spec("/src/main.py"), index=_index())
assert results == ["/src/main.py"]
@pytest.mark.asyncio
async def test_find_size_filters_file_start():
too_big = await find(None,
_spec("/src/main.py"),
max_size=50,
index=_index())
assert too_big == []
big_enough = await find(None,
_spec("/src/main.py"),
min_size=100,
index=_index())
assert big_enough == ["/src/main.py"]
@pytest.mark.asyncio
async def test_find_no_index_raises():
with pytest.raises(ValueError):
await find(None, _spec("/"), index=None)
+45
View File
@@ -0,0 +1,45 @@
# ========= 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 base64
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.github.config import GitHubConfig
from mirage.core.github.read import read_bytes
@pytest.fixture
def config():
return GitHubConfig(token="ghp_test")
@pytest.mark.asyncio
@patch("mirage.core.github.read.github_get", new_callable=AsyncMock)
async def test_read_bytes_utf8(mock_get, config):
content = b"hello world"
mock_get.return_value = {"content": base64.b64encode(content).decode()}
result = await read_bytes(config, "acme", "proj", "sha123")
assert result == content
assert result.decode("utf-8") == "hello world"
@pytest.mark.asyncio
@patch("mirage.core.github.read.github_get", new_callable=AsyncMock)
async def test_read_bytes_binary(mock_get, config):
content = bytes(range(256))
mock_get.return_value = {"content": base64.b64encode(content).decode()}
result = await read_bytes(config, "acme", "proj", "sha456")
assert result == content
+115
View File
@@ -0,0 +1,115 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock
import pytest
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.github.readdir import readdir
from mirage.core.github.tree_entry import TreeEntry
from mirage.types import PathSpec
def _index_from_tree(tree: dict[str, TreeEntry]) -> RAMIndexCacheStore:
index = RAMIndexCacheStore()
dirs: dict[str, list[tuple[str, IndexEntry]]] = defaultdict(list)
for path, entry in tree.items():
parts = path.rsplit("/", 1)
if len(parts) == 2:
parent, name = "/" + parts[0], parts[1]
else:
parent, name = "/", parts[0]
resource_type = "folder" if entry.type == "tree" else "file"
idx_entry = IndexEntry(
id=entry.sha,
name=name,
resource_type=resource_type,
size=entry.size,
)
dirs[parent].append((name, idx_entry))
for parent, entries in dirs.items():
index._entries.update({
("/" + parent.strip("/") + "/" + name).replace("//", "/"):
e
for name, e in entries
})
child_keys = sorted(
("/" + parent.strip("/") + "/" + name).replace("//", "/")
for name, _ in entries)
index._children[parent] = child_keys
index._expiry[parent] = datetime.now(
timezone.utc) + timedelta(days=365)
return index
@pytest.fixture
def tree():
return {
"src":
TreeEntry(path="src", type="tree", sha="aaa", size=None),
"src/main.py":
TreeEntry(path="src/main.py", type="blob", sha="bbb", size=120),
"src/utils":
TreeEntry(path="src/utils", type="tree", sha="ccc", size=None),
"src/utils/helpers.py":
TreeEntry(path="src/utils/helpers.py", type="blob", sha="ddd",
size=80),
"README.md":
TreeEntry(path="README.md", type="blob", sha="eee", size=50),
}
@pytest.mark.asyncio
async def test_readdir_root(tree):
index = _index_from_tree(tree)
result = await readdir(
None, PathSpec(resource_path="", virtual="/", directory="/"), index)
assert result == ["/README.md", "/src"]
@pytest.mark.asyncio
async def test_readdir_subdirectory(tree):
index = _index_from_tree(tree)
result = await readdir(
None, PathSpec(resource_path="src", virtual="/src", directory="/src"),
index)
assert result == ["/src/main.py", "/src/utils"]
@pytest.mark.asyncio
async def test_readdir_nested(tree):
index = _index_from_tree(tree)
result = await readdir(
None,
PathSpec(resource_path="src/utils",
virtual="/src/utils",
directory="/src/utils"), index)
assert result == ["/src/utils/helpers.py"]
@pytest.mark.asyncio
async def test_readdir_missing_directory(tree):
index = _index_from_tree(tree)
accessor = MagicMock()
accessor.truncated = False
with pytest.raises(FileNotFoundError):
await readdir(
accessor,
PathSpec(resource_path="nonexistent",
virtual="/nonexistent",
directory="/nonexistent"), index)
+45
View File
@@ -0,0 +1,45 @@
# ========= 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.core.github.config import GitHubConfig
from mirage.core.github.repo import fetch_default_branch
@pytest.fixture
def config():
return GitHubConfig(token="ghp_test")
@pytest.mark.asyncio
@patch("mirage.core.github.repo.github_get", new_callable=AsyncMock)
async def test_fetch_default_branch_main(mock_get, config):
mock_get.return_value = {"default_branch": "main"}
result = await fetch_default_branch(config, "acme", "proj")
assert result == "main"
mock_get.assert_awaited_once_with(config.token,
"/repos/{owner}/{repo}",
owner="acme",
repo="proj")
@pytest.mark.asyncio
@patch("mirage.core.github.repo.github_get", new_callable=AsyncMock)
async def test_fetch_default_branch_master(mock_get, config):
mock_get.return_value = {"default_branch": "master"}
result = await fetch_default_branch(config, "acme", "proj")
assert result == "master"
+77
View File
@@ -0,0 +1,77 @@
# ========= 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 types import SimpleNamespace
import pytest
from mirage.core.github.scope import (count_scope_files, is_repo_root,
scope_relative_key)
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
@pytest.fixture
def entries():
def _f():
return SimpleNamespace(resource_type="file")
def _d():
return SimpleNamespace(resource_type="folder")
return {
"/README.md": _f(),
"/src": _d(),
"/src/main.py": _f(),
"/src/utils.py": _f(),
"/src/models": _d(),
"/src/models/user.py": _f(),
}
def test_scope_relative_key_strips_mount_prefix():
path = PathSpec(resource_path=mount_key("/gh/src", "/gh"),
virtual="/gh/src",
directory="/gh/src")
assert scope_relative_key(path) == "/src"
def test_scope_relative_key_root_becomes_slash():
path = PathSpec(resource_path=mount_key("/gh", "/gh"),
virtual="/gh",
directory="/gh")
assert scope_relative_key(path) == "/"
def test_is_repo_root():
assert is_repo_root("/")
assert is_repo_root("")
assert not is_repo_root("/src")
def test_count_scope_files_root_counts_all(entries):
assert count_scope_files(entries, "/") == 4
def test_count_scope_files_subdir(entries):
assert count_scope_files(entries, "/src") == 3
def test_count_scope_files_single_file(entries):
assert count_scope_files(entries, "/src/main.py") == 1
def test_count_scope_files_missing(entries):
assert count_scope_files(entries, "/nope") == 0
+117
View File
@@ -0,0 +1,117 @@
# ========= 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.core.github.config import GitHubConfig
from mirage.core.github.search import SearchResult, narrow_paths, search_code
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
@pytest.fixture
def config():
return GitHubConfig(token="ghp_test")
@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_basic(mock_get, config):
mock_get.return_value = {
"items": [
{
"path": "src/main.py",
"sha": "aaa"
},
{
"path": "src/utils.py",
"sha": "bbb"
},
]
}
result = await search_code(config, "acme", "proj", "import os")
assert len(result) == 2
assert result[0] == SearchResult(path="src/main.py", sha="aaa")
mock_get.assert_awaited_once_with(config.token,
"/search/code",
params={"q": "import os repo:acme/proj"})
@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_empty_results(mock_get, config):
mock_get.return_value = {"items": []}
result = await search_code(config, "acme", "proj", "nonexistent")
assert result == []
@pytest.mark.asyncio
@patch("mirage.core.github.search.github_get", new_callable=AsyncMock)
async def test_search_code_with_path_filter(mock_get, config):
mock_get.return_value = {"items": [{"path": "src/main.py", "sha": "aaa"}]}
await search_code(config, "acme", "proj", "import os", path_filter="src/")
mock_get.assert_awaited_once_with(
config.token,
"/search/code",
params={"q": "import os repo:acme/proj path:src/"})
@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_strips_leading_slash_in_filter(
mock_search, config):
mock_search.return_value = [SearchResult(path="src/main.py", sha="aaa")]
paths = [PathSpec(resource_path="src", virtual="/src", directory="/src")]
await narrow_paths(config, "acme", "proj", "import", paths)
_, kwargs = mock_search.await_args
assert kwargs["path_filter"] == "src"
@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_root_uses_no_filter(mock_search, config):
mock_search.return_value = [SearchResult(path="src/main.py", sha="aaa")]
paths = [PathSpec(resource_path="", virtual="/", directory="/")]
await narrow_paths(config, "acme", "proj", "import", paths)
_, kwargs = mock_search.await_args
assert kwargs["path_filter"] is None
@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_normalizes_results_with_leading_slash(
mock_search, config):
mock_search.return_value = [
SearchResult(path="src/main.py", sha="aaa"),
SearchResult(path="src/utils.py", sha="bbb"),
]
paths = [
PathSpec(resource_path=mount_key("/gh", "/gh"),
virtual="/gh",
directory="/gh")
]
out = await narrow_paths(config, "acme", "proj", "import", paths)
assert [p.virtual for p in out] == ["/gh/src/main.py", "/gh/src/utils.py"]
assert [p.resource_path for p in out] == ["src/main.py", "src/utils.py"]
@pytest.mark.asyncio
@patch("mirage.core.github.search.search_code", new_callable=AsyncMock)
async def test_narrow_paths_logs_and_continues_on_error(mock_search, config):
mock_search.side_effect = RuntimeError("boom")
paths = [PathSpec(resource_path="src", virtual="/src", directory="/src")]
out = await narrow_paths(config, "acme", "proj", "import", paths)
assert out == []
+125
View File
@@ -0,0 +1,125 @@
# ========= 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 collections import defaultdict
from datetime import datetime, timedelta, timezone
import pytest
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.github.stat import stat
from mirage.core.github.tree_entry import TreeEntry
from mirage.types import FileType, PathSpec
def _index_from_tree(tree: dict[str, TreeEntry]) -> RAMIndexCacheStore:
index = RAMIndexCacheStore()
dirs: dict[str, list[tuple[str, IndexEntry]]] = defaultdict(list)
for path, entry in tree.items():
parts = path.rsplit("/", 1)
if len(parts) == 2:
parent, name = "/" + parts[0], parts[1]
else:
parent, name = "/", parts[0]
resource_type = "folder" if entry.type == "tree" else "file"
idx_entry = IndexEntry(
id=entry.sha,
name=name,
resource_type=resource_type,
size=entry.size,
)
dirs[parent].append((name, idx_entry))
for parent, entries in dirs.items():
index._entries.update({
("/" + parent.strip("/") + "/" + name).replace("//", "/"):
e
for name, e in entries
})
child_keys = sorted(
("/" + parent.strip("/") + "/" + name).replace("//", "/")
for name, _ in entries)
index._children[parent] = child_keys
index._expiry[parent] = datetime.now(
timezone.utc) + timedelta(days=365)
return index
@pytest.fixture
def tree():
return {
"src":
TreeEntry(path="src", type="tree", sha="aaa", size=None),
"src/main.py":
TreeEntry(path="src/main.py", type="blob", sha="bbb", size=120),
"README.md":
TreeEntry(path="README.md", type="blob", sha="ccc", size=50),
}
@pytest.mark.asyncio
async def test_stat_file(tree):
index = _index_from_tree(tree)
result = await stat(
None,
PathSpec(resource_path="src/main.py",
virtual="/src/main.py",
directory="/src/main.py"), index)
assert result.name == "main.py"
assert result.size == 120
assert result.type == FileType.TEXT
assert result.extra == {"sha": "bbb"}
@pytest.mark.asyncio
async def test_stat_directory(tree):
index = _index_from_tree(tree)
result = await stat(
None, PathSpec(resource_path="src", virtual="/src", directory="/src"),
index)
assert result.name == "src"
assert result.type == FileType.DIRECTORY
@pytest.mark.asyncio
async def test_stat_root(tree):
index = _index_from_tree(tree)
result = await stat(None,
PathSpec(resource_path="", virtual="/", directory="/"),
index)
assert result.name == "/"
assert result.type == FileType.DIRECTORY
@pytest.mark.asyncio
async def test_stat_not_found(tree):
index = _index_from_tree(tree)
with pytest.raises(FileNotFoundError):
await stat(
None,
PathSpec(resource_path="nonexistent.py",
virtual="/nonexistent.py",
directory="/nonexistent.py"), index)
@pytest.mark.asyncio
async def test_stat_strip_slashes(tree):
index = _index_from_tree(tree)
result = await stat(
None,
PathSpec(resource_path="README.md",
virtual="/README.md",
directory="/README.md"), index)
assert result.name == "README.md"
assert result.size == 50
@@ -0,0 +1,66 @@
# ========= 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, timedelta, timezone
import pytest
from mirage.cache.index import IndexEntry
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.github.stat import stat
from mirage.types import PathSpec
@pytest.mark.asyncio
async def test_github_stat_returns_fingerprint_from_blob_sha():
index = RAMIndexCacheStore()
entry = IndexEntry(
id="abc123",
name="main.py",
resource_type="file",
size=42,
)
index._entries["/src/main.py"] = entry
index._children["/src"] = ["/src/main.py"]
index._expiry["/src"] = datetime.now(timezone.utc) + timedelta(days=365)
result = await stat(
None,
PathSpec(resource_path="src/main.py",
virtual="/src/main.py",
directory="/src/main.py"),
index,
)
assert result.fingerprint == "abc123"
assert result.extra == {"sha": "abc123"}
@pytest.mark.asyncio
async def test_github_stat_directory_has_no_fingerprint():
index = RAMIndexCacheStore()
entry = IndexEntry(
id="dir_sha",
name="src",
resource_type="folder",
)
index._entries["/src"] = entry
result = await stat(
None,
PathSpec(resource_path="src", virtual="/src", directory="/src"),
index,
)
assert result.fingerprint is None
+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 logging
from unittest.mock import AsyncMock, patch
import pytest
from mirage.core.github.config import GitHubConfig
from mirage.core.github.tree import fetch_tree
from mirage.core.github.tree_entry import TreeEntry
@pytest.fixture
def config():
return GitHubConfig(token="ghp_test")
@pytest.mark.asyncio
@patch("mirage.core.github.tree.github_get", new_callable=AsyncMock)
async def test_fetch_tree_parses_entries(mock_get, config):
mock_get.return_value = {
"truncated":
False,
"tree": [
{
"path": "src",
"type": "tree",
"sha": "aaa",
"size": None
},
{
"path": "src/main.py",
"type": "blob",
"sha": "bbb",
"size": 120
},
],
}
tree, truncated = await fetch_tree(config, "acme", "proj", "main")
assert "src" in tree
assert "src/main.py" in tree
assert tree["src"] == TreeEntry(path="src",
type="tree",
sha="aaa",
size=None)
assert tree["src/main.py"] == TreeEntry(path="src/main.py",
type="blob",
sha="bbb",
size=120)
@pytest.mark.asyncio
@patch("mirage.core.github.tree.github_get", new_callable=AsyncMock)
async def test_fetch_tree_truncation_warning(mock_get, config, caplog):
mock_get.return_value = {"truncated": True, "tree": []}
with caplog.at_level(logging.WARNING):
await fetch_tree(config, "acme", "proj", "main")
assert "truncated" in caplog.text
@pytest.mark.asyncio
@patch("mirage.core.github.tree.github_get", new_callable=AsyncMock)
async def test_fetch_tree_passes_params(mock_get, config):
mock_get.return_value = {"tree": []}
await fetch_tree(config, "acme", "proj", "v1")
mock_get.assert_awaited_once_with(
config.token,
"/repos/{owner}/{repo}/git/trees/{ref}",
owner="acme",
repo="proj",
ref="v1",
params={"recursive": "1"},
)