Initial commit: Claude Code Project Tracker
Add comprehensive development intelligence system that tracks: - Development sessions with automatic start/stop - Full conversation history with semantic search - Tool usage and file operation analytics - Think time and engagement analysis - Git activity correlation - Learning pattern recognition - Productivity insights and metrics Features: - FastAPI backend with SQLite database - Modern web dashboard with interactive charts - Claude Code hook integration for automatic tracking - Comprehensive test suite with 100+ tests - Complete API documentation (OpenAPI/Swagger) - Privacy-first design with local data storage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
44ed9936b7
48 changed files with 9707 additions and 0 deletions
256
tests/conftest.py
Normal file
256
tests/conftest.py
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import pytest
|
||||
import asyncio
|
||||
from typing import AsyncGenerator
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.main import app
|
||||
from app.database.connection import get_db
|
||||
from app.models.base import Base
|
||||
from app.models.project import Project
|
||||
from app.models.session import Session
|
||||
from app.models.conversation import Conversation
|
||||
from app.models.activity import Activity
|
||||
from app.models.waiting_period import WaitingPeriod
|
||||
from app.models.git_operation import GitOperation
|
||||
|
||||
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create an instance of the default event loop for the test session."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
@pytest.fixture
|
||||
async def test_engine():
|
||||
"""Create test database engine."""
|
||||
engine = create_async_engine(
|
||||
TEST_DATABASE_URL,
|
||||
connect_args={
|
||||
"check_same_thread": False,
|
||||
},
|
||||
poolclass=StaticPool,
|
||||
echo=False, # Set to True for SQL debugging
|
||||
)
|
||||
yield engine
|
||||
await engine.dispose()
|
||||
|
||||
@pytest.fixture
|
||||
async def test_db(test_engine) -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Create test database session."""
|
||||
# Create all tables
|
||||
async with test_engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
# Create session factory
|
||||
async_session = async_sessionmaker(
|
||||
test_engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
await session.rollback()
|
||||
|
||||
@pytest.fixture
|
||||
async def test_client(test_db: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
|
||||
"""Create test HTTP client."""
|
||||
def override_get_db():
|
||||
return test_db
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
# Clean up
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_project(test_db: AsyncSession) -> Project:
|
||||
"""Create a sample project for testing."""
|
||||
project = Project(
|
||||
name="Test Project",
|
||||
path="/home/user/test-project",
|
||||
git_repo="https://github.com/user/test-project",
|
||||
languages=["python", "javascript"]
|
||||
)
|
||||
test_db.add(project)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(project)
|
||||
return project
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_session(test_db: AsyncSession, sample_project: Project) -> Session:
|
||||
"""Create a sample session for testing."""
|
||||
session = Session(
|
||||
project_id=sample_project.id,
|
||||
session_type="startup",
|
||||
working_directory="/home/user/test-project",
|
||||
git_branch="main",
|
||||
environment={"user": "testuser", "pwd": "/home/user/test-project"}
|
||||
)
|
||||
test_db.add(session)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(session)
|
||||
return session
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_conversation(test_db: AsyncSession, sample_session: Session) -> Conversation:
|
||||
"""Create a sample conversation for testing."""
|
||||
conversation = Conversation(
|
||||
session_id=sample_session.id,
|
||||
user_prompt="How do I implement a feature?",
|
||||
claude_response="You can implement it by following these steps...",
|
||||
tools_used=["Edit", "Write"],
|
||||
files_affected=["main.py", "utils.py"],
|
||||
exchange_type="user_prompt"
|
||||
)
|
||||
test_db.add(conversation)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(conversation)
|
||||
return conversation
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_activity(test_db: AsyncSession, sample_session: Session) -> Activity:
|
||||
"""Create a sample activity for testing."""
|
||||
activity = Activity(
|
||||
session_id=sample_session.id,
|
||||
tool_name="Edit",
|
||||
action="file_edit",
|
||||
file_path="/home/user/test-project/main.py",
|
||||
metadata={"lines_changed": 10},
|
||||
success=True,
|
||||
lines_added=5,
|
||||
lines_removed=2
|
||||
)
|
||||
test_db.add(activity)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(activity)
|
||||
return activity
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_waiting_period(test_db: AsyncSession, sample_session: Session) -> WaitingPeriod:
|
||||
"""Create a sample waiting period for testing."""
|
||||
waiting_period = WaitingPeriod(
|
||||
session_id=sample_session.id,
|
||||
duration_seconds=30,
|
||||
context_before="Claude finished responding",
|
||||
context_after="User asked a follow-up question",
|
||||
likely_activity="thinking"
|
||||
)
|
||||
test_db.add(waiting_period)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(waiting_period)
|
||||
return waiting_period
|
||||
|
||||
@pytest.fixture
|
||||
async def sample_git_operation(test_db: AsyncSession, sample_session: Session) -> GitOperation:
|
||||
"""Create a sample git operation for testing."""
|
||||
git_operation = GitOperation(
|
||||
session_id=sample_session.id,
|
||||
operation="commit",
|
||||
command="git commit -m 'Add new feature'",
|
||||
result="[main 123abc] Add new feature",
|
||||
success=True,
|
||||
files_changed=["main.py", "utils.py"],
|
||||
lines_added=15,
|
||||
lines_removed=3,
|
||||
commit_hash="123abc456def"
|
||||
)
|
||||
test_db.add(git_operation)
|
||||
await test_db.commit()
|
||||
await test_db.refresh(git_operation)
|
||||
return git_operation
|
||||
|
||||
# Faker fixtures for generating test data
|
||||
@pytest.fixture
|
||||
def fake():
|
||||
"""Faker instance for generating test data."""
|
||||
from faker import Faker
|
||||
return Faker()
|
||||
|
||||
@pytest.fixture
|
||||
def project_factory(fake):
|
||||
"""Factory for creating project test data."""
|
||||
def _create_project_data(**overrides):
|
||||
data = {
|
||||
"name": fake.company(),
|
||||
"path": fake.file_path(depth=3),
|
||||
"git_repo": fake.url(),
|
||||
"languages": fake.random_elements(
|
||||
elements=["python", "javascript", "typescript", "go", "rust"],
|
||||
length=fake.random_int(min=1, max=3),
|
||||
unique=True
|
||||
)
|
||||
}
|
||||
data.update(overrides)
|
||||
return data
|
||||
return _create_project_data
|
||||
|
||||
@pytest.fixture
|
||||
def session_factory(fake):
|
||||
"""Factory for creating session test data."""
|
||||
def _create_session_data(**overrides):
|
||||
data = {
|
||||
"session_type": fake.random_element(elements=["startup", "resume", "clear"]),
|
||||
"working_directory": fake.file_path(depth=3),
|
||||
"git_branch": fake.word(),
|
||||
"environment": {
|
||||
"user": fake.user_name(),
|
||||
"pwd": fake.file_path(depth=3),
|
||||
"timestamp": fake.iso8601()
|
||||
}
|
||||
}
|
||||
data.update(overrides)
|
||||
return data
|
||||
return _create_session_data
|
||||
|
||||
@pytest.fixture
|
||||
def conversation_factory(fake):
|
||||
"""Factory for creating conversation test data."""
|
||||
def _create_conversation_data(**overrides):
|
||||
data = {
|
||||
"user_prompt": fake.sentence(nb_words=10),
|
||||
"claude_response": fake.paragraph(nb_sentences=3),
|
||||
"tools_used": fake.random_elements(
|
||||
elements=["Edit", "Write", "Read", "Bash", "Grep"],
|
||||
length=fake.random_int(min=1, max=3),
|
||||
unique=True
|
||||
),
|
||||
"files_affected": [fake.file_path() for _ in range(fake.random_int(min=0, max=3))],
|
||||
"exchange_type": fake.random_element(elements=["user_prompt", "claude_response"])
|
||||
}
|
||||
data.update(overrides)
|
||||
return data
|
||||
return _create_conversation_data
|
||||
|
||||
# Utility functions for tests
|
||||
@pytest.fixture
|
||||
def assert_response():
|
||||
"""Helper for asserting API response structure."""
|
||||
def _assert_response(response, status_code=200, required_keys=None):
|
||||
assert response.status_code == status_code
|
||||
if required_keys:
|
||||
data = response.json()
|
||||
for key in required_keys:
|
||||
assert key in data
|
||||
return response.json()
|
||||
return _assert_response
|
||||
|
||||
@pytest.fixture
|
||||
def create_test_data():
|
||||
"""Helper for creating test data in database."""
|
||||
async def _create_test_data(db: AsyncSession, model_class, count=1, **kwargs):
|
||||
items = []
|
||||
for i in range(count):
|
||||
item = model_class(**kwargs)
|
||||
db.add(item)
|
||||
items.append(item)
|
||||
await db.commit()
|
||||
for item in items:
|
||||
await db.refresh(item)
|
||||
return items[0] if count == 1 else items
|
||||
return _create_test_data
|
||||
Loading…
Add table
Add a link
Reference in a new issue