Files
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

212 lines
8.0 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 asyncio
from contextlib import ExitStack
import pytest
from mirage.resource.ram import RAMResource
from mirage.resource.s3 import S3Config, S3Resource
from mirage.types import DriftPolicy, MountMode
from mirage.workspace import Workspace
from mirage.workspace.snapshot import ContentDriftError, install_fingerprints
from tests.integration.s3_mock import patch_s3_multi
def _load(*args, **kwargs):
return asyncio.run(Workspace.load(*args, **kwargs))
def test_install_fingerprints_pins_revision_and_queues_drift():
ws = Workspace({"/m": (RAMResource(), MountMode.WRITE)},
mode=MountMode.WRITE)
entries = [
{
"path": "/m/pinned.txt",
"mount_prefix": "/m/",
"revision": "v9"
},
{
"path": "/m/checked.txt",
"mount_prefix": "/m/",
"fingerprint": "fp1"
},
]
install_fingerprints(ws, entries, DriftPolicy.STRICT)
mount = ws._registry.mount_for("/m/pinned.txt")
assert mount.revisions["/m/pinned.txt"] == "v9"
assert ws._drift_check_pending is True
queued = [path for (_, path, _) in ws._pending_drift]
assert "/m/checked.txt" in queued
def _config() -> S3Config:
return S3Config(
bucket="test-bucket",
region="us-east-1",
aws_access_key_id="fake",
aws_secret_access_key="fake",
)
def test_strict_load_raises_when_s3_etag_drifts(tmp_path):
"""Snapshot a workspace with one read on /s3, mutate the underlying
object so its ETag changes, load with strict policy: the next read
must raise ContentDriftError rather than silently serve drifted
bytes.
"""
store = {"data.csv": b"version 1 bytes\n"}
with ExitStack() as stack:
stack.enter_context(patch_s3_multi({"test-bucket": store}))
src = Workspace({"/s3": (S3Resource(_config()), MountMode.WRITE)},
mode=MountMode.WRITE)
result = asyncio.run(src.execute("cat /s3/data.csv"))
assert b"version 1" in result.stdout
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
store["data.csv"] = b"VERSION 2 DRIFTED\n"
dst = _load(snap, resources={"/s3": S3Resource(_config())})
with pytest.raises(ContentDriftError) as exc_info:
asyncio.run(dst.execute("cat /s3/data.csv"))
assert exc_info.value.path == "/s3/data.csv"
live = exc_info.value.live_fingerprint
recorded = exc_info.value.snapshot_fingerprint
assert live != recorded
def test_off_load_serves_drifted_bytes_silently(tmp_path):
"""drift_policy=OFF disables the check: the load returns the new
bytes with no error. This is the only opt-out from drift detection.
"""
store = {"data.csv": b"version 1 bytes\n"}
with ExitStack() as stack:
stack.enter_context(patch_s3_multi({"test-bucket": store}))
src = Workspace({"/s3": (S3Resource(_config()), MountMode.WRITE)},
mode=MountMode.WRITE)
asyncio.run(src.execute("cat /s3/data.csv"))
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
store["data.csv"] = b"VERSION 2 DRIFTED\n"
dst = _load(snap,
resources={"/s3": S3Resource(_config())},
drift_policy=DriftPolicy.OFF)
result = asyncio.run(dst.execute("cat /s3/data.csv"))
assert b"VERSION 2 DRIFTED" in result.stdout
def test_strict_load_passes_when_etag_unchanged(tmp_path):
"""The control case: same bytes still in S3, strict load must succeed
and serve them. Verifies the check is precise (no false positives).
"""
store = {"data.csv": b"stable bytes\n"}
with ExitStack() as stack:
stack.enter_context(patch_s3_multi({"test-bucket": store}))
src = Workspace({"/s3": (S3Resource(_config()), MountMode.WRITE)},
mode=MountMode.WRITE)
asyncio.run(src.execute("cat /s3/data.csv"))
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
dst = _load(snap, resources={"/s3": S3Resource(_config())})
result = asyncio.run(dst.execute("cat /s3/data.csv"))
assert b"stable bytes" in result.stdout
def test_unrecorded_path_skips_drift_check(tmp_path):
"""A path the agent did not read at snapshot time has no recorded
fingerprint, so it must NOT be drift-checked at load — just live-
served. Tests that drift-check is opt-in per recorded path.
"""
store = {
"read-me.txt": b"recorded\n",
"added-later.txt": b"not in snapshot\n"
}
with ExitStack() as stack:
stack.enter_context(patch_s3_multi({"test-bucket": store}))
src = Workspace({"/s3": (S3Resource(_config()), MountMode.WRITE)},
mode=MountMode.WRITE)
asyncio.run(src.execute("cat /s3/read-me.txt"))
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
dst = _load(snap, resources={"/s3": S3Resource(_config())})
result = asyncio.run(dst.execute("cat /s3/added-later.txt"))
assert b"not in snapshot" in result.stdout
def test_version_pin_serves_original_bytes_on_versioned_bucket(tmp_path):
"""On a versioned S3 bucket, snapshot captures the object's VersionId
alongside its ETag. After the live bytes drift, load with default
STRICT policy pins reads to the recorded VersionId and serves the
ORIGINAL bytes the agent saw, instead of raising drift or returning
the current head.
"""
store = {"data.csv": b"original\n"}
with ExitStack() as stack:
stack.enter_context(
patch_s3_multi({"test-bucket": store}, versioned={"test-bucket"}))
src = Workspace({"/s3": (S3Resource(_config()), MountMode.WRITE)},
mode=MountMode.WRITE)
asyncio.run(src.execute("cat /s3/data.csv"))
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
store["data.csv"] = b"mutated bytes\n"
dst = _load(snap, resources={"/s3": S3Resource(_config())})
# Cache holds snapshot bytes; clear so we hit S3 and exercise the
# pin path, not the cache path.
_drop_path_from_cache(dst, "/s3/data.csv")
result = asyncio.run(dst.execute("cat /s3/data.csv"))
assert result.stdout == b"original\n"
def _drop_path_from_cache(ws, path: str) -> None:
cache = ws._cache
cache._entries.pop(path, None)
cache._store.files.pop(path, None)
def test_live_only_mount_does_not_block_snapshot(tmp_path, caplog):
"""Workspaces with non-SUPPORTS_SNAPSHOT mounts (RAM here as a stand-
in for Gmail/Slack/Linear) snapshot fine; no fingerprints are
captured for those paths and the load layer logs an honest warning
surfacing the live-only mount list.
"""
src = Workspace({"/m": (RAMResource(), MountMode.WRITE)},
mode=MountMode.WRITE)
asyncio.run(src.execute("echo body > /m/note.txt"))
asyncio.run(src.execute("cat /m/note.txt"))
snap = tmp_path / "snap.tar"
asyncio.run(src.snapshot(snap))
with caplog.at_level("WARNING"):
_load(snap)
assert any("live-only" in r.message.lower()
or "live-only" in r.getMessage().lower()
for r in caplog.records) or any(
"no drift" in r.message.lower()
or "no drift" in r.getMessage().lower()
for r in caplog.records)