Files
2026-07-13 13:12:33 +08:00

1657 lines
56 KiB
Python

from __future__ import annotations
import json
import multiprocessing
import os
import sys
import uuid
from contextlib import contextmanager
from pathlib import Path
import pytest
from opensquilla.recovery import (
ProfileOperationLock,
RecoveryError,
StaleRecoveryTransactionError,
inspect_profile,
)
from opensquilla.recovery.cleanup import (
_path_identity_payload,
abandon_cleanup_transaction,
cleanup_apply,
cleanup_inspect,
)
def _home(path: Path, value: str) -> Path:
workspace = path / "workspace"
workspace.mkdir(parents=True)
(workspace / "SOUL.md").write_text(value + "\n", encoding="utf-8")
(path / "config.toml").write_text('workspace_dir = "workspace"\n', encoding="utf-8")
return path
def _recovery_profile(user_data: Path, recovery_id: str, value: str) -> Path:
root = user_data / "recovery-profiles" / recovery_id
_home(root / "opensquilla", value)
(root / "desktop-credential.json").write_text("{}\n", encoding="utf-8")
(root / "logs").mkdir()
(root / "logs" / "desktop.log").write_text("synthetic\n", encoding="utf-8")
return root
def _backup(user_data: Path, primary: Path) -> Path:
transaction_id = str(uuid.uuid4())
backup = primary.with_name(f"{primary.name}.backup.{transaction_id}")
_home(backup, "backup")
history = {
"schema_version": 1,
"backups": [
{
"transaction_id": transaction_id,
"committed_at": "2026-07-11T00:00:00+00:00",
"source": str(user_data / "synthetic-source"),
"target": str(primary),
"backup": str(backup),
"source_identity": _path_identity_payload(backup),
"target_identity": _path_identity_payload(primary),
"backup_identity": _path_identity_payload(backup),
}
],
}
(user_data / "profile-replacement-history.json").write_text(
json.dumps(history, indent=2) + "\n",
encoding="utf-8",
)
return backup
def _desktop_primary(user_data: Path) -> Path:
primary = _home(user_data / "opensquilla", "primary")
(user_data / "desktop-credential.json").write_text("{}\n", encoding="utf-8")
(user_data / "logs").mkdir()
(user_data / "logs" / "desktop.log").write_text("synthetic\n", encoding="utf-8")
(user_data / "desktop-profile-context.json").write_text(
'{"schema_version":1,"active_profile_kind":"primary"}\n',
encoding="utf-8",
)
return primary
def _hold_profile_lock(home: str, state_root: str, ready, release) -> None:
os.environ["OPENSQUILLA_USER_STATE_DIR"] = state_root
with ProfileOperationLock(home):
ready.set()
release.wait(10)
def _hold_gateway(home: str, ready, release) -> None:
from opensquilla.gateway.pidlock import GatewayPidLock
state = Path(home) / "state"
lock = GatewayPidLock(state)
lock.acquire()
try:
ready.set()
release.wait(10)
finally:
lock.release()
def _hold_recreated_legacy_gateway(home: str, ready, release) -> None:
from opensquilla.gateway.pidlock import GatewayPidLock
home_path = Path(home)
state = home_path / "state"
state.mkdir(parents=True)
lock = GatewayPidLock(state)
lock.acquire()
try:
workspace = home_path / "workspace"
workspace.mkdir()
(workspace / "SOUL.md").write_text("recreated by old gateway\n", encoding="utf-8")
ready.set()
release.wait(10)
finally:
lock.release()
def test_delete_current_primary_preserves_recovery_profiles_and_backups(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
recovery_id = str(uuid.uuid4())
recovery = _recovery_profile(user_data, recovery_id, "recovery")
backup = _backup(user_data, primary)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
assert inspected.outcome == "ready"
assert all("backup" not in item.kind for item in inspected.items)
assert not (tmp_path / "lock-state").exists(), "cleanup inspection must remain read-only"
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete", result.stable_code
assert not primary.exists()
assert not (user_data / "desktop-credential.json").exists()
assert not (user_data / "logs").exists()
assert not (user_data / "desktop-profile-context.json").exists()
assert recovery.is_dir()
assert backup.is_dir()
assert (user_data / "profile-replacement-history.json").is_file()
def test_delete_current_profile_with_state_but_no_legacy_lock_completes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
state = primary / "state"
state.mkdir()
(state / "sessions.db").write_bytes(b"synthetic sessions")
assert not (state / "gateway.pid.lock").exists()
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete", result.stable_code
assert not primary.exists()
def test_cleanup_revision_ignores_directory_metadata_but_not_child_changes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
state = primary / "state"
state.mkdir()
(state / "gateway.pid.lock").write_bytes(b"synthetic lock authority\n")
before = cleanup_module._build_plan(
user_data,
mode="delete-current-profile",
profile_kind="primary",
recovery_id=None,
)
for directory in (primary, primary / "workspace", state):
value = directory.stat()
os.utime(
directory,
ns=(value.st_atime_ns, value.st_mtime_ns + 1_000_000_000),
)
metadata_only = cleanup_module._build_plan(
user_data,
mode="delete-current-profile",
profile_kind="primary",
recovery_id=None,
)
assert metadata_only.revision == before.revision
(state / "sessions.db").write_bytes(b"new user data\n")
changed = cleanup_module._build_plan(
user_data,
mode="delete-current-profile",
profile_kind="primary",
recovery_id=None,
)
assert changed.revision != before.revision
def test_reset_current_settings_preserves_config_workspace_and_sessions(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
config = primary / "config.toml"
config_before = config.read_bytes()
workspace = primary / "workspace" / "SOUL.md"
workspace_before = workspace.read_bytes()
sessions = primary / "state" / "sessions.db"
sessions.parent.mkdir()
sessions.write_bytes(b"synthetic session database")
(user_data / "migration-provider-setup.json").write_text("{}\n", encoding="utf-8")
(user_data / "migration-last-result.json").write_text("{}\n", encoding="utf-8")
inspected = cleanup_inspect(
user_data,
mode="reset-current-settings",
profile_kind="primary",
)
assert inspected.outcome == "ready"
assert {item.kind for item in inspected.items} == {
"primary-credential",
"migration-pending",
"migration-result",
}
result = cleanup_apply(
user_data,
mode="reset-current-settings",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete", result.stable_code
assert not (user_data / "desktop-credential.json").exists()
assert not (user_data / "migration-provider-setup.json").exists()
assert not (user_data / "migration-last-result.json").exists()
assert config.read_bytes() == config_before
assert workspace.read_bytes() == workspace_before
assert sessions.read_bytes() == b"synthetic session database"
assert (user_data / "desktop-profile-context.json").is_file()
def test_reset_recovery_settings_only_clears_selected_recovery_credential(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
recovery_id = str(uuid.uuid4())
recovery_root = _recovery_profile(user_data, recovery_id, "recovery")
recovery_home = recovery_root / "opensquilla"
sessions = recovery_home / "state" / "sessions.db"
sessions.parent.mkdir()
sessions.write_bytes(b"synthetic recovery sessions")
config_before = (recovery_home / "config.toml").read_bytes()
workspace_before = (recovery_home / "workspace" / "SOUL.md").read_bytes()
inspected = cleanup_inspect(
user_data,
mode="reset-current-settings",
profile_kind="recovery",
recovery_id=recovery_id,
)
result = cleanup_apply(
user_data,
mode="reset-current-settings",
profile_kind="recovery",
recovery_id=recovery_id,
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
assert not (recovery_root / "desktop-credential.json").exists()
assert (recovery_root / "logs" / "desktop.log").is_file()
assert (recovery_home / "config.toml").read_bytes() == config_before
assert (recovery_home / "workspace" / "SOUL.md").read_bytes() == workspace_before
assert sessions.read_bytes() == b"synthetic recovery sessions"
assert primary.is_dir()
def test_delete_current_recovery_preserves_primary_other_recovery_and_backups(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
selected_id = str(uuid.uuid4())
other_id = str(uuid.uuid4())
selected = _recovery_profile(user_data, selected_id, "selected")
other = _recovery_profile(user_data, other_id, "other")
backup = _backup(user_data, primary)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="recovery",
recovery_id=selected_id,
)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="recovery",
recovery_id=selected_id,
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete", result.stable_code
assert not selected.exists()
assert primary.is_dir()
assert other.is_dir()
assert backup.is_dir()
assert (user_data / "profile-replacement-history.json").is_file()
assert not (user_data / "desktop-profile-context.json").exists()
def test_delete_all_inventory_and_apply_removes_every_owned_category(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
recovery_id = str(uuid.uuid4())
_recovery_profile(user_data, recovery_id, "recovery")
backup = _backup(user_data, primary)
(user_data / "migration-provider-setup.json").write_text("{}\n", encoding="utf-8")
(user_data / "migration-last-result.json").write_text("{}\n", encoding="utf-8")
(user_data / "desktop-locale.json").write_text('{"locale":"zh-CN"}\n', encoding="utf-8")
chromium_storage = user_data / "Local Storage" / "leveldb"
chromium_storage.mkdir(parents=True)
(chromium_storage / "000001.log").write_text("synthetic cache\n", encoding="utf-8")
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
assert inspected.outcome == "ready"
kinds = {item.kind for item in inspected.items}
assert {
"primary-home",
"primary-credential",
"primary-logs",
"profile-context",
"profile-backup",
"replacement-history",
"migration-pending",
"migration-result",
"recovery-profiles-container",
"user-data-entry",
} <= kinds
residual_paths = {item.path for item in inspected.items if item.kind == "user-data-entry"}
assert user_data / "desktop-locale.json" in residual_paths
assert user_data / "Local Storage" in residual_paths
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
assert all(not item.exists for item in result.items)
assert not backup.exists()
assert list(user_data.iterdir()) == []
def test_delete_all_from_recovery_profile_still_covers_primary_and_every_recovery(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
selected_id = str(uuid.uuid4())
other_id = str(uuid.uuid4())
_recovery_profile(user_data, selected_id, "selected")
_recovery_profile(user_data, other_id, "other")
backup = _backup(user_data, primary)
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="recovery",
recovery_id=selected_id,
)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="recovery",
recovery_id=selected_id,
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
assert all(not item.exists for item in result.items)
assert not primary.exists()
assert not backup.exists()
assert list(user_data.iterdir()) == []
def test_cleanup_apply_requires_exact_user_data_confirmation(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=tmp_path / "different-user-data",
)
assert result.outcome == "blocked"
assert result.stable_code == "cleanup_confirmation_mismatch"
assert primary.is_dir()
def test_delete_all_retains_overlapping_coordination_locks_without_split_brain(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
# Models macOS: user-state/OpenSquilla is also Electron userData A.
user_data = tmp_path / "OpenSquilla"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path))
_desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
assert inspected.outcome == "ready"
assert all(item.path != user_data / "profile-locks" for item in inspected.items)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
assert [path.name for path in user_data.iterdir()] == ["profile-locks"]
assert (user_data / "profile-locks").is_dir()
def test_cleanup_apply_blocks_when_profile_lock_is_busy(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
state_root = tmp_path / "lock-state"
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(state_root))
primary = _desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
context = multiprocessing.get_context("spawn" if sys.platform == "win32" else "fork")
ready = context.Event()
release = context.Event()
process = context.Process(
target=_hold_profile_lock,
args=(str(primary), str(state_root), ready, release),
)
process.start()
assert ready.wait(5)
try:
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
finally:
release.set()
process.join(10)
assert result.outcome == "blocked"
assert result.stable_code == "profile_lock_busy"
assert primary.is_dir()
def test_cleanup_apply_contends_with_replacement_history_authority(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
state_root = tmp_path / "lock-state"
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(state_root))
_desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
context = multiprocessing.get_context("spawn" if sys.platform == "win32" else "fork")
ready = context.Event()
release = context.Event()
process = context.Process(
target=_hold_profile_lock,
args=(str(user_data), str(state_root), ready, release),
)
process.start()
assert ready.wait(5)
try:
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
finally:
release.set()
process.join(10)
assert result.outcome == "blocked"
assert result.stable_code == "profile_lock_busy"
assert (user_data / "opensquilla").is_dir()
def test_cleanup_apply_refuses_running_legacy_gateway(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
(primary / "state").mkdir()
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
context = multiprocessing.get_context("spawn" if sys.platform == "win32" else "fork")
ready = context.Event()
release = context.Event()
process = context.Process(
target=_hold_gateway,
args=(str(primary), ready, release),
)
process.start()
assert ready.wait(5)
try:
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
finally:
release.set()
process.join(10)
assert result.outcome == "blocked"
assert result.stable_code == "legacy_gateway_running"
assert primary.is_dir()
def test_cleanup_rejects_linked_user_data_root_without_following_it(tmp_path: Path) -> None:
real_user_data = tmp_path / "real-user-data"
real_user_data.mkdir()
sentinel = real_user_data / "keep.txt"
sentinel.write_text("keep\n", encoding="utf-8")
linked_user_data = tmp_path / "linked-user-data"
try:
linked_user_data.symlink_to(real_user_data, target_is_directory=True)
except OSError:
pytest.skip("directory symlink creation is unavailable")
report = cleanup_inspect(
linked_user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
assert report.outcome == "blocked"
assert report.stable_code == "cleanup_user_data_unsafe"
assert sentinel.read_text(encoding="utf-8") == "keep\n"
def test_cleanup_apply_rejects_stale_recursive_inventory(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
(primary / "workspace" / "SOUL.md").write_text("changed after inspect\n", encoding="utf-8")
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "blocked"
assert result.stable_code == "cleanup_inventory_stale"
assert primary.is_dir()
def test_cleanup_never_deletes_canonical_profile_recreated_after_quarantine(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
original_remove = cleanup_module._remove_no_follow
context = multiprocessing.get_context("spawn" if sys.platform == "win32" else "fork")
ready = context.Event()
release = context.Event()
process: multiprocessing.Process | None = None
def recreate_canonical_before_deleting_quarantine(path: Path) -> None:
nonlocal process
if process is None and path.name.startswith(f".{primary.name}.cleanup."):
process = context.Process(
target=_hold_recreated_legacy_gateway,
args=(str(primary), ready, release),
)
process.start()
assert ready.wait(5)
original_remove(path)
monkeypatch.setattr(
cleanup_module,
"_remove_no_follow",
recreate_canonical_before_deleting_quarantine,
)
try:
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
finally:
release.set()
if process is not None:
process.join(10)
assert process is not None
assert process.exitcode == 0
assert result.outcome == "partial"
assert (primary / "workspace" / "SOUL.md").read_text(encoding="utf-8") == (
"recreated by old gateway\n"
)
assert (user_data / "desktop-credential.json").is_file()
assert (user_data / "desktop-profile-context.json").is_file()
assert (user_data / ".opensquilla.profile-cleanup.json").is_file()
def test_cleanup_releases_legacy_handles_before_deleting_profile_tombstone(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
import opensquilla.recovery.locking as locking_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
state = primary / "state"
state.mkdir()
(state / "gateway.pid.lock").write_bytes(b"synthetic lock authority\n")
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
monkeypatch.setattr(
locking_module,
"_windows_requires_legacy_lock_handoff",
lambda: True,
)
real_acquire = cleanup_module.acquire_legacy_gateway_locks
captured_locks = []
@contextmanager
def capture_locks(*homes, **kwargs):
with real_acquire(*homes, **kwargs) as locks:
captured_locks.extend(locks)
yield locks
real_remove = cleanup_module._remove_no_follow
observed_tombstone_delete = False
def assert_handles_released(path: Path) -> None:
nonlocal observed_tombstone_delete
if path.name.startswith(f".{primary.name}.cleanup."):
observed_tombstone_delete = True
moved_state = path / "state"
assert not any(lock.holds_state_root(moved_state) for lock in captured_locks)
real_remove(path)
monkeypatch.setattr(cleanup_module, "acquire_legacy_gateway_locks", capture_locks)
monkeypatch.setattr(cleanup_module, "_remove_no_follow", assert_handles_released)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete", result.stable_code
assert observed_tombstone_delete is True
assert captured_locks
assert not primary.exists()
def test_cleanup_rolls_back_directory_swapped_at_no_replace_boundary(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
import opensquilla.recovery.locking as locking_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
displaced = user_data / "displaced-original-profile"
real_move = cleanup_module.native_move_no_replace
swapped = False
monkeypatch.setattr(
locking_module,
"_windows_requires_legacy_lock_handoff",
lambda: True,
)
def swap_then_move(
source: Path,
destination: Path,
**move_options: object,
) -> None:
nonlocal swapped
if source == primary and not swapped:
swapped = True
source.rename(displaced)
_home(source, "replacement profile")
real_move(source, destination, **move_options) # type: ignore[arg-type]
monkeypatch.setattr(cleanup_module, "native_move_no_replace", swap_then_move)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert swapped is True
assert result.outcome == "partial"
assert (primary / "workspace" / "SOUL.md").read_text(encoding="utf-8") == (
"replacement profile\n"
)
assert (displaced / "workspace" / "SOUL.md").read_text(encoding="utf-8") == (
"primary\n"
)
assert (user_data / ".opensquilla.profile-cleanup.json").is_file()
def test_cleanup_backup_delete_failure_preserves_history_and_cleanup_journal(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
primary = _desktop_primary(user_data)
backup = _backup(user_data, primary)
history = user_data / "profile-replacement-history.json"
history_before = history.read_bytes()
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
original_remove = cleanup_module._remove_no_follow
def fail_backup_delete(path: Path) -> None:
if path == backup or path.name.startswith(f".{backup.name}.cleanup."):
raise OSError("synthetic failure after backup quarantine")
original_remove(path)
monkeypatch.setattr(cleanup_module, "_remove_no_follow", fail_backup_delete)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "partial"
assert backup.is_dir()
assert history.read_bytes() == history_before
assert (user_data / ".opensquilla.profile-cleanup.json").is_file()
def test_recovery_profile_inspection_sees_global_partial_cleanup_journal(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
recovery_id = str(uuid.uuid4())
recovery_root = _recovery_profile(user_data, recovery_id, "recovery")
recovery_home = recovery_root / "opensquilla"
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="recovery",
recovery_id=recovery_id,
)
original_remove = cleanup_module._remove_no_follow
def fail_recovery_delete(path: Path) -> None:
if path == recovery_root or path.name.startswith(f".{recovery_root.name}.cleanup."):
raise OSError("synthetic partial recovery profile deletion")
original_remove(path)
monkeypatch.setattr(cleanup_module, "_remove_no_follow", fail_recovery_delete)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="recovery",
recovery_id=recovery_id,
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
report = inspect_profile(recovery_home, profile_kind="desktop-recovery")
assert result.outcome == "partial"
assert recovery_home.is_dir()
assert report.outcome == "recovery_required"
assert report.stable_code == "cleanup_transaction_incomplete"
assert "recover-transaction" not in report.allowed_actions
def test_incomplete_cleanup_journal_blocks_fresh_primary_bootstrap(tmp_path: Path) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
home = user_data / "opensquilla"
(user_data / ".opensquilla.profile-cleanup.json").write_text(
json.dumps(
{
"schema_version": 1,
"operation": "profile-cleanup",
"transaction_id": str(uuid.uuid4()),
"phase": "prepared",
"primary_home": str(home),
"tombstones": [],
}
)
+ "\n",
encoding="utf-8",
)
report = inspect_profile(home, profile_kind="desktop-primary")
assert report.outcome == "recovery_required"
assert report.stable_code == "cleanup_transaction_incomplete"
assert "recover-transaction" not in report.allowed_actions
def test_primary_cleanup_journal_does_not_block_unaffected_recovery_profile(
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
primary = _desktop_primary(user_data)
recovery_id = str(uuid.uuid4())
recovery_root = _recovery_profile(user_data, recovery_id, "recovery")
recovery_home = recovery_root / "opensquilla"
(recovery_home / "state").mkdir()
(user_data / ".opensquilla.profile-cleanup.json").write_text(
json.dumps(
{
"schema_version": 1,
"operation": "profile-cleanup",
"transaction_id": str(uuid.uuid4()),
"phase": "prepared",
"primary_home": str(primary),
"mode": "delete-current-profile",
"profile_kind": "primary",
"recovery_id": None,
"tombstones": [],
}
)
+ "\n",
encoding="utf-8",
)
report = inspect_profile(recovery_home, profile_kind="desktop-recovery")
assert report.outcome == "recovery_profile"
assert report.stable_code != "cleanup_transaction_incomplete"
def test_cleanup_journal_does_not_apply_to_an_ordinary_cli_profile(tmp_path: Path) -> None:
home = _home(tmp_path / "cli-home", "cli")
(home / "state").mkdir()
(home.parent / f".{home.name}.profile-cleanup.json").write_text(
"malformed guard that belongs only to Desktop\n",
encoding="utf-8",
)
report = inspect_profile(home, profile_kind="cli-home")
assert report.outcome == "ready"
assert report.stable_code != "cleanup_transaction_incomplete"
def test_cleanup_unlinks_logs_symlink_without_following_external_target(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
logs = user_data / "logs"
for child in logs.iterdir():
child.unlink()
logs.rmdir()
external = tmp_path / "external-logs"
external.mkdir()
sentinel = external / "keep.txt"
sentinel.write_text("keep\n", encoding="utf-8")
try:
logs.symlink_to(external, target_is_directory=True)
except OSError:
pytest.skip("symlink creation is unavailable")
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
assert not os.path.lexists(logs)
assert sentinel.read_text(encoding="utf-8") == "keep\n"
@pytest.mark.parametrize(
("filename", "content", "stable_code", "mode"),
[
(
"profile-replacement-history.json",
"not-json\n",
"cleanup_history_invalid",
"delete-all-user-data",
),
(
".opensquilla.profile-replace.json",
'{"schema_version":1,"phase":"prepared"}\n',
"cleanup_transaction_incomplete",
"delete-current-profile",
),
],
)
def test_malformed_history_or_unfinished_journal_blocks_cleanup(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
filename: str,
content: str,
stable_code: str,
mode: str,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
(user_data / filename).write_text(content, encoding="utf-8")
report = cleanup_inspect(
user_data,
mode=mode,
profile_kind="primary",
)
assert report.outcome == "blocked"
assert report.stable_code == stable_code
@pytest.mark.parametrize("selected_kind", ["primary", "recovery"])
def test_delete_current_ignores_unrelated_bad_history_and_non_uuid_recovery(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
selected_kind: str,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
selected_id = str(uuid.uuid4())
selected = _recovery_profile(user_data, selected_id, "selected")
unrelated = user_data / "recovery-profiles" / "not-a-uuid"
unrelated.mkdir()
(unrelated / "keep.txt").write_text("keep\n", encoding="utf-8")
history = user_data / "profile-replacement-history.json"
history.write_text("malformed and unrelated\n", encoding="utf-8")
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind=selected_kind,
recovery_id=selected_id if selected_kind == "recovery" else None,
)
assert inspected.outcome == "ready"
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind=selected_kind,
recovery_id=selected_id if selected_kind == "recovery" else None,
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "complete"
if selected_kind == "recovery":
assert not selected.exists()
assert (user_data / "opensquilla").exists()
else:
assert not (user_data / "opensquilla").exists()
assert selected.exists()
assert history.read_text(encoding="utf-8") == "malformed and unrelated\n"
assert (unrelated / "keep.txt").read_text(encoding="utf-8") == "keep\n"
def test_non_uuid_recovery_entry_blocks_all_cleanup(tmp_path: Path) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
_desktop_primary(user_data)
(user_data / "recovery-profiles" / "not-a-uuid").mkdir(parents=True)
report = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
assert report.outcome == "blocked"
assert report.stable_code == "cleanup_recovery_entry_invalid"
def test_cleanup_delete_failure_returns_partial_and_never_claims_all(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
credential = user_data / "desktop-credential.json"
original_remove = cleanup_module._remove_no_follow
def fail_credential(path: Path) -> None:
if path == credential:
raise OSError("synthetic sharing violation")
original_remove(path)
monkeypatch.setattr(cleanup_module, "_remove_no_follow", fail_credential)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "partial"
assert result.stable_code == "cleanup_partial"
assert credential.exists()
def test_cleanup_reports_partial_when_missing_known_path_is_recreated_after_apply(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
credential = user_data / "desktop-credential.json"
credential.unlink()
inspected = cleanup_inspect(
user_data,
mode="delete-current-profile",
profile_kind="primary",
)
credential_item = next(
item for item in inspected.items if item.kind == "primary-credential"
)
assert credential_item.exists is False
real_current_item = cleanup_module._current_item
recreated = False
def recreate_before_final_inventory(planned):
nonlocal recreated
if not recreated:
recreated = True
credential.write_text("{}\n", encoding="utf-8")
return real_current_item(planned)
monkeypatch.setattr(cleanup_module, "_current_item", recreate_before_final_inventory)
result = cleanup_apply(
user_data,
mode="delete-current-profile",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert recreated is True
assert result.outcome == "partial"
assert credential.is_file()
assert (user_data / ".opensquilla.profile-cleanup.json").is_file()
def test_delete_all_reports_partial_for_new_unplanned_user_data_entry(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
late_entry = user_data / "late-chromium-cache" / "cache.bin"
real_current_item = cleanup_module._current_item
recreated = False
def create_unplanned_entry_before_final_inventory(planned):
nonlocal recreated
if not recreated:
recreated = True
late_entry.parent.mkdir()
late_entry.write_bytes(b"synthetic cache")
return real_current_item(planned)
monkeypatch.setattr(
cleanup_module,
"_current_item",
create_unplanned_entry_before_final_inventory,
)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert recreated is True
assert result.outcome == "partial"
assert late_entry.read_bytes() == b"synthetic cache"
assert (user_data / ".opensquilla.profile-cleanup.json").is_file()
def test_partial_delete_all_can_be_abandoned_then_new_recovery_profile_runs(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
_desktop_primary(user_data)
inspected = cleanup_inspect(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
)
credential = user_data / "desktop-credential.json"
original_remove = cleanup_module._remove_no_follow
def fail_credential(path: Path) -> None:
if path == credential:
raise OSError("synthetic partial cleanup")
original_remove(path)
monkeypatch.setattr(cleanup_module, "_remove_no_follow", fail_credential)
result = cleanup_apply(
user_data,
mode="delete-all-user-data",
profile_kind="primary",
transaction_id=inspected.transaction_id,
expected_revision=inspected.revision,
confirm_user_data=user_data,
)
assert result.outcome == "partial"
recovery_id = str(uuid.uuid4())
recovery_root = _recovery_profile(user_data, recovery_id, "new recovery")
recovery_home = recovery_root / "opensquilla"
(recovery_home / "state").mkdir()
blocked = inspect_profile(recovery_home, profile_kind="desktop-recovery")
assert blocked.stable_code == "cleanup_transaction_incomplete"
assert "abandon-cleanup" in blocked.allowed_actions
preserved = abandon_cleanup_transaction(
user_data,
home=recovery_home,
profile_kind="desktop-recovery",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert preserved.is_file()
assert ".profile-cleanup.abandoned." in preserved.name
assert not (user_data / ".opensquilla.profile-cleanup.json").exists()
assert credential.is_file()
usable = inspect_profile(recovery_home, profile_kind="desktop-recovery")
assert usable.outcome == "recovery_profile"
def test_malformed_cleanup_journal_can_be_quarantined_without_deleting_data(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
recovery_id = str(uuid.uuid4())
recovery_root = _recovery_profile(user_data, recovery_id, "safe recovery")
recovery_home = recovery_root / "opensquilla"
(recovery_home / "state").mkdir()
journal = user_data / ".opensquilla.profile-cleanup.json"
journal.write_text("malformed cleanup authority\n", encoding="utf-8")
before = (recovery_home / "workspace" / "SOUL.md").read_bytes()
blocked = inspect_profile(recovery_home, profile_kind="desktop-recovery")
preserved = abandon_cleanup_transaction(
user_data,
home=recovery_home,
profile_kind="desktop-recovery",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert preserved.read_text(encoding="utf-8") == "malformed cleanup authority\n"
assert not journal.exists()
assert (recovery_home / "workspace" / "SOUL.md").read_bytes() == before
assert inspect_profile(
recovery_home,
profile_kind="desktop-recovery",
).outcome == "recovery_profile"
@pytest.mark.parametrize("mutation", ["identity", "digest"])
def test_abandon_cleanup_rejects_journal_swapped_after_inspection(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
mutation: str,
) -> None:
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
home = _desktop_primary(user_data)
journal = user_data / ".opensquilla.profile-cleanup.json"
first = b"malformed cleanup journal alpha\n"
second = b"malformed cleanup journal bravo\n"
assert len(first) == len(second)
journal.write_bytes(first)
blocked = inspect_profile(home, profile_kind="desktop-primary")
assert blocked.stable_code == "cleanup_transaction_incomplete"
before = journal.lstat()
before_identity = (
before.st_dev,
before.st_ino,
before.st_mode,
before.st_size,
before.st_mtime_ns,
)
if mutation == "identity":
replacement = user_data / ".replacement-cleanup-journal.json"
replacement.write_bytes(first)
os.replace(replacement, journal)
after = journal.lstat()
after_identity = (
after.st_dev,
after.st_ino,
after.st_mode,
after.st_size,
after.st_mtime_ns,
)
assert after_identity != before_identity
else:
journal.write_bytes(second)
os.utime(journal, ns=(before.st_atime_ns, before.st_mtime_ns))
after = journal.lstat()
after_identity = (
after.st_dev,
after.st_ino,
after.st_mode,
after.st_size,
after.st_mtime_ns,
)
assert after_identity == before_identity
current = journal.read_bytes()
swapped = inspect_profile(home, profile_kind="desktop-primary")
assert swapped.stable_code == "cleanup_transaction_incomplete"
assert swapped.transaction_id == blocked.transaction_id
assert swapped.revision != blocked.revision
with pytest.raises(StaleRecoveryTransactionError):
abandon_cleanup_transaction(
user_data,
home=home,
profile_kind="desktop-primary",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert journal.read_bytes() == current
assert not tuple(user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))
def test_abandon_cleanup_rechecks_captured_journal_after_internal_inspection(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.engine as engine_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
home = _desktop_primary(user_data)
journal = user_data / ".opensquilla.profile-cleanup.json"
original = b"original cleanup authority\n"
replacement_data = b"replacement cleanup authority\n"
journal.write_bytes(original)
blocked = inspect_profile(home, profile_kind="desktop-primary")
real_inspect = engine_module.inspect_profile
def inspect_then_swap(*args, **kwargs):
report = real_inspect(*args, **kwargs)
replacement = user_data / ".replacement-cleanup-journal.json"
replacement.write_bytes(replacement_data)
os.replace(replacement, journal)
return report
monkeypatch.setattr(engine_module, "inspect_profile", inspect_then_swap)
with pytest.raises(RecoveryError) as error:
abandon_cleanup_transaction(
user_data,
home=home,
profile_kind="desktop-primary",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert error.value.stable_code == "config_changed"
assert journal.read_bytes() == replacement_data
assert not tuple(user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))
def test_abandon_cleanup_rolls_back_journal_swapped_at_move_boundary(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
home = _desktop_primary(user_data)
journal = user_data / ".opensquilla.profile-cleanup.json"
journal.write_bytes(b"original cleanup authority\n")
blocked = inspect_profile(home, profile_kind="desktop-primary")
replacement_data = b"replacement cleanup authority\n"
real_move = cleanup_module.native_move_no_replace
swapped = False
def swap_then_move(source: Path, destination: Path) -> None:
nonlocal swapped
if source == journal and not swapped:
swapped = True
replacement = user_data / ".replacement-cleanup-journal.json"
replacement.write_bytes(replacement_data)
os.replace(replacement, journal)
real_move(source, destination)
monkeypatch.setattr(cleanup_module, "native_move_no_replace", swap_then_move)
with pytest.raises(RecoveryError) as error:
abandon_cleanup_transaction(
user_data,
home=home,
profile_kind="desktop-primary",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert error.value.stable_code == "config_changed"
assert journal.read_bytes() == replacement_data
assert not tuple(user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))
def test_abandon_cleanup_reports_unknown_when_swapped_move_cannot_be_rolled_back(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
import opensquilla.recovery.cleanup as cleanup_module
user_data = tmp_path / "user-data"
user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
home = _desktop_primary(user_data)
journal = user_data / ".opensquilla.profile-cleanup.json"
journal.write_bytes(b"original cleanup authority\n")
blocked = inspect_profile(home, profile_kind="desktop-primary")
replacement_data = b"replacement cleanup authority\n"
recreated_data = b"new canonical cleanup authority\n"
real_move = cleanup_module.native_move_no_replace
swapped = False
def swap_move_and_recreate(source: Path, destination: Path) -> None:
nonlocal swapped
if source == journal and not swapped:
swapped = True
replacement = user_data / ".replacement-cleanup-journal.json"
replacement.write_bytes(replacement_data)
os.replace(replacement, journal)
real_move(source, destination)
journal.write_bytes(recreated_data)
return
real_move(source, destination)
monkeypatch.setattr(
cleanup_module,
"native_move_no_replace",
swap_move_and_recreate,
)
with pytest.raises(RecoveryError) as error:
abandon_cleanup_transaction(
user_data,
home=home,
profile_kind="desktop-primary",
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert error.value.stable_code == "atomic_state_unknown"
assert journal.read_bytes() == recreated_data
preserved = tuple(user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))
assert len(preserved) == 1
assert preserved[0].read_bytes() == replacement_data
@pytest.mark.parametrize("profile_kind", ["desktop-primary", "desktop-recovery"])
def test_abandon_cleanup_rejects_profile_from_another_user_data_root(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
profile_kind: str,
) -> None:
user_data = tmp_path / "selected-user-data"
user_data.mkdir()
foreign_user_data = tmp_path / "foreign-user-data"
foreign_user_data.mkdir()
monkeypatch.setenv("OPENSQUILLA_USER_STATE_DIR", str(tmp_path / "lock-state"))
if profile_kind == "desktop-primary":
foreign_home = _desktop_primary(foreign_user_data)
else:
recovery_id = str(uuid.uuid4())
foreign_home = (
_recovery_profile(foreign_user_data, recovery_id, "foreign recovery")
/ "opensquilla"
)
selected_journal = user_data / ".opensquilla.profile-cleanup.json"
foreign_journal = foreign_user_data / ".opensquilla.profile-cleanup.json"
selected_before = b"selected cleanup authority\n"
foreign_before = b"foreign cleanup authority\n"
selected_journal.write_bytes(selected_before)
foreign_journal.write_bytes(foreign_before)
blocked = inspect_profile(foreign_home, profile_kind=profile_kind)
assert blocked.stable_code == "cleanup_transaction_incomplete"
with pytest.raises(RecoveryError) as error:
abandon_cleanup_transaction(
user_data,
home=foreign_home,
profile_kind=profile_kind,
transaction_id=blocked.transaction_id,
expected_revision=blocked.revision,
)
assert error.value.stable_code == "cleanup_profile_selector_invalid"
assert selected_journal.read_bytes() == selected_before
assert foreign_journal.read_bytes() == foreign_before
assert not tuple(user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))
assert not tuple(foreign_user_data.glob(".opensquilla.profile-cleanup.abandoned.*.json"))