122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
|
|
"""Regression tests for KIN-ARCH-002.
|
|||
|
|
|
|||
|
|
Проблема: функция create_project_with_phases имела нестабильную сигнатуру —
|
|||
|
|
параметр path с дефолтом на позиции 4, после чего шли обязательные параметры
|
|||
|
|
(description, selected_roles), что могло приводить к SyntaxError при инвалидации
|
|||
|
|
.pyc-кеша в Python 3.14+.
|
|||
|
|
|
|||
|
|
Фикс: параметры path переносится после обязательных ИЛИ изолируется через *
|
|||
|
|
(keyword-only) — текущий код использует * для description/selected_roles.
|
|||
|
|
|
|||
|
|
Тесты покрывают:
|
|||
|
|
1. Вызов с path как позиционным аргументом (текущая конвенция в тестах)
|
|||
|
|
2. Вызов с path=... как keyword-аргументом (безопасная конвенция)
|
|||
|
|
3. Вызов без path=None (дефолт работает)
|
|||
|
|
4. Нет SyntaxError при импорте core.phases (regression guard)
|
|||
|
|
5. Стабильность числа тестов: полный suite запускается без collection errors
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
from core.db import init_db
|
|||
|
|
from core import models
|
|||
|
|
from core.phases import create_project_with_phases
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def conn():
|
|||
|
|
c = init_db(db_path=":memory:")
|
|||
|
|
yield c
|
|||
|
|
c.close()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# KIN-ARCH-002 — regression: signature stability of create_project_with_phases
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_import_core_phases_no_syntax_error():
|
|||
|
|
"""KIN-ARCH-002: импорт core.phases не вызывает SyntaxError."""
|
|||
|
|
import core.phases # noqa: F401 — если упадёт SyntaxError, тест падает
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_path_as_positional_arg(conn):
|
|||
|
|
"""KIN-ARCH-002: path передаётся как позиционный аргумент (4-я позиция).
|
|||
|
|
|
|||
|
|
Текущая конвенция во всех тестах и в web/api.py.
|
|||
|
|
Регрессионная защита: изменение сигнатуры не должно сломать этот вызов.
|
|||
|
|
"""
|
|||
|
|
result = create_project_with_phases(
|
|||
|
|
conn, "arch002a", "Project A", "/some/path",
|
|||
|
|
description="Описание A", selected_roles=["business_analyst"],
|
|||
|
|
)
|
|||
|
|
assert result["project"]["id"] == "arch002a"
|
|||
|
|
assert len(result["phases"]) == 2 # business_analyst + architect
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_path_as_keyword_arg(conn):
|
|||
|
|
"""KIN-ARCH-002: path передаётся как keyword-аргумент.
|
|||
|
|
|
|||
|
|
Рекомендуемая конвенция по итогам debugger-расследования.
|
|||
|
|
Гарантирует, что будущий рефакторинг сигнатуры не сломает код.
|
|||
|
|
"""
|
|||
|
|
result = create_project_with_phases(
|
|||
|
|
conn, "arch002b", "Project B",
|
|||
|
|
description="Описание B",
|
|||
|
|
selected_roles=["tech_researcher"],
|
|||
|
|
path="/keyword/path",
|
|||
|
|
)
|
|||
|
|
assert result["project"]["id"] == "arch002b"
|
|||
|
|
assert result["project"]["path"] == "/keyword/path"
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_path_none_without_operations_raises(conn):
|
|||
|
|
"""KIN-ARCH-002: path=None для non-operations проекта → IntegrityError из БД (CHECK constraint)."""
|
|||
|
|
import sqlite3
|
|||
|
|
with pytest.raises(sqlite3.IntegrityError, match="CHECK constraint"):
|
|||
|
|
create_project_with_phases(
|
|||
|
|
conn, "arch002fail", "Fail",
|
|||
|
|
description="D",
|
|||
|
|
selected_roles=["marketer"],
|
|||
|
|
path=None,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_phases_count_is_deterministic(conn):
|
|||
|
|
"""KIN-ARCH-002: при каждом вызове создаётся ровно N+1 фаз (N researchers + architect)."""
|
|||
|
|
for idx, (roles, expected_count) in enumerate([
|
|||
|
|
(["business_analyst"], 2),
|
|||
|
|
(["business_analyst", "tech_researcher"], 3),
|
|||
|
|
(["business_analyst", "market_researcher", "legal_researcher"], 4),
|
|||
|
|
]):
|
|||
|
|
project_id = f"arch002_det_{idx}"
|
|||
|
|
result = create_project_with_phases(
|
|||
|
|
conn, project_id, f"Project {len(roles)}",
|
|||
|
|
description="Det test",
|
|||
|
|
selected_roles=roles,
|
|||
|
|
path=f"/tmp/det/{idx}",
|
|||
|
|
)
|
|||
|
|
assert len(result["phases"]) == expected_count, (
|
|||
|
|
f"roles={roles}: ожидали {expected_count} фаз, "
|
|||
|
|
f"получили {len(result['phases'])}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_arch_002_first_phase_active_regardless_of_call_convention(conn):
|
|||
|
|
"""KIN-ARCH-002: первая фаза всегда active независимо от способа передачи path."""
|
|||
|
|
# Positional convention
|
|||
|
|
r1 = create_project_with_phases(
|
|||
|
|
conn, "p_pos", "P pos", "/pos",
|
|||
|
|
description="D", selected_roles=["business_analyst"],
|
|||
|
|
)
|
|||
|
|
assert r1["phases"][0]["status"] == "active"
|
|||
|
|
assert r1["phases"][0]["task_id"] is not None
|
|||
|
|
|
|||
|
|
# Keyword convention
|
|||
|
|
r2 = create_project_with_phases(
|
|||
|
|
conn, "p_kw", "P kw",
|
|||
|
|
description="D", selected_roles=["business_analyst"], path="/kw",
|
|||
|
|
)
|
|||
|
|
assert r2["phases"][0]["status"] == "active"
|
|||
|
|
assert r2["phases"][0]["task_id"] is not None
|