diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7a7c08c..e984a5dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,9 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[dev,binary]" - - run: mypy --config-file pyproject.toml features/ shared/ mcp_server/ rag/ hooks/ wiki/ lifecycle/ graph/ core/ + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra dev --extra binary --python 3.12 + - run: uv run mypy --config-file pyproject.toml features/ shared/ mcp_server/ rag/ hooks/ wiki/ lifecycle/ graph/ core/ quality: runs-on: ubuntu-latest @@ -81,9 +82,15 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: "pip" - - run: pip install -e ".[dev,binary]" "aiosqlite>=0.21.0,!=0.22.0" - - run: python scripts/install_ner_models.py - - run: pytest tests/ -v --tb=long --timeout=30 + - uses: astral-sh/setup-uv@v5 + # Seed pip into the venv: scripts/install_ner_models.py shells out to + # `sys.executable -m pip install `, and a uv-created venv + # has no pip otherwise. `uv sync` keeps the seeded pip, and the pip + # cache (setup-python `cache: pip`) keeps the model wheels warm. + - run: uv venv --seed --python ${{ matrix.python-version }} + - run: uv sync --locked --extra dev --extra binary --python ${{ matrix.python-version }} + - run: uv run python scripts/install_ner_models.py + - run: uv run pytest tests/ -v --tb=long --timeout=30 -n auto coverage: runs-on: ubuntu-latest @@ -93,9 +100,11 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[dev,binary]" "aiosqlite>=0.21.0,!=0.22.0" - - run: python scripts/install_ner_models.py - - run: pytest tests/ --cov=features --cov=shared --cov=core --cov=rag --cov=wiki --cov=graph --cov=lifecycle --cov=hooks --cov=mcp_server --cov-report=xml --cov-report=term-missing --cov-fail-under=60 --timeout=60 + - uses: astral-sh/setup-uv@v5 + - run: uv venv --seed --python 3.12 # NER installer needs pip in the venv + - run: uv sync --locked --extra dev --extra binary --python 3.12 + - run: uv run python scripts/install_ner_models.py + - run: uv run pytest tests/ -n auto --cov=features --cov=shared --cov=core --cov=rag --cov=wiki --cov=graph --cov=lifecycle --cov=hooks --cov=mcp_server --cov-report=xml --cov-report=term-missing --cov-fail-under=60 --timeout=60 - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -136,6 +145,7 @@ jobs: with: python-version: "3.12" cache: "pip" - - run: pip install -e ".[docs]" + - uses: astral-sh/setup-uv@v5 + - run: uv sync --locked --extra docs --python 3.12 - name: Build docs (strict — fails on broken refs) - run: mkdocs build --strict + run: uv run mkdocs build --strict diff --git a/tests/test_features/test_source_time.py b/tests/test_features/test_source_time.py index 2e943f35..b4fa30cc 100644 --- a/tests/test_features/test_source_time.py +++ b/tests/test_features/test_source_time.py @@ -241,7 +241,7 @@ async def get_layer(self, *a: Any, **k: Any) -> Any: @pytest.mark.asyncio @pytest.mark.parametrize( ("payload_ts", "expected"), - [(time.time() - 5 * 86400, "forwarded"), ("2026-09-18T07:03:00", "refused"), (None, "refused")], + [(1700000000.0, "forwarded"), ("2026-09-18T07:03:00", "refused"), (None, "refused")], ) async def test_new_message_forwards_a_numeric_ts_and_refuses_anything_else(cm: Any, payload_ts: Any, expected: str, monkeypatch) -> None: """Hop 4: the daemon's `ts` reaches capture — if, and only if, it is a number. diff --git a/tests/test_s17_stage1_tail.py b/tests/test_s17_stage1_tail.py index 4b74f6d8..1fb1e209 100644 --- a/tests/test_s17_stage1_tail.py +++ b/tests/test_s17_stage1_tail.py @@ -174,13 +174,16 @@ async def test_distiller_similar_to_on_conflict() -> None: @pytest.fixture -async def migrated_cm(): +async def migrated_cm(tmp_path, monkeypatch): """Hermetic global DB + полная схема (l0_journal и co-таблицы живут миграциями).""" from shared.connection import connection_manager from shared.migrations import MigrationManager + monkeypatch.setattr(connection_manager, "base_dir", tmp_path) # патчим base_dir, не подменяем объект + connection_manager._conns.clear() # cached conns pin the old tmp dir await MigrationManager(cm=connection_manager).migrate() - return connection_manager + yield connection_manager + connection_manager._conns.clear() @pytest.mark.asyncio @@ -231,11 +234,12 @@ async def test_zero_result_journal_and_miner(migrated_cm) -> None: @pytest.mark.asyncio async def test_zero_result_single_hit_not_surfaced(migrated_cm) -> None: - from lifecycle.graph_miners import log_zero_result, miner_zero_results + from lifecycle.graph_miners import ensure_zero_result, log_zero_result, miner_zero_results from shared.constants import DB_NAME cm = migrated_cm conn = await cm.get(DB_NAME) + await ensure_zero_result(cm) # lazy-ensure таблица: без неё DELETE падает в герметичной БД await conn.execute("DELETE FROM recall_zero_results") # сессионная БД: изолируем журнал await conn.commit() await log_zero_result(cm, "user", "s17zr2", "единичный провал запроса XYZ") @@ -253,11 +257,12 @@ async def test_zero_result_miner_lets_a_failure_escape(migrated_cm, monkeypatch) ноль — это работа, которая не нашлась, а сбой пусть ловит `graph_enrich`. """ from graph.epistemic import EpistemicGraph - from lifecycle.graph_miners import log_zero_result, miner_zero_results + from lifecycle.graph_miners import ensure_zero_result, log_zero_result, miner_zero_results from shared.constants import DB_NAME cm = migrated_cm conn = await cm.get(DB_NAME) + await ensure_zero_result(cm) # lazy-ensure таблица: без неё DELETE падает в герметичной БД await conn.execute("DELETE FROM recall_zero_results") await conn.commit() for _ in range(2):