agnes-the-ai-analyst/tests/test_parquet_lock_sweep.py
ZdenekSrotyr 12db59127b
release: 0.53.0 — close Tier B trackers (#259-#261) + admin UI fix (#265) (#267)
* release: 0.53.0 — Tier B trackers + admin UI bugfix

Closes #259 (init resume sentinel), #260 (startup parquet-lock sweep),
#261 (materialized schema uses local parquet, not BQ), #265 (admin
tables apostrophe → HTML-entity escape).

Tracker notes: #262 closed as obsolete (pre-empted by 0.51.0 changes),
#266 left open pending UX clarification.

* fix(init): move resume sentinel from .agnes/ to .claude/

The clean-install integration test (test_clean_install_integration.py)
forbids creating .agnes/ in the workspace root via its
forbidden_unconditional list — that path is reserved for ~/.agnes/ in
the user's HOME (marketplace clone, CA bundle).

.claude/ is already created by agnes init for settings.json + hooks,
so dropping init-complete next to those keeps the resume sentinel
consistent with the rest of Claude Code's workspace surface and lets
the clean-install assertions pass.

Issue #259.

* docs(changelog): point #259 entry at new .claude/init-complete path

Follows the sentinel move from .agnes/ → .claude/ to keep the changelog
in sync with what 0.53.0 actually ships.
2026-05-12 16:28:41 +02:00

61 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Startup sweep of stale `.parquet.lock` files (Issue #260).
The lock acquire path already reclaims stale locks lazily on the next
materialize attempt, but a dedicated startup sweep removes zombies
sitting next to parquets without waiting for the next sync.
"""
from pathlib import Path
def test_sweep_returns_zero_when_no_data_dir(tmp_path: Path):
"""Missing directory must not raise — operators starting on a fresh
VM before any extracts exist should see a clean startup."""
from connectors.bigquery.extractor import sweep_stale_parquet_locks
assert sweep_stale_parquet_locks(tmp_path / "nonexistent") == 0
def test_sweep_keeps_fresh_locks(tmp_path: Path, monkeypatch):
"""A lock file mtime'd within the TTL stays put."""
from connectors.bigquery.extractor import sweep_stale_parquet_locks
extracts = tmp_path / "extracts" / "bigquery" / "data"
extracts.mkdir(parents=True)
lock = extracts / "t.parquet.lock"
lock.touch()
monkeypatch.setenv("AGNES_INSTANCE_CONFIG", str(tmp_path / "no.yaml"))
n = sweep_stale_parquet_locks(tmp_path / "extracts")
assert n == 0
assert lock.exists()
def test_sweep_removes_stale_locks(tmp_path: Path):
"""A lock mtime'd > TTL ago gets unlinked."""
import os
import time
from connectors.bigquery.extractor import sweep_stale_parquet_locks, _get_lock_ttl_seconds
extracts = tmp_path / "extracts" / "bigquery" / "data"
extracts.mkdir(parents=True)
lock = extracts / "old.parquet.lock"
lock.touch()
# Backdate mtime to 2× TTL ago.
ttl = _get_lock_ttl_seconds()
ancient = time.time() - (ttl * 2)
os.utime(lock, (ancient, ancient))
n = sweep_stale_parquet_locks(tmp_path / "extracts")
assert n == 1
assert not lock.exists()
def test_sweep_handles_multiple_sources(tmp_path: Path):
"""Recursive search covers bq + keboola + jira layouts under one root."""
import os, time
from connectors.bigquery.extractor import sweep_stale_parquet_locks, _get_lock_ttl_seconds
for source in ("bigquery", "keboola", "jira"):
d = tmp_path / "extracts" / source / "data"
d.mkdir(parents=True)
lock = d / "t.parquet.lock"
lock.touch()
ancient = time.time() - (_get_lock_ttl_seconds() * 2)
os.utime(lock, (ancient, ancient))
n = sweep_stale_parquet_locks(tmp_path / "extracts")
assert n == 3