diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c16c13..b75db29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,8 +12,9 @@ jobs: name: Portable / Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v6 @@ -35,6 +36,84 @@ jobs: --ignore=src/session_recall/codex_fix/tests/test_v5_sandbox.py --ignore=src/session_recall/codex_fix/tests/test_v5_workflow.py + windows-test: + name: Windows / Python 3.12 + runs-on: windows-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install package + run: pip install -e ".[dev]" + + - name: Lint with ruff + run: ruff check src/ + + # The Codex launcher and fixer are POSIX-only (deploy/install-codex.md); + # native Windows covers recall for Copilot CLI and Claude Code. + - name: Run tests + shell: bash + run: >- + pytest src/ -q -W error + --ignore=src/session_recall/codex_fix + --ignore=src/session_recall/providers/codex + + package-smoke: + name: Package / wheel install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Build wheel + run: | + pip install build + python -m build --wheel + + - name: Install wheel into a clean environment + run: | + python -m venv "$RUNNER_TEMP/pkg" + "$RUNNER_TEMP/pkg/bin/pip" install dist/*.whl + + - name: Check console scripts and bundled data + env: + SESSION_RECALL_ENABLE_CLAUDE_BACKEND: "1" + run: | + set -euo pipefail + expected="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")" + src_pyz="$(ls src/session_recall/codex_fix/data/artifacts/*.pyz | wc -l)" + bin="$RUNNER_TEMP/pkg/bin" + cd "$RUNNER_TEMP" + for cmd in session-recall session-recall-cc session-recall-claude \ + session-recall-codex session-recall-codex-fix; do + "$bin/$cmd" --help > /dev/null + done + test "$("$bin/session-recall" --version)" = "auto-memory $expected" + test "$("$bin/session-recall-codex" --version)" = "$expected" + test "$("$bin/session-recall-codex-fix" --version)" = "$expected" + "$bin/python" - "$src_pyz" <<'EOF' + import sys + from importlib.resources import files + fix = files("session_recall.codex_fix") / "data" + codex = files("session_recall.providers.codex") / "verifications" + for f in (fix / "catalogue-v1.json", fix / "model-policy-v3.json", + codex / "captured-profiles.json"): + assert f.is_file(), f"missing from wheel: {f}" + pyz = [p for p in (fix / "artifacts").iterdir() if p.name.endswith(".pyz")] + assert len(pyz) == int(sys.argv[1]), f"wheel has {len(pyz)} .pyz, source has {sys.argv[1]}" + print(f"package data ok ({len(pyz)} artifacts)") + EOF + v5-stage-c-macos: name: V5 Stage C / macOS ARM64 runs-on: macos-15 diff --git a/src/session_recall/tests/e2e/conftest.py b/src/session_recall/tests/e2e/conftest.py index 2d1e7f4..1516d48 100644 --- a/src/session_recall/tests/e2e/conftest.py +++ b/src/session_recall/tests/e2e/conftest.py @@ -5,6 +5,7 @@ import sqlite3 import subprocess import sys +import tempfile from datetime import datetime, timedelta, timezone import pytest @@ -244,7 +245,7 @@ def empty_db(tmp_path_factory): def run_cli(*args: str, db_path: str | None = None) -> subprocess.CompletedProcess: """Invoke session-recall as a subprocess with optional DB override. - Runs from /tmp to avoid auto-detection of the current git repo, + Runs from the system temp dir to avoid auto-detection of the current git repo, ensuring the fixture DB's repos are visible without --repo filtering. """ env = os.environ.copy() @@ -257,7 +258,7 @@ def run_cli(*args: str, db_path: str | None = None) -> subprocess.CompletedProce text=True, timeout=30, env=env, - cwd="/tmp", + cwd=tempfile.gettempdir(), ) diff --git a/src/session_recall/tests/e2e/test_e2e_health_provider.py b/src/session_recall/tests/e2e/test_e2e_health_provider.py index bf20f51..b899862 100644 --- a/src/session_recall/tests/e2e/test_e2e_health_provider.py +++ b/src/session_recall/tests/e2e/test_e2e_health_provider.py @@ -4,6 +4,9 @@ import os import subprocess import sys +import tempfile + +import pytest def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): @@ -18,10 +21,15 @@ def _run_health(*extra_args: str, env_override: dict[str, str] | None = None): text=True, timeout=30, env=env, - cwd="/tmp", + cwd=tempfile.gettempdir(), ) +# Human-readable health prints zone emoji; Windows' default cp1252 pipe +# encoding cannot encode them. Strict so CI flags this once it is fixed. +@pytest.mark.xfail( + sys.platform == "win32", reason="health emoji on cp1252 stdout", strict=True +) def test_e2e_health_default_still_works(): result = _run_health() assert result.returncode == 0, f"stderr: {result.stderr}" diff --git a/src/session_recall/tests/test_copilot_cli_labels.py b/src/session_recall/tests/test_copilot_cli_labels.py new file mode 100644 index 0000000..f3a8b2f --- /dev/null +++ b/src/session_recall/tests/test_copilot_cli_labels.py @@ -0,0 +1,51 @@ +"""Tests for Copilot CLI workspace labels and repo detection.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +from session_recall.providers.copilot_cli import _labels + +posix_only = pytest.mark.skipif( + sys.platform == "win32", reason="POSIX path rendering" +) + + +@pytest.mark.parametrize("value", [None, ""]) +def test_label_empty_path_returns_none(value) -> None: + assert _labels._local_workspace_label(value) is None + + +@posix_only +def test_label_absolute_path_is_unchanged() -> None: + assert _labels._local_workspace_label("/work/app") == "local:/work/app" + + +@posix_only +def test_label_relative_path_keeps_forward_slashes() -> None: + assert _labels._local_workspace_label("projects/app") == "local:projects/app" + + +@posix_only +def test_label_expands_home(monkeypatch, tmp_path: Path) -> None: + monkeypatch.setenv("HOME", str(tmp_path)) + assert _labels._local_workspace_label("~/app") == f"local:{tmp_path}/app" + + +def test_detect_repo_from_directory(monkeypatch, tmp_path: Path) -> None: + monkeypatch.setattr( + _labels, "detect_repo_for_cwd", lambda p: "o/r" if p == str(tmp_path) else None + ) + assert _labels._detect_repo_for_path(str(tmp_path)) == "o/r" + + +def test_detect_repo_from_file_uses_its_directory(monkeypatch, tmp_path: Path) -> None: + target = tmp_path / "notes.md" + target.write_text("x", encoding="utf-8") + monkeypatch.setattr( + _labels, "detect_repo_for_cwd", lambda p: "o/r" if p == str(tmp_path) else None + ) + assert _labels._detect_repo_for_path(str(target)) == "o/r" diff --git a/src/session_recall/tests/test_provider_backends.py b/src/session_recall/tests/test_provider_backends.py index 4bef207..f04e541 100644 --- a/src/session_recall/tests/test_provider_backends.py +++ b/src/session_recall/tests/test_provider_backends.py @@ -3,8 +3,11 @@ from __future__ import annotations import json +import sys from pathlib import Path +import pytest + from session_recall.providers.copilot_cli import CopilotCliProvider from session_recall.providers.file import ( VSCodeProvider, @@ -13,6 +16,12 @@ _is_wsl, ) +# Windows Python rewrites POSIX/WSL session paths (/home/x -> \home\x). +# Fixed by PR #29; strict so CI flags these once the fix lands. +posix_paths_on_windows = pytest.mark.xfail( + sys.platform == "win32", reason="POSIX paths rewritten on Windows (#29)", strict=True +) + def test_vscode_kind1_inputtext_extraction() -> None: row = { @@ -126,6 +135,7 @@ def test_cli_fallback_prefers_context_repository_field(tmp_path: Path) -> None: assert results[0]["repository"] == "dezgit2025/auto-memory" +@posix_paths_on_windows def test_cli_fallback_uses_tool_path_to_infer_repository( monkeypatch, tmp_path: Path ) -> None: @@ -176,6 +186,7 @@ def test_cli_fallback_uses_tool_path_to_infer_repository( assert sessions[0]["repository"] == "dezgit2025/auto-memory" +@posix_paths_on_windows def test_cli_fallback_labels_non_repo_session_as_local_workspace( tmp_path: Path, ) -> None: