"""Integration tests for incremental graphify extract behavior.""" from __future__ import annotations import json import os import subprocess import sys from pathlib import Path import pytest PYTHON = sys.executable # Backend-selecting env vars. These tests assume no working LLM backend (a docs # corpus should fail without one); strip them so a developer who has a real # ANTHROPIC_API_KEY / OPENAI_API_KEY / etc. exported does not make a docs extract # succeed and break the "no backend" path. CI has none of these set anyway. _LLM_ENV_KEYS = ( "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "MOONSHOT_API_KEY", "DEEPSEEK_API_KEY", "OLLAMA_BASE_URL", "AWS_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_ACCESS_KEY_ID", ) def _run(args: list[str], cwd: Path) -> subprocess.CompletedProcess: env = {k: v for k, v in os.environ.items() if k not in _LLM_ENV_KEYS} return subprocess.run( [PYTHON, "-m", "graphify"] + args, cwd=cwd, capture_output=True, text=True, env=env, ) def _make_docs_corpus(tmp_path: Path) -> Path: docs = tmp_path / "docs" docs.mkdir() (docs / "intro.md").write_text("# Introduction\nThis doc introduces the system.") (docs / "api.md").write_text("# API Reference\nThe API has endpoints.") return docs def test_manifest_written_after_extract(tmp_path): """After a full extract run, manifest.json must exist (or run fails before writing it).""" docs = _make_docs_corpus(tmp_path) r = _run(["extract", str(docs)], tmp_path) # Should fail with no API key — but NOT with a path error assert "no LLM API key" in r.stderr or r.returncode != 0 # manifest should NOT exist (run failed before writing) manifest = docs / "graphify-out" / "manifest.json" assert not manifest.exists() def test_incremental_mode_detected_via_manifest(tmp_path): """If manifest.json + graph.json exist, incremental mode message is shown.""" docs = _make_docs_corpus(tmp_path) out = docs / "graphify-out" out.mkdir() (out / "graph.json").write_text(json.dumps({"nodes": [], "links": []})) (out / "manifest.json").write_text(json.dumps({"document": [str(docs / "intro.md")]})) r = _run(["extract", str(docs)], tmp_path) combined = r.stdout + r.stderr assert "incremental" in combined.lower() or r.returncode != 0 def test_no_incremental_without_manifest(tmp_path): """Without manifest.json, full scan message is shown (not incremental).""" docs = _make_docs_corpus(tmp_path) r = _run(["extract", str(docs)], tmp_path) # Check combined output doesn't contain incremental-mode phrasing. # Use a phrase rather than a bare word to avoid matching the tmp_path, # which pytest derives from the test name and contains "incremental". assert "incremental update" not in r.stdout.lower() assert "incremental scan" not in r.stdout.lower()