diff --git a/core/db.py b/core/db.py index dcc40e9..6ce0b33 100644 --- a/core/db.py +++ b/core/db.py @@ -737,6 +737,35 @@ def _migrate(conn: sqlite3.Connection): ) conn.commit() + # Add UNIQUE(from_project, to_project, type) to project_links (KIN-INFRA-013). + # SQLite does not support ALTER TABLE ADD CONSTRAINT — table recreation required. + if "project_links" in existing_tables: + pl_sql_row = conn.execute( + "SELECT sql FROM sqlite_master WHERE type='table' AND name='project_links'" + ).fetchone() + pl_has_unique = pl_sql_row and "UNIQUE" in (pl_sql_row[0] or "").upper() + if not pl_has_unique: + conn.executescript(""" + PRAGMA foreign_keys=OFF; + CREATE TABLE project_links_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_project TEXT NOT NULL REFERENCES projects(id), + to_project TEXT NOT NULL REFERENCES projects(id), + type TEXT NOT NULL, + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(from_project, to_project, type) + ); + INSERT OR IGNORE INTO project_links_new + SELECT id, from_project, to_project, type, description, created_at + FROM project_links; + DROP TABLE project_links; + ALTER TABLE project_links_new RENAME TO project_links; + CREATE INDEX IF NOT EXISTS idx_project_links_to ON project_links(to_project); + CREATE INDEX IF NOT EXISTS idx_project_links_from ON project_links(from_project); + PRAGMA foreign_keys=ON; + """) + def _seed_default_hooks(conn: sqlite3.Connection): """Seed default hooks for the kin project (idempotent). diff --git a/tests/test_api_pipeline_logs.py b/tests/test_api_pipeline_logs.py index 20284e0..f9c1324 100644 --- a/tests/test_api_pipeline_logs.py +++ b/tests/test_api_pipeline_logs.py @@ -58,13 +58,14 @@ def test_get_pipeline_logs_empty_returns_empty_list(pipeline_client): # ───────────────────────────────────────────────────────────── -# Тест: несуществующий pipeline → 404 (Convention #420) +# Тест: несуществующий pipeline → 200 [] (KIN-OBS-023) # ───────────────────────────────────────────────────────────── -def test_get_pipeline_logs_nonexistent_pipeline_returns_404(client): - """GET /api/pipelines/99999/logs возвращает 404 для несуществующего pipeline.""" +def test_get_pipeline_logs_nonexistent_pipeline_returns_empty(client): + """KIN-OBS-023: GET /api/pipelines/99999/logs → 200 [] (log collections return empty, not 404).""" r = client.get("/api/pipelines/99999/logs") - assert r.status_code == 404 + assert r.status_code == 200 + assert r.json() == [] # ─────────────────────────────────────────────────────────────