|
1 | 1 | """Tests for the indexing service (property graph model + tree-sitter parser).""" |
2 | 2 | from __future__ import annotations |
3 | 3 |
|
| 4 | +import json |
4 | 5 | import sqlite3 |
5 | 6 |
|
6 | 7 | import pytest |
@@ -390,6 +391,33 @@ def test_ignores_non_python_files(self, tmp_path): |
390 | 391 | graph = index_directory(tmp_path) |
391 | 392 | assert len(graph.vertices("module")) == 1 |
392 | 393 |
|
| 394 | + def test_respects_ignore_files_with_trailheadignore_precedence(self, tmp_path): |
| 395 | + from trailhead.services.indexing.walker import index_directory |
| 396 | + |
| 397 | + (tmp_path / ".gitignore").write_text("*.py\n") |
| 398 | + (tmp_path / ".trailheadignore").write_text("!keep.py\n") |
| 399 | + (tmp_path / "keep.py").write_text("def keep():\n pass\n") |
| 400 | + (tmp_path / "drop.py").write_text("def drop():\n pass\n") |
| 401 | + |
| 402 | + graph = index_directory(tmp_path) |
| 403 | + |
| 404 | + module_names = {v.properties["name"] for v in graph.vertices("module")} |
| 405 | + assert module_names == {"keep"} |
| 406 | + |
| 407 | + def test_ignores_directories_listed_in_ignore_files(self, tmp_path): |
| 408 | + from trailhead.services.indexing.walker import index_directory |
| 409 | + |
| 410 | + generated = tmp_path / "generated" |
| 411 | + generated.mkdir() |
| 412 | + (tmp_path / ".gitignore").write_text("generated/\n") |
| 413 | + (tmp_path / "keep.py").write_text("def keep():\n pass\n") |
| 414 | + (generated / "skip.py").write_text("def skip():\n pass\n") |
| 415 | + |
| 416 | + graph = index_directory(tmp_path) |
| 417 | + |
| 418 | + module_names = {v.properties["name"] for v in graph.vertices("module")} |
| 419 | + assert module_names == {"keep"} |
| 420 | + |
393 | 421 | def test_accepts_existing_graph(self, tmp_path): |
394 | 422 | from trailhead.services.indexing.graph import PropertyGraph |
395 | 423 | from trailhead.services.indexing.walker import index_directory |
@@ -517,6 +545,23 @@ def test_persist_append_mode_keeps_existing_rows(self, tmp_path): |
517 | 545 | assert v_count == 4 |
518 | 546 | assert e_count == 2 |
519 | 547 |
|
| 548 | + def test_persist_indexed_files_respects_ignore_files(self, tmp_path): |
| 549 | + from trailhead.services.indexing.sqlite_store import get_indexed_files |
| 550 | + from trailhead.services.indexing.sqlite_store import persist_indexed_files |
| 551 | + |
| 552 | + root = tmp_path / "src" |
| 553 | + root.mkdir() |
| 554 | + (root / ".gitignore").write_text("skip.py\n") |
| 555 | + (root / "keep.py").write_text("def keep():\n pass\n") |
| 556 | + (root / "skip.py").write_text("def skip():\n pass\n") |
| 557 | + db = tmp_path / "graph.db" |
| 558 | + |
| 559 | + count = persist_indexed_files(root, db) |
| 560 | + indexed_files = get_indexed_files(db) |
| 561 | + |
| 562 | + assert count == 1 |
| 563 | + assert set(indexed_files) == {str((root / "keep.py").resolve())} |
| 564 | + |
520 | 565 | def test_persist_vertex_embeddings_writes_rows(self, tmp_path, monkeypatch): |
521 | 566 | from trailhead.services.indexing.graph import PropertyGraph |
522 | 567 | from trailhead.services.indexing.sqlite_store import persist_graph |
@@ -629,3 +674,63 @@ def test_index_command_embed_model_persists_vectors(self, tmp_path, monkeypatch) |
629 | 674 | ] |
630 | 675 | ) |
631 | 676 | assert rc == 0 |
| 677 | + |
| 678 | + |
| 679 | +class TestIndexCommandDryRun: |
| 680 | + def test_dry_run_lists_candidates_without_creating_db(self, tmp_path, capsys): |
| 681 | + from trailhead.cli import app as cli |
| 682 | + |
| 683 | + src = tmp_path / "src" |
| 684 | + src.mkdir() |
| 685 | + (src / ".gitignore").write_text("*.py\n") |
| 686 | + (src / ".trailheadignore").write_text("!keep.py\n") |
| 687 | + (src / "keep.py").write_text("def keep():\n pass\n") |
| 688 | + (src / "drop.py").write_text("def drop():\n pass\n") |
| 689 | + |
| 690 | + rc = cli.main(["index", str(src), "--dry-run"]) |
| 691 | + output = capsys.readouterr().out |
| 692 | + |
| 693 | + assert rc == 0 |
| 694 | + assert "Would index 1 file(s)" in output |
| 695 | + assert "keep.py" in output |
| 696 | + assert "drop.py" not in output |
| 697 | + assert not (src / ".trailhead").exists() |
| 698 | + |
| 699 | + def test_dry_run_json_uses_preview_schema(self, tmp_path, capsys): |
| 700 | + from trailhead.cli import app as cli |
| 701 | + |
| 702 | + src = tmp_path / "src" |
| 703 | + src.mkdir() |
| 704 | + (src / "keep.py").write_text("def keep():\n pass\n") |
| 705 | + |
| 706 | + rc = cli.main(["index", str(src), "--dry-run", "--output", "json"]) |
| 707 | + payload = json.loads(capsys.readouterr().out) |
| 708 | + |
| 709 | + assert rc == 0 |
| 710 | + assert payload == { |
| 711 | + "root": str(src.resolve()), |
| 712 | + "count": 1, |
| 713 | + "files": ["keep.py"], |
| 714 | + } |
| 715 | + |
| 716 | + @pytest.mark.parametrize( |
| 717 | + "argv", |
| 718 | + [ |
| 719 | + ["index", "src", "--dry-run", "--watch"], |
| 720 | + ["index", "src", "--dry-run", "--in-memory"], |
| 721 | + ["index", "src", "--dry-run", "--sqlite-db", "graph.db"], |
| 722 | + ["index", "src", "--dry-run", "--embed-model", "sentence-transformers/all-MiniLM-L6-v2"], |
| 723 | + ], |
| 724 | + ) |
| 725 | + def test_dry_run_rejects_incompatible_flags(self, tmp_path, argv): |
| 726 | + from trailhead.cli import app as cli |
| 727 | + |
| 728 | + src = tmp_path / "src" |
| 729 | + src.mkdir() |
| 730 | + (src / "keep.py").write_text("def keep():\n pass\n") |
| 731 | + |
| 732 | + normalized_argv = [str(src) if arg == "src" else arg for arg in argv] |
| 733 | + |
| 734 | + rc = cli.main(normalized_argv) |
| 735 | + |
| 736 | + assert rc == 1 |
0 commit comments