diff --git a/cli/main.py b/cli/main.py index a8ae0cb..6752d61 100644 --- a/cli/main.py +++ b/cli/main.py @@ -6,7 +6,7 @@ Uses core.models for all data access, never raw SQL. import json import os import sys -from datetime import datetime, timezone +from datetime import datetime from pathlib import Path import click @@ -618,13 +618,13 @@ def run_task(ctx, task_id, dry_run, allow_write): # Step 1: PM decomposes click.echo("Running PM to decompose task...") - pm_started_at = datetime.now(timezone.utc).isoformat() + pm_started_at = datetime.utcnow().isoformat() pm_result = run_agent( conn, "pm", task_id, project_id, model="sonnet", dry_run=dry_run, allow_write=allow_write, noninteractive=is_noninteractive, ) - pm_ended_at = datetime.now(timezone.utc).isoformat() + pm_ended_at = datetime.utcnow().isoformat() if dry_run: click.echo("\n--- PM Prompt (dry-run) ---") diff --git a/tests/test_api.py b/tests/test_api.py index 1107d71..081824a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2342,102 +2342,3 @@ def test_get_pipeline_logs_since_id_returns_pm_entries(client): done_log = next(log for log in logs if "PM done" in log["message"]) assert done_log["extra_json"]["steps_count"] == 2 assert done_log["extra_json"]["tokens_used"] == 1000 - - -# --------------------------------------------------------------------------- -# KIN-103 — PATCH /api/projects/{id} — worktrees_enabled toggle -# --------------------------------------------------------------------------- - -def test_patch_project_worktrees_enabled_true(client): - """PATCH с worktrees_enabled=true → 200, поле установлено в 1.""" - r = client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - assert r.status_code == 200 - assert r.json()["worktrees_enabled"] == 1 - - -def test_patch_project_worktrees_enabled_false(client): - """После включения PATCH с worktrees_enabled=false → 200, поле установлено в 0.""" - client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - r = client.patch("/api/projects/p1", json={"worktrees_enabled": False}) - assert r.status_code == 200 - assert r.json()["worktrees_enabled"] == 0 - - -def test_patch_project_worktrees_enabled_true_persisted_via_sql(client): - """После PATCH worktrees_enabled=True прямой SQL подтверждает значение 1.""" - client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - - from core.db import init_db - conn = init_db(api_module.DB_PATH) - row = conn.execute("SELECT worktrees_enabled FROM projects WHERE id = 'p1'").fetchone() - conn.close() - assert row is not None - assert row[0] == 1 - - -def test_patch_project_worktrees_enabled_false_persisted_via_sql(client): - """После PATCH worktrees_enabled=False прямой SQL подтверждает значение 0.""" - client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - client.patch("/api/projects/p1", json={"worktrees_enabled": False}) - - from core.db import init_db - conn = init_db(api_module.DB_PATH) - row = conn.execute("SELECT worktrees_enabled FROM projects WHERE id = 'p1'").fetchone() - conn.close() - assert row is not None - assert row[0] == 0 - - -def test_patch_project_worktrees_enabled_null_before_first_update(client): - """Новый проект имеет worktrees_enabled=0 (DEFAULT) до первого обновления.""" - client.post("/api/projects", json={"id": "p_new", "name": "New", "path": "/new"}) - - from core.db import init_db - conn = init_db(api_module.DB_PATH) - row = conn.execute("SELECT worktrees_enabled FROM projects WHERE id = 'p_new'").fetchone() - conn.close() - assert row is not None - assert not row[0] # DEFAULT 0 или NULL — в любом случае falsy - - -def test_patch_project_worktrees_enabled_get_includes_field(client): - """GET проекта включает worktrees_enabled в ответе.""" - r = client.get("/api/projects/p1") - assert r.status_code == 200 - data = r.json() - assert "worktrees_enabled" in data - - -def test_patch_project_worktrees_enabled_and_autocommit_together(client): - """PATCH с worktrees_enabled и autocommit_enabled → оба поля обновлены.""" - r = client.patch("/api/projects/p1", json={ - "worktrees_enabled": True, - "autocommit_enabled": True, - }) - assert r.status_code == 200 - data = r.json() - assert data["worktrees_enabled"] == 1 - assert data["autocommit_enabled"] == 1 - - -def test_patch_project_worktrees_enabled_no_change_when_not_in_patch(client): - """PATCH без worktrees_enabled → поле не меняется.""" - # Сначала установим worktrees_enabled=1 - client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - # Потом патч без worktrees_enabled не должен его менять - r = client.patch("/api/projects/p1", json={"autocommit_enabled": True}) - assert r.status_code == 200 - assert r.json()["worktrees_enabled"] == 1 - - -def test_patch_project_worktrees_enabled_toggle_sequence(client): - """Последовательные включение/выключение worktrees_enabled.""" - # Включаем - r1 = client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - assert r1.json()["worktrees_enabled"] == 1 - # Отключаем - r2 = client.patch("/api/projects/p1", json={"worktrees_enabled": False}) - assert r2.json()["worktrees_enabled"] == 0 - # Снова включаем - r3 = client.patch("/api/projects/p1", json={"worktrees_enabled": True}) - assert r3.json()["worktrees_enabled"] == 1 diff --git a/tests/test_kin_104_regression.py b/tests/test_kin_104_regression.py deleted file mode 100644 index 81fe9e1..0000000 --- a/tests/test_kin_104_regression.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Regression tests for KIN-104 — замена datetime.utcnow() на datetime.now(timezone.utc) в cli/main.py. - -AC1: datetime.utcnow() отсутствует в cli/main.py. -AC2: timezone импортирован из datetime в cli/main.py. -AC3: datetime.now(timezone.utc).isoformat() возвращает строку с суффиксом +00:00 (aware datetime). -""" - -import re -from datetime import datetime, timezone -from pathlib import Path - -import pytest - - -CLI_MAIN = Path(__file__).parent.parent / "cli" / "main.py" - - -# --------------------------------------------------------------------------- -# AC1: utcnow() отсутствует в источнике -# --------------------------------------------------------------------------- - -def test_cli_main_does_not_contain_utcnow(): - """cli/main.py не должен содержать вызовов datetime.utcnow().""" - source = CLI_MAIN.read_text(encoding="utf-8") - assert "utcnow" not in source, "Найден устаревший вызов utcnow() в cli/main.py" - - -# --------------------------------------------------------------------------- -# AC2: timezone импортирован -# --------------------------------------------------------------------------- - -def test_cli_main_imports_timezone(): - """cli/main.py должен импортировать timezone из datetime.""" - source = CLI_MAIN.read_text(encoding="utf-8") - # Принимаем: from datetime import datetime, timezone или from datetime import ..., timezone, ... - assert re.search(r"from datetime import [^#\n]*timezone", source), \ - "timezone не импортирован из datetime в cli/main.py" - - -# --------------------------------------------------------------------------- -# AC3: aware datetime → isoformat содержит +00:00 -# --------------------------------------------------------------------------- - -def test_now_timezone_utc_isoformat_contains_utc_offset(): - """datetime.now(timezone.utc).isoformat() должен содержать '+00:00'.""" - ts = datetime.now(timezone.utc).isoformat() - assert "+00:00" in ts, f"Ожидался суффикс +00:00, получено: {ts}" - - -def test_now_timezone_utc_is_aware(): - """datetime.now(timezone.utc) должен возвращать aware datetime (tzinfo не None).""" - dt = datetime.now(timezone.utc) - assert dt.tzinfo is not None, "datetime.now(timezone.utc) вернул naive datetime" diff --git a/web/api.py b/web/api.py index 86a0a83..b6fc146 100644 --- a/web/api.py +++ b/web/api.py @@ -233,7 +233,6 @@ class ProjectPatch(BaseModel): execution_mode: str | None = None autocommit_enabled: bool | None = None auto_test_enabled: bool | None = None - worktrees_enabled: bool | None = None obsidian_vault_path: str | None = None deploy_command: str | None = None deploy_host: str | None = None @@ -252,7 +251,7 @@ class ProjectPatch(BaseModel): def patch_project(project_id: str, body: ProjectPatch): has_any = any([ body.execution_mode, body.autocommit_enabled is not None, - body.auto_test_enabled is not None, body.worktrees_enabled is not None, + body.auto_test_enabled is not None, body.obsidian_vault_path, body.deploy_command is not None, body.deploy_host is not None, body.deploy_path is not None, body.deploy_runtime is not None, body.deploy_restart_cmd is not None, @@ -281,8 +280,6 @@ def patch_project(project_id: str, body: ProjectPatch): fields["autocommit_enabled"] = int(body.autocommit_enabled) if body.auto_test_enabled is not None: fields["auto_test_enabled"] = int(body.auto_test_enabled) - if body.worktrees_enabled is not None: - fields["worktrees_enabled"] = int(body.worktrees_enabled) if body.obsidian_vault_path is not None: fields["obsidian_vault_path"] = body.obsidian_vault_path if body.deploy_command is not None: