Files
strukto-ai--mirage/python/tests/core/mongodb/test_stream.py
T
wehub-resource-sync bcbd1bdb22
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
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

343 lines
11 KiB
Python

# ========= 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 datetime as dt
import json
from unittest.mock import AsyncMock, patch
import pytest
from bson import Decimal128, ObjectId
from mirage.accessor.mongodb import MongoDBAccessor
from mirage.cache.index.ram import RAMIndexCacheStore
from mirage.core.mongodb.stream import read_stream, read_tail, watch_stream
from mirage.resource.mongodb.config import MongoDBConfig
from mirage.types import PathSpec
DOCS_PATH = "/db1/collections/coll1/documents.jsonl"
VIEW_DOCS_PATH = "/db1/views/myview/documents.jsonl"
async def _gen(items):
for item in items:
yield item
@pytest.fixture
def index():
return RAMIndexCacheStore()
@pytest.fixture
def accessor():
return MongoDBAccessor(config=MongoDBConfig(
uri="mongodb://localhost:27017"))
def _patched_iter(docs):
return patch("mirage.core.mongodb.stream.iter_documents",
new=lambda *args, **kwargs: _gen(docs))
def _patched_watch(docs):
return patch("mirage.core.mongodb.stream.iter_inserts",
new=lambda *args, **kwargs: _gen(docs))
def _path(s: str) -> PathSpec:
return PathSpec(virtual=s, directory=s, resource_path=s.strip("/"))
async def _collect(gen):
chunks = []
async for chunk in gen:
chunks.append(chunk)
return b"".join(chunks)
@pytest.mark.asyncio
async def test_read_stream_yields_one_jsonl_line_per_doc(accessor, index):
oid_a, oid_b = ObjectId(), ObjectId()
docs = [{"_id": oid_a, "title": "A"}, {"_id": oid_b, "title": "B"}]
with _patched_iter(docs):
data = await _collect(read_stream(accessor, _path(DOCS_PATH), index))
lines = [line for line in data.decode().split("\n") if line]
assert len(lines) == 2
first = json.loads(lines[0])
assert first["title"] == "A"
assert first["_id"] == {"$oid": str(oid_a)}
@pytest.mark.asyncio
async def test_read_stream_preserves_bson_types_via_extended_json(
accessor, index):
docs = [{
"_id":
ObjectId("65f0000000000000000000a1"),
"date":
dt.datetime(2026, 5, 15, 12, 30, 45, tzinfo=dt.timezone.utc),
"decimal":
Decimal128("123.456"),
}]
with _patched_iter(docs):
data = await _collect(read_stream(accessor, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["_id"] == {"$oid": "65f0000000000000000000a1"}
assert "$date" in parsed["date"]
assert parsed["decimal"] == {"$numberDecimal": "123.456"}
@pytest.mark.asyncio
async def test_read_stream_empty_yields_nothing(accessor, index):
with _patched_iter([]):
chunks = []
async for chunk in read_stream(accessor, _path(DOCS_PATH), index):
chunks.append(chunk)
assert chunks == []
@pytest.mark.asyncio
async def test_read_stream_short_circuits_when_consumer_closes(
accessor, index):
consumed: list[int] = []
async def _instrumented(*_args, **_kwargs):
for i in range(1000):
consumed.append(i)
yield {"_id": ObjectId(), "i": i}
with patch("mirage.core.mongodb.stream.iter_documents", new=_instrumented):
gen = read_stream(accessor, _path(DOCS_PATH), index)
await gen.__anext__()
await gen.aclose()
assert len(consumed) <= 2
@pytest.mark.asyncio
async def test_read_stream_works_on_view_documents_path(accessor, index):
oid = ObjectId()
docs = [{"_id": oid, "v": 1}]
with _patched_iter(docs):
data = await _collect(
read_stream(accessor, _path(VIEW_DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["v"] == 1
assert parsed["_id"] == {"$oid": str(oid)}
@pytest.mark.asyncio
async def test_read_stream_directory_path_raises(accessor, index):
with _patched_iter([]):
with pytest.raises(FileNotFoundError):
async for _ in read_stream(accessor, _path("/db1"), index):
pass
@pytest.mark.asyncio
async def test_read_stream_schema_json_path_raises(accessor, index):
with _patched_iter([]):
with pytest.raises(FileNotFoundError):
async for _ in read_stream(
accessor, _path("/db1/collections/coll1/schema.json"),
index):
pass
@pytest.mark.asyncio
async def test_read_stream_elides_configured_top_level_field(index):
cfg = MongoDBConfig(
uri="mongodb://localhost:27017",
elide_fields={"db1.coll1": ["vector"]},
)
acc = MongoDBAccessor(config=cfg)
oid = ObjectId()
docs = [{"_id": oid, "title": "hi", "vector": [0.1, 0.2, 0.3, 0.4, 0.5]}]
with _patched_iter(docs):
data = await _collect(read_stream(acc, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["title"] == "hi"
assert "vector" not in parsed
assert parsed["_id"] == {"$oid": str(oid)}
@pytest.mark.asyncio
async def test_read_stream_elides_configured_nested_path(index):
cfg = MongoDBConfig(
uri="mongodb://localhost:27017",
elide_fields={"db1.coll1": ["metadata.embedding"]},
)
acc = MongoDBAccessor(config=cfg)
docs = [{
"_id": ObjectId(),
"metadata": {
"tag": "alpha",
"embedding": [0.1] * 1024,
},
}]
with _patched_iter(docs):
data = await _collect(read_stream(acc, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["metadata"]["tag"] == "alpha"
assert "embedding" not in parsed["metadata"]
@pytest.mark.asyncio
async def test_read_stream_elision_isolated_to_configured_collection(index):
cfg = MongoDBConfig(
uri="mongodb://localhost:27017",
elide_fields={"db1.other_coll": ["vector"]},
)
acc = MongoDBAccessor(config=cfg)
docs = [{"_id": ObjectId(), "vector": [1.0, 2.0]}]
with _patched_iter(docs):
data = await _collect(read_stream(acc, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["vector"] == [1.0, 2.0]
@pytest.mark.asyncio
async def test_watch_stream_yields_one_jsonl_line_per_insert(accessor, index):
oid_a, oid_b = ObjectId(), ObjectId()
docs = [{"_id": oid_a, "title": "A"}, {"_id": oid_b, "title": "B"}]
with _patched_watch(docs):
data = await _collect(watch_stream(accessor, _path(DOCS_PATH), index))
lines = [line for line in data.decode().split("\n") if line]
assert len(lines) == 2
assert json.loads(lines[0])["_id"] == {"$oid": str(oid_a)}
assert json.loads(lines[1])["title"] == "B"
@pytest.mark.asyncio
async def test_watch_stream_preserves_bson_types(accessor, index):
docs = [{
"_id":
ObjectId("65f0000000000000000000a1"),
"date":
dt.datetime(2026, 5, 15, 12, 30, 45, tzinfo=dt.timezone.utc),
"decimal":
Decimal128("123.456"),
}]
with _patched_watch(docs):
data = await _collect(watch_stream(accessor, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["_id"] == {"$oid": "65f0000000000000000000a1"}
assert "$date" in parsed["date"]
assert parsed["decimal"] == {"$numberDecimal": "123.456"}
@pytest.mark.asyncio
async def test_watch_stream_empty_yields_nothing(accessor, index):
with _patched_watch([]):
chunks = []
async for chunk in watch_stream(accessor, _path(DOCS_PATH), index):
chunks.append(chunk)
assert chunks == []
@pytest.mark.asyncio
async def test_watch_stream_short_circuits_when_consumer_closes(
accessor, index):
consumed: list[int] = []
async def _instrumented(*_args, **_kwargs):
for i in range(1000):
consumed.append(i)
yield {"_id": ObjectId(), "i": i}
with patch("mirage.core.mongodb.stream.iter_inserts", new=_instrumented):
gen = watch_stream(accessor, _path(DOCS_PATH), index)
await gen.__anext__()
await gen.aclose()
assert len(consumed) <= 2
@pytest.mark.asyncio
async def test_watch_stream_directory_path_raises(accessor, index):
with _patched_watch([]):
with pytest.raises(FileNotFoundError):
async for _ in watch_stream(accessor, _path("/db1"), index):
pass
@pytest.mark.asyncio
async def test_read_tail_returns_docs_in_ascending_order(accessor):
docs = [{"_id": 5, "name": "e"}, {"_id": 4, "name": "d"}]
with patch(
"mirage.core.mongodb.stream.find_documents",
new_callable=AsyncMock,
return_value=list(docs),
) as fake:
data = await read_tail(accessor, _path(DOCS_PATH), 2)
lines = data.decode().splitlines()
assert '"_id": 4' in lines[0]
assert '"_id": 5' in lines[1]
assert data.endswith(b"\n")
assert fake.await_args.kwargs["limit"] == 2
assert fake.await_args.kwargs["sort"] == [("_id", -1)]
@pytest.mark.asyncio
async def test_read_tail_caps_limit_at_max_doc_limit(accessor):
with patch(
"mirage.core.mongodb.stream.find_documents",
new_callable=AsyncMock,
return_value=[],
) as fake:
data = await read_tail(accessor, _path(DOCS_PATH), 10**9)
assert data == b""
assert fake.await_args.kwargs["limit"] == accessor.config.max_doc_limit
@pytest.mark.asyncio
async def test_read_tail_applies_elision(index):
cfg = MongoDBConfig(
uri="mongodb://localhost:27017",
elide_fields={"db1.coll1": ["vector"]},
)
acc = MongoDBAccessor(config=cfg)
docs = [{"_id": 1, "title": "hi", "vector": [0.1, 0.2]}]
with patch(
"mirage.core.mongodb.stream.find_documents",
new_callable=AsyncMock,
return_value=docs,
):
data = await read_tail(acc, _path(DOCS_PATH), 1)
parsed = json.loads(data.decode().strip())
assert parsed["title"] == "hi"
assert "vector" not in parsed
@pytest.mark.asyncio
async def test_read_tail_rejects_non_documents_path(accessor):
with pytest.raises(FileNotFoundError):
await read_tail(accessor, _path("/db1/collections/coll1/schema.json"),
5)
@pytest.mark.asyncio
async def test_watch_stream_applies_elision(index):
cfg = MongoDBConfig(
uri="mongodb://localhost:27017",
elide_fields={"db1.coll1": ["vector"]},
)
acc = MongoDBAccessor(config=cfg)
oid = ObjectId()
docs = [{"_id": oid, "title": "live", "vector": [0.1, 0.2, 0.3]}]
with _patched_watch(docs):
data = await _collect(watch_stream(acc, _path(DOCS_PATH), index))
parsed = json.loads(data.decode().strip())
assert parsed["title"] == "live"
assert "vector" not in parsed
assert parsed["_id"] == {"$oid": str(oid)}