feat: implement knowledge management system (spec complete, all 53 tasks done)

This commit is contained in:
2026-07-01 14:14:16 +02:00
parent e4256d3222
commit 8ac664d33d
89 changed files with 19759 additions and 72 deletions
@@ -0,0 +1,178 @@
"""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