"""Tests for QuickCapture – schnelle Wissenserfassung. Requirements: 3.1, 3.2, 3.3, 3.7, 8.3 """ from __future__ import annotations import re from datetime import datetime from pathlib import Path import pytest from monorepo.knowledge.ingestion.capture import QuickCapture @pytest.fixture def monorepo_root(tmp_path: Path) -> Path: """Create a minimal monorepo structure for testing.""" for ctx in ("bahn", "dhive", "privat"): (tmp_path / ctx / "knowledge" / "inbox").mkdir(parents=True) return tmp_path @pytest.fixture def capture(monorepo_root: Path) -> QuickCapture: """Create a QuickCapture instance.""" return QuickCapture(monorepo_root) # --------------------------------------------------------------------------- # _detect_input_type tests # --------------------------------------------------------------------------- class TestDetectInputType: """Tests for _detect_input_type.""" def test_detects_http_url(self, capture: QuickCapture) -> None: assert capture._detect_input_type("http://example.com") == "url" def test_detects_https_url(self, capture: QuickCapture) -> None: assert capture._detect_input_type("https://docs.python.org/3/") == "url" def test_detects_url_with_whitespace(self, capture: QuickCapture) -> None: assert capture._detect_input_type(" https://example.com ") == "url" def test_detects_file_path_with_extension(self, capture: QuickCapture) -> None: assert capture._detect_input_type("/home/user/notes.md") == "file" def test_detects_windows_path(self, capture: QuickCapture) -> None: assert capture._detect_input_type("C:\\Users\\doc.pdf") == "file" def test_plain_text(self, capture: QuickCapture) -> None: assert capture._detect_input_type("Just a note about something") == "text" def test_text_with_dot_but_no_separator(self, capture: QuickCapture) -> None: # e.g. "version 3.14" should not be detected as file assert capture._detect_input_type("version 3.14 is out") == "text" # --------------------------------------------------------------------------- # _generate_filename tests # --------------------------------------------------------------------------- class TestGenerateFilename: """Tests for _generate_filename.""" def test_filename_format(self, capture: QuickCapture) -> None: filename = capture._generate_filename("My Test Note") # Should match YYYY-MM-DD-HH-MM-slug.md pattern = r"^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-my-test-note\.md$" assert re.match(pattern, filename), f"Unexpected filename: {filename}" def test_filename_slugifies_special_chars(self, capture: QuickCapture) -> None: filename = capture._generate_filename("Hello, World! (2024)") assert "hello-world-2024" in filename def test_filename_limits_slug_length(self, capture: QuickCapture) -> None: long_title = "a" * 100 filename = capture._generate_filename(long_title) # Timestamp is 16 chars (YYYY-MM-DD-HH-MM) + 1 dash + slug (<=50) + .md slug_part = filename.split("-", 5)[-1].replace(".md", "") assert len(slug_part) <= 50 def test_filename_ends_with_md(self, capture: QuickCapture) -> None: filename = capture._generate_filename("test") assert filename.endswith(".md") def test_filename_collapses_multiple_hyphens(self, capture: QuickCapture) -> None: filename = capture._generate_filename("foo---bar///baz") # Should not have consecutive hyphens in slug slug_part = filename[17:] # skip timestamp prefix "YYYY-MM-DD-HH-MM-" assert "--" not in slug_part # --------------------------------------------------------------------------- # capture() integration tests # --------------------------------------------------------------------------- class TestCapture: """Tests for capture().""" def test_capture_text_creates_file( self, capture: QuickCapture, monorepo_root: Path ) -> None: result = capture.capture("Eine schnelle Notiz", context="bahn") assert result.exists() assert result.parent == monorepo_root / "bahn" / "knowledge" / "inbox" def test_capture_text_returns_path(self, capture: QuickCapture) -> None: result = capture.capture("Notiz", context="privat") assert isinstance(result, Path) def test_capture_text_frontmatter( self, capture: QuickCapture, monorepo_root: Path ) -> None: result = capture.capture( "Meeting notes from today", context="dhive", tags=["meeting", "project-x"], ) content = result.read_text(encoding="utf-8") assert "type: inbox" in content assert 'title: "Meeting notes from today"' in content assert "created:" in content assert "source:" in content assert "type: text" in content assert "tags: [meeting, project-x]" in content assert "source_context: dhive" in content def test_capture_url_detected( self, capture: QuickCapture, monorepo_root: Path ) -> None: url = "https://docs.python.org/3/library/pathlib.html" result = capture.capture(url, context="bahn") content = result.read_text(encoding="utf-8") assert "type: url" in content assert f"[{url}]({url})" in content def test_capture_file_detected( self, capture: QuickCapture, monorepo_root: Path ) -> None: filepath = "/home/user/documents/report.pdf" result = capture.capture(filepath, context="privat") content = result.read_text(encoding="utf-8") assert "type: file" in content assert f"Source file: `{filepath}`" in content def test_capture_creates_inbox_dir_if_missing( self, capture: QuickCapture, monorepo_root: Path ) -> None: # Remove inbox dir import shutil inbox = monorepo_root / "bahn" / "knowledge" / "inbox" shutil.rmtree(inbox) assert not inbox.exists() result = capture.capture("test", context="bahn") assert result.exists() assert inbox.exists() def test_capture_empty_tags( self, capture: QuickCapture, monorepo_root: Path ) -> None: result = capture.capture("note", context="bahn") content = result.read_text(encoding="utf-8") assert "tags: []" in content def test_capture_url_title_from_domain( self, capture: QuickCapture, monorepo_root: Path ) -> None: result = capture.capture("https://example.com/docs/intro", context="bahn") content = result.read_text(encoding="utf-8") assert "example.com/docs/intro" in content