121 lines
5.2 KiB
Python
121 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
|