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. =========
+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.core.notion._client import NotionAPIError, notion_headers
from mirage.resource.notion.config import NotionConfig
def test_notion_headers():
config = NotionConfig(api_key="ntn_test123")
headers = notion_headers(config)
assert headers["Authorization"] == "Bearer ntn_test123"
assert headers["Notion-Version"] == "2022-06-28"
assert headers["Content-Type"] == "application/json"
def test_notion_api_error():
err = NotionAPIError("bad request", status=400, code="invalid_json")
assert str(err) == "bad request"
assert err.status == 400
assert err.code == "invalid_json"
+168
View File
@@ -0,0 +1,168 @@
# ========= 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
import pytest
from mirage.accessor.notion import NotionAccessor
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.notion import read as notion_read
from mirage.core.notion import readdir as notion_readdir
from mirage.core.notion import stat as notion_stat
from mirage.core.notion.read import read
from mirage.core.notion.readdir import readdir
from mirage.core.notion.stat import stat
from mirage.resource.notion.config import NotionConfig
from mirage.types import FileType, PathSpec
DATABASE_ID = "db123"
ROW_ID = "row456"
@pytest.fixture
def accessor():
return NotionAccessor(NotionConfig(api_key="ntn_test"))
@pytest.mark.asyncio
async def test_readdir_root_includes_pages_and_databases(accessor):
entries = await readdir(accessor, "/")
assert entries == ["/pages", "/databases"]
@pytest.mark.asyncio
async def test_readdir_databases_lists_database_directories(
accessor, monkeypatch):
monkeypatch.setattr(
notion_readdir,
"search_databases",
AsyncMock(return_value=[{
"id": DATABASE_ID,
"title": [{
"plain_text": "Tasks"
}],
"last_edited_time": "2026-01-01T00:00:00Z",
}]),
)
entries = await readdir(accessor, "/databases", RAMIndexCacheStore())
assert entries == [f"/databases/Tasks__{DATABASE_ID}"]
@pytest.mark.asyncio
async def test_readdir_database_lists_row_pages(accessor, monkeypatch):
monkeypatch.setattr(
notion_readdir,
"query_database",
AsyncMock(return_value=[{
"object": "page",
"id": ROW_ID,
"properties": {
"Name": {
"type": "title",
"title": [{
"plain_text": "Row A"
}],
}
},
"last_edited_time": "2026-01-02T00:00:00Z",
}]),
)
entries = await readdir(accessor, f"/databases/Tasks__{DATABASE_ID}")
assert entries == [
f"/databases/Tasks__{DATABASE_ID}/database.json",
f"/databases/Tasks__{DATABASE_ID}/Row_A__{ROW_ID}",
]
@pytest.mark.asyncio
async def test_read_database_json_returns_database_metadata(
accessor, monkeypatch):
monkeypatch.setattr(
notion_read,
"get_database",
AsyncMock(
return_value={
"id": DATABASE_ID,
"title": [{
"plain_text": "Tasks"
}],
"properties": {
"Name": {
"type": "title"
}
},
"last_edited_time": "2026-01-03T00:00:00Z",
}),
)
data = await read(accessor,
f"/databases/Tasks__{DATABASE_ID}/database.json")
import json
decoded = json.loads(data)
assert decoded["database_id"] == DATABASE_ID
assert decoded["title"] == "Tasks"
assert decoded["properties"] == {"Name": {"type": "title"}}
assert "rows" not in decoded
assert "row_count" not in decoded
@pytest.mark.asyncio
async def test_readdir_database_row_lists_page_json_and_child_pages(
accessor,
monkeypatch,
):
monkeypatch.setattr(
notion_readdir,
"list_block_children",
AsyncMock(return_value=[{
"id": "child789",
"type": "child_page",
"child_page": {
"title": "Child"
},
}]),
)
entries = await readdir(
accessor, f"/databases/Tasks__{DATABASE_ID}/Row-A__{ROW_ID}")
assert entries == [
f"/databases/Tasks__{DATABASE_ID}/Row-A__{ROW_ID}/page.json",
f"/databases/Tasks__{DATABASE_ID}/Row-A__{ROW_ID}/Child__child789",
]
@pytest.mark.asyncio
async def test_stat_database_dir_uses_database_metadata(accessor, monkeypatch):
monkeypatch.setattr(
notion_stat,
"get_database",
AsyncMock(return_value={"last_edited_time": "2026-01-03T00:00:00Z"}),
)
result = await stat(accessor, f"/databases/Tasks__{DATABASE_ID}")
assert result.name == f"Tasks__{DATABASE_ID}"
assert result.type == FileType.DIRECTORY
assert result.modified == "2026-01-03T00:00:00Z"
assert result.extra == {"database_id": DATABASE_ID}
@pytest.mark.asyncio
async def test_stat_database_row_dir(accessor):
result = await stat(
accessor,
PathSpec.from_str_path(
f"/databases/Tasks__{DATABASE_ID}/Row-A__{ROW_ID}"),
)
assert result.name == f"Row-A__{ROW_ID}"
assert result.type == FileType.DIRECTORY
assert result.extra == {"page_id": ROW_ID}
+106
View File
@@ -0,0 +1,106 @@
# ========= 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.core.notion import find as find_mod
from mirage.types import FileStat, FileType, PathSpec
from mirage.utils.key_prefix import mount_key
_DIRS = {"/db", "/db/sub"}
_FILES = {
"/db/page1.md": 10,
"/db/sub/page2.md": 20,
}
_CHILDREN = {
"/db": ["/db/page1.md", "/db/sub"],
"/db/sub": ["/db/sub/page2.md"],
}
async def _fake_readdir(accessor, path, index):
key = path.virtual if isinstance(path, PathSpec) else path
key = "/" + key.strip("/") if key.strip("/") else "/"
return _CHILDREN.get(key, [])
async def _fake_stat(accessor, path, index=None):
key = path.virtual if isinstance(path, PathSpec) else path
key = "/" + key.strip("/") if key.strip("/") else "/"
if key in _DIRS:
return FileStat(name=key.rsplit("/", 1)[-1] or "/",
type=FileType.DIRECTORY)
return FileStat(name=key.rsplit("/", 1)[-1],
type=FileType.TEXT,
size=_FILES.get(key))
@pytest.fixture(autouse=True)
def _patch(monkeypatch):
monkeypatch.setattr(find_mod, "readdir", _fake_readdir)
monkeypatch.setattr(find_mod, "stat", _fake_stat)
@pytest.mark.asyncio
async def test_find_all():
out = await find_mod.find(None, PathSpec.from_str_path("/db"))
assert out == ["/db", "/db/page1.md", "/db/sub", "/db/sub/page2.md"]
@pytest.mark.asyncio
async def test_find_name_matches_mount_root_start_path():
spec = PathSpec(resource_path=mount_key("/db", "/db"),
virtual="/db",
directory="/db")
out = await find_mod.find(None, spec, name="db")
assert out == ["/"]
@pytest.mark.asyncio
async def test_find_type_file():
out = await find_mod.find(None, PathSpec.from_str_path("/db"), type="f")
assert out == ["/db/page1.md", "/db/sub/page2.md"]
@pytest.mark.asyncio
async def test_find_type_dir():
out = await find_mod.find(None, PathSpec.from_str_path("/db"), type="d")
assert out == ["/db", "/db/sub"]
@pytest.mark.asyncio
async def test_find_name_glob():
out = await find_mod.find(None, PathSpec.from_str_path("/db"), name="*.md")
assert out == ["/db/page1.md", "/db/sub/page2.md"]
@pytest.mark.asyncio
async def test_find_maxdepth():
out = await find_mod.find(None, PathSpec.from_str_path("/db"), maxdepth=1)
assert out == ["/db", "/db/page1.md", "/db/sub"]
@pytest.mark.asyncio
async def test_find_mindepth():
out = await find_mod.find(None, PathSpec.from_str_path("/db"), mindepth=1)
assert out == ["/db/page1.md", "/db/sub", "/db/sub/page2.md"]
@pytest.mark.asyncio
async def test_find_min_size():
out = await find_mod.find(None,
PathSpec.from_str_path("/db"),
type="f",
min_size=15)
assert out == ["/db/sub/page2.md"]
+72
View File
@@ -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 pytest
import pytest_asyncio
from mirage.accessor.notion import NotionAccessor
from mirage.cache.index import IndexEntry, RAMIndexCacheStore
from mirage.core.notion.glob import resolve_glob
from mirage.types import PathSpec
def page_entry(name: str) -> IndexEntry:
return IndexEntry(id=name,
name=name,
resource_type="notion/page",
vfs_name=name)
def file_entry(name: str) -> IndexEntry:
return IndexEntry(id=name, name=name, resource_type="file", vfs_name=name)
def glob_spec(virtual: str) -> PathSpec:
last_slash = virtual.rfind("/")
return PathSpec(
virtual=virtual,
directory=virtual[:last_slash + 1],
resource_path=virtual[len("/notion"):].strip("/"),
pattern=virtual[last_slash + 1:],
resolved=False,
)
@pytest_asyncio.fixture
async def index():
store = RAMIndexCacheStore()
await store.set_dir("/pages", [
("Demo_page__uuid1", page_entry("Demo_page__uuid1")),
("Roadmap__uuid2", page_entry("Roadmap__uuid2")),
])
await store.set_dir("/pages/Demo_page__uuid1", [
("page.json", file_entry("page.json")),
("page.md", file_entry("page.md")),
])
return store
@pytest.mark.asyncio
async def test_mid_path_glob_expands_from_index(index):
spec = glob_spec("/notion/pages/Demo_page__*/page.md")
result = await resolve_glob(NotionAccessor(config=None), [spec], index)
assert [p.virtual
for p in result] == ["/notion/pages/Demo_page__uuid1/page.md"]
@pytest.mark.asyncio
async def test_last_component_glob(index):
spec = glob_spec("/notion/pages/Demo*")
result = await resolve_glob(NotionAccessor(config=None), [spec], index)
assert [p.virtual for p in result] == ["/notion/pages/Demo_page__uuid1"]
+123
View File
@@ -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 json
import pytest
from mirage.core.notion.normalize import normalize_page, to_json_bytes
from mirage.core.notion.pathing import page_dirname, split_suffix_id
class TestSplitSuffixId:
def test_basic(self):
label, oid = split_suffix_id("my-page__abc123")
assert label == "my-page"
assert oid == "abc123"
def test_with_suffix(self):
label, oid = split_suffix_id("my-page__abc123.json", suffix=".json")
assert label == "my-page"
assert oid == "abc123"
def test_no_separator_raises(self):
with pytest.raises(FileNotFoundError):
split_suffix_id("noid")
def test_wrong_suffix_raises(self):
with pytest.raises(FileNotFoundError):
split_suffix_id("my-page__abc.json", suffix=".md")
class TestPageDirname:
def test_with_title(self):
page = {
"id": "abc-123",
"properties": {
"title": {
"type": "title",
"title": [{
"plain_text": "Hello World"
}],
},
},
}
result = page_dirname(page)
assert result.endswith("__abc-123")
assert "hello" in result.lower()
def test_untitled(self):
page = {"id": "xyz", "properties": {}}
result = page_dirname(page)
assert result == "untitled__xyz"
class TestNormalizePage:
def test_basic(self):
page = {
"id": "abc-123",
"url": "https://notion.so/abc123",
"created_time": "2026-01-01T00:00:00.000Z",
"last_edited_time": "2026-04-15T00:00:00.000Z",
"parent": {
"type": "workspace",
"workspace": True
},
"archived": False,
"created_by": {
"id": "user1"
},
"last_edited_by": {
"id": "user2"
},
"properties": {
"title": {
"type": "title",
"title": [{
"plain_text": "Test"
}]
},
},
}
blocks = [
{
"type": "paragraph",
"paragraph": {
"rich_text": [{
"plain_text": "Hello",
"annotations": {}
}]
}
},
{
"type": "child_page",
"child_page": {
"title": "Sub"
}
},
]
result = normalize_page(page, blocks)
assert result["page_id"] == "abc-123"
assert result["title"] == "Test"
assert "Hello" in result["markdown"]
assert len(result["blocks"]) == 1
def test_to_json_bytes(self):
data = to_json_bytes({"key": "value"})
assert isinstance(data, bytes)
parsed = json.loads(data)
assert parsed["key"] == "value"
+90
View File
@@ -0,0 +1,90 @@
# ========= 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.cache.index import RAMIndexCacheStore
from mirage.core.notion import readdir as readdir_mod
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
_ACCESSOR = SimpleNamespace(config=None)
TOP_ID = "aaaa1111-2222-3333-4444-555566667777"
_TOP_PAGE = {
"id": TOP_ID,
"parent": {
"type": "workspace"
},
"last_edited_time": "2026-01-02T00:00:00.000Z",
"properties": {
"title": {
"type": "title",
"title": [{
"type": "text",
"plain_text": "Top1"
}],
}
},
}
async def _fake_search_pages(config):
return [_TOP_PAGE]
@pytest.fixture(autouse=True)
def _patch(monkeypatch):
monkeypatch.setattr(readdir_mod, "search_pages", _fake_search_pages)
def _spec(original: str, prefix: str = "") -> PathSpec:
return PathSpec(resource_path=mount_key(original, prefix),
virtual=original,
directory=original)
@pytest.mark.asyncio
async def test_root_lists_pages_with_prefix():
out = await readdir_mod.readdir(_ACCESSOR, _spec("/notion", "/notion"))
assert out == ["/notion/pages", "/notion/databases"]
@pytest.mark.asyncio
async def test_pages_listing_cold():
out = await readdir_mod.readdir(_ACCESSOR, _spec("/pages"))
assert out == [f"/pages/Top1__{TOP_ID}"]
@pytest.mark.asyncio
async def test_pages_listing_keeps_prefix_on_warm_cache_hit():
index = RAMIndexCacheStore()
spec = _spec("/notion/pages", "/notion")
cold = await readdir_mod.readdir(_ACCESSOR, spec, index)
warm = await readdir_mod.readdir(_ACCESSOR, spec, index)
assert warm == cold
assert warm == [f"/notion/pages/Top1__{TOP_ID}"]
@pytest.mark.asyncio
async def test_pages_listing_stores_remote_time():
index = RAMIndexCacheStore()
spec = _spec("/notion/pages", "/notion")
await readdir_mod.readdir(_ACCESSOR, spec, index)
lookup = await index.get(f"/pages/Top1__{TOP_ID}")
assert lookup.entry is not None
assert lookup.entry.remote_time == "2026-01-02T00:00:00.000Z"
+200
View File
@@ -0,0 +1,200 @@
# ========= 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.notion.render import (_block_to_md, _rich_text_to_md,
blocks_to_markdown)
class TestRichTextToMd:
def test_plain(self):
rt = [{"plain_text": "hello", "annotations": {}}]
assert _rich_text_to_md(rt) == "hello"
def test_bold(self):
rt = [{"plain_text": "bold", "annotations": {"bold": True}}]
assert _rich_text_to_md(rt) == "**bold**"
def test_italic(self):
rt = [{"plain_text": "em", "annotations": {"italic": True}}]
assert _rich_text_to_md(rt) == "*em*"
def test_code(self):
rt = [{"plain_text": "x", "annotations": {"code": True}}]
assert _rich_text_to_md(rt) == "`x`"
def test_link(self):
rt = [{
"plain_text": "click",
"annotations": {},
"href": "https://example.com"
}]
assert _rich_text_to_md(rt) == "[click](https://example.com)"
def test_multiple(self):
rt = [
{
"plain_text": "a ",
"annotations": {}
},
{
"plain_text": "b",
"annotations": {
"bold": True
}
},
]
assert _rich_text_to_md(rt) == "a **b**"
class TestBlockToMd:
def test_paragraph(self):
block = {
"type": "paragraph",
"paragraph": {
"rich_text": [{
"plain_text": "hi",
"annotations": {}
}]
}
}
assert _block_to_md(block) == "hi"
def test_heading_1(self):
block = {
"type": "heading_1",
"heading_1": {
"rich_text": [{
"plain_text": "Title",
"annotations": {}
}]
}
}
assert _block_to_md(block) == "# Title"
def test_heading_2(self):
block = {
"type": "heading_2",
"heading_2": {
"rich_text": [{
"plain_text": "Sub",
"annotations": {}
}]
}
}
assert _block_to_md(block) == "## Sub"
def test_bulleted_list_item(self):
block = {
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{
"plain_text": "item",
"annotations": {}
}]
}
}
assert _block_to_md(block) == "- item"
def test_to_do_checked(self):
block = {
"type": "to_do",
"to_do": {
"rich_text": [{
"plain_text": "done",
"annotations": {}
}],
"checked": True
}
}
assert _block_to_md(block) == "- [x] done"
def test_code_block(self):
block = {
"type": "code",
"code": {
"rich_text": [{
"plain_text": "print(1)",
"annotations": {}
}],
"language": "python"
}
}
assert _block_to_md(block) == "```python\nprint(1)\n```"
def test_divider(self):
block = {"type": "divider", "divider": {}}
assert _block_to_md(block) == "---"
def test_child_page_empty(self):
block = {"type": "child_page", "child_page": {"title": "Sub"}}
assert _block_to_md(block) == ""
class TestBlocksToMarkdown:
def test_multiple_blocks(self):
blocks = [
{
"type": "heading_1",
"heading_1": {
"rich_text": [{
"plain_text": "Title",
"annotations": {}
}]
}
},
{
"type": "paragraph",
"paragraph": {
"rich_text": [{
"plain_text": "body",
"annotations": {}
}]
}
},
]
result = blocks_to_markdown(blocks)
assert result == "# Title\n\nbody\n"
def test_empty(self):
assert blocks_to_markdown([]) == ""
def test_nested_children_indent(self):
blocks = [
{
"type":
"bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{
"plain_text": "parent",
"annotations": {}
}]
},
"children": [
{
"type": "bulleted_list_item",
"bulleted_list_item": {
"rich_text": [{
"plain_text": "child",
"annotations": {}
}]
},
},
],
},
]
result = blocks_to_markdown(blocks)
assert result == "- parent\n\n - child\n"
+69
View File
@@ -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. =========
from types import SimpleNamespace
import pytest
from mirage.cache.index import RAMIndexCacheStore
from mirage.core.notion import readdir as readdir_mod
from mirage.core.notion.stat import stat
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_key
_ACCESSOR = SimpleNamespace(config=None)
TOP_ID = "aaaa1111-2222-3333-4444-555566667777"
_TOP_PAGE = {
"id": TOP_ID,
"parent": {
"type": "workspace"
},
"last_edited_time": "2026-01-02T00:00:00.000Z",
"properties": {
"title": {
"type": "title",
"title": [{
"type": "text",
"plain_text": "Top1"
}],
}
},
}
async def _fake_search_pages(config):
return [_TOP_PAGE]
@pytest.fixture(autouse=True)
def _patch(monkeypatch):
monkeypatch.setattr(readdir_mod, "search_pages", _fake_search_pages)
def _spec(original: str, prefix: str = "") -> PathSpec:
return PathSpec(resource_path=mount_key(original, prefix),
virtual=original,
directory=original)
@pytest.mark.asyncio
async def test_stat_page_returns_modified_from_index():
index = RAMIndexCacheStore()
await readdir_mod.readdir(_ACCESSOR, _spec("/notion/pages", "/notion"),
index)
spec = _spec(f"/notion/pages/Top1__{TOP_ID}", "/notion")
s = await stat(_ACCESSOR, spec, index)
assert s.modified == "2026-01-02T00:00:00.000Z"