diff --git a/tests/test_deploy.py b/tests/test_deploy.py index e673edc..f4b19ee 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -740,112 +740,3 @@ class TestProjectLinksIndexMigration: assert "idx_project_links_from" in after assert before == after conn.close() - - -# --------------------------------------------------------------------------- -# 12. Migration: UNIQUE(from_project, to_project, type) (KIN-INFRA-013) -# Convention #433: set-assert unique constraint after fresh init -# Convention #434: negative test — ALTER TABLE cannot add UNIQUE in SQLite -# --------------------------------------------------------------------------- - -class TestProjectLinksUniqueMigration: - """KIN-INFRA-013: UNIQUE(from_project, to_project, type) на project_links.""" - - # --- fresh schema --- - - def test_fresh_schema_project_links_has_unique_constraint(self): - """Свежая схема должна иметь UNIQUE-ограничение на (from_project, to_project, type).""" - conn = init_db(db_path=":memory:") - unique_indexes = [ - r for r in conn.execute("PRAGMA index_list(project_links)").fetchall() - if r[2] == 1 # unique == 1 - ] - assert len(unique_indexes) >= 1 - conn.close() - - # --- модельный уровень --- - - def test_create_duplicate_link_raises_integrity_error(self, conn): - """Дублирующая вставка должна вызывать IntegrityError.""" - import sqlite3 as _sqlite3 - models.create_project(conn, "dup_a", "A", "/a") - models.create_project(conn, "dup_b", "B", "/b") - models.create_project_link(conn, "dup_a", "dup_b", "depends_on") - with pytest.raises(_sqlite3.IntegrityError): - models.create_project_link(conn, "dup_a", "dup_b", "depends_on") - - # --- migration guard: 3 кейса (Convention #384) --- - - # Кейс 1: без таблицы — guard не падает - def test_migrate_without_project_links_table_no_error_unique(self): - conn = _old_schema_no_deploy() # project_links отсутствует - _migrate(conn) # не должно упасть - conn.close() - - # Кейс 2: таблица без UNIQUE → _migrate() добавляет ограничение - def test_migrate_adds_unique_constraint_to_old_schema(self): - conn = _schema_with_project_links_no_indexes() # без UNIQUE - _migrate(conn) - pl_sql = conn.execute( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_links'" - ).fetchone() - assert "UNIQUE" in (pl_sql[0] or "").upper() - conn.close() - - # Кейс 3: таблица уже с UNIQUE → _migrate() идемпотентен - def test_migrate_unique_constraint_is_idempotent(self): - conn = init_db(db_path=":memory:") - before_sql = conn.execute( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_links'" - ).fetchone()[0] - _migrate(conn) - after_sql = conn.execute( - "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_links'" - ).fetchone()[0] - assert before_sql == after_sql - conn.close() - - # Convention #434: документируем, почему ALTER TABLE нельзя использовать - def test_alter_table_cannot_add_unique_constraint(self): - """SQLite не поддерживает ALTER TABLE ADD CONSTRAINT. - - Именно поэтому _migrate() пересоздаёт таблицу вместо ALTER TABLE. - """ - import sqlite3 as _sqlite3 - _conn = _sqlite3.connect(":memory:") - _conn.execute("CREATE TABLE t (a TEXT, b TEXT)") - with pytest.raises(_sqlite3.OperationalError): - _conn.execute("ALTER TABLE t ADD CONSTRAINT uq UNIQUE (a, b)") - _conn.close() - - -# --------------------------------------------------------------------------- -# 13. API: POST /api/project-links возвращает 409 при дублировании -# --------------------------------------------------------------------------- - -class TestProjectLinksDuplicateAPI: - def _create_projects(self, client): - client.post("/api/projects", json={"id": "dup_p2", "name": "P2", "path": "/p2"}) - - def test_create_duplicate_link_returns_409(self, client): - self._create_projects(client) - client.post("/api/project-links", json={ - "from_project": "p1", "to_project": "dup_p2", "type": "depends_on" - }) - r = client.post("/api/project-links", json={ - "from_project": "p1", "to_project": "dup_p2", "type": "depends_on" - }) - assert r.status_code == 409 - assert "already exists" in r.json()["detail"].lower() - - def test_same_projects_different_type_not_duplicate(self, client): - """Одна пара проектов с разными type — не дубликат.""" - self._create_projects(client) - r1 = client.post("/api/project-links", json={ - "from_project": "p1", "to_project": "dup_p2", "type": "depends_on" - }) - r2 = client.post("/api/project-links", json={ - "from_project": "p1", "to_project": "dup_p2", "type": "references" - }) - assert r1.status_code == 201 - assert r2.status_code == 201 diff --git a/tests/test_runner.py b/tests/test_runner.py index acfbf7c..e382c80 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -2713,7 +2713,7 @@ class TestPMStepPipelineLog: run_pipeline(conn, "VDOL-001", steps) # pm_result=None по умолчанию pm_logs = conn.execute( - "SELECT * FROM pipeline_log WHERE message='PM start: task planning' OR message LIKE 'PM done:%'" + "SELECT * FROM pipeline_log WHERE message='PM step: task decomposed'" ).fetchall() assert len(pm_logs) == 0 @@ -2741,7 +2741,7 @@ class TestPMStepPipelineLog: ) pm_logs = conn.execute( - "SELECT * FROM pipeline_log WHERE message='PM start: task planning' OR message LIKE 'PM done:%'" + "SELECT * FROM pipeline_log WHERE message='PM step: task decomposed'" ).fetchall() assert len(pm_logs) == 0