From 41fb54c5e753dc6d5a3b3fd219087513dcff2b6a Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 14 May 2026 13:29:27 +0300 Subject: [PATCH] fix: add runtime schema guard for skipped count --- README.md | 4 ++-- app/db.py | 13 ++++++++++++- app/version.py | 6 +++--- pyproject.toml | 2 +- tests/test_api_mcp.py | 4 ++-- tests/test_db_schema.py | 27 +++++++++++++++++++++++++++ 6 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 tests/test_db_schema.py diff --git a/README.md b/README.md index 1fb3615..6c76c4a 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ docker compose up --build - MCP: `http://localhost:8001/mcp` - Postgres: `localhost:5432` -Таблицы создаются приложением при старте. SQL-миграция для ручного применения лежит в `migrations/001_init.sql`. +Таблицы создаются приложением при старте. При обновлении существующей базы приложение также добавляет недостающие runtime-колонки, например `crawl_runs.skipped_count`. SQL-миграции для ручного применения лежат в `migrations/`. ## Парсинг @@ -115,4 +115,4 @@ docker compose exec postgres pg_dump -U miem miem_workers > backup.sql docker compose down ``` -Версия сервиса: `0.6.0`. Админка всегда показывает версии backend и frontend в footer. +Версия сервиса: `0.6.1`. Админка всегда показывает версии backend и frontend в footer. diff --git a/app/db.py b/app/db.py index 21318d7..14272cd 100644 --- a/app/db.py +++ b/app/db.py @@ -1,6 +1,6 @@ from collections.abc import Generator -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect, text from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from app.config import get_settings @@ -25,6 +25,17 @@ def init_db() -> None: import app.models # noqa: F401 Base.metadata.create_all(bind=engine) + _ensure_runtime_schema() + + +def _ensure_runtime_schema() -> None: + inspector = inspect(engine) + if "crawl_runs" not in inspector.get_table_names(): + return + crawl_run_columns = {column["name"] for column in inspector.get_columns("crawl_runs")} + if "skipped_count" not in crawl_run_columns: + with engine.begin() as connection: + connection.execute(text("ALTER TABLE crawl_runs ADD COLUMN skipped_count INTEGER NOT NULL DEFAULT 0")) def get_db() -> Generator[Session, None, None]: diff --git a/app/version.py b/app/version.py index ad11beb..49eb537 100644 --- a/app/version.py +++ b/app/version.py @@ -1,3 +1,3 @@ -APP_VERSION = "0.6.0" -FRONTEND_VERSION = "0.6.0" -BACKEND_VERSION = "0.6.0" +APP_VERSION = "0.6.1" +FRONTEND_VERSION = "0.6.1" +BACKEND_VERSION = "0.6.1" diff --git a/pyproject.toml b/pyproject.toml index e468f34..843c32b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "miem-workers" -version = "0.6.0" +version = "0.6.1" description = "MIEM employees parser, admin API, and MCP server" requires-python = ">=3.11" dependencies = [ diff --git a/tests/test_api_mcp.py b/tests/test_api_mcp.py index a17117b..c74fe6f 100644 --- a/tests/test_api_mcp.py +++ b/tests/test_api_mcp.py @@ -20,7 +20,7 @@ def test_health_returns_versions(): response = client.get("/api/health") assert response.status_code == 200 - assert response.json()["backend_version"] == "0.6.0" + assert response.json()["backend_version"] == "0.6.1" def test_mcp_lists_tools_without_auth_and_ignores_auth_header(): @@ -154,7 +154,7 @@ def test_mcp_service_info_returns_tools_and_dataset_hash(): assert response.status_code == 200 payload = json.loads(response.json()["result"]["content"][0]["text"]) assert payload["service_name"] == "miem-employees" - assert payload["backend_version"] == "0.6.0" + assert payload["backend_version"] == "0.6.1" assert payload["dataset"]["hash"] assert any(tool["name"] == "sync_employees" for tool in payload["tools"]) diff --git a/tests/test_db_schema.py b/tests/test_db_schema.py new file mode 100644 index 0000000..a806a4c --- /dev/null +++ b/tests/test_db_schema.py @@ -0,0 +1,27 @@ +from sqlalchemy import create_engine, inspect, text + +from app.db import _ensure_runtime_schema + + +def test_runtime_schema_adds_skipped_count_to_existing_crawl_runs_table(monkeypatch): + engine = create_engine("sqlite:///:memory:") + with engine.begin() as connection: + connection.execute( + text( + """ + CREATE TABLE crawl_runs ( + id INTEGER PRIMARY KEY, + source_url TEXT NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'running', + found_count INTEGER NOT NULL DEFAULT 0, + parsed_count INTEGER NOT NULL DEFAULT 0 + ) + """ + ) + ) + monkeypatch.setattr("app.db.engine", engine) + + _ensure_runtime_schema() + + columns = {column["name"] for column in inspect(engine).get_columns("crawl_runs")} + assert "skipped_count" in columns -- 2.49.1