kin: KIN-OBS-027 Исправить catch(e: any) и setTimeout cleanup в LiveConsole.vue
This commit is contained in:
parent
79757a3120
commit
b58da600d4
2 changed files with 111 additions and 2 deletions
|
|
@ -740,3 +740,112 @@ 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
|
||||
|
|
|
|||
|
|
@ -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 step: task decomposed'"
|
||||
"SELECT * FROM pipeline_log WHERE message='PM start: task planning' OR message LIKE 'PM done:%'"
|
||||
).fetchall()
|
||||
assert len(pm_logs) == 0
|
||||
|
||||
|
|
@ -2741,7 +2741,7 @@ class TestPMStepPipelineLog:
|
|||
)
|
||||
|
||||
pm_logs = conn.execute(
|
||||
"SELECT * FROM pipeline_log WHERE message='PM step: task decomposed'"
|
||||
"SELECT * FROM pipeline_log WHERE message='PM start: task planning' OR message LIKE 'PM done:%'"
|
||||
).fetchall()
|
||||
assert len(pm_logs) == 0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue