feat: complete CLI, Docker & deployment docs (Step 10.3)

- Add comprehensive CLI tests for seed and serve commands
- Create README.md with development setup, deployment guide,
  and environment variable reference
- Mark Step 10.3 as complete in PLAN.md

This completes the final implementation step.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 18:21:14 +00:00
parent 49871f60c5
commit c6a87e35d4
4 changed files with 578 additions and 6 deletions

241
tests/test_cli_seed.py Normal file
View File

@@ -0,0 +1,241 @@
# ABOUTME: End-to-end tests for seed CLI command.
# ABOUTME: Tests seeding reference data via subprocess.
import os
import subprocess
import sys
from pathlib import Path
from animaltrack.db import get_db
from animaltrack.repositories import (
FeedTypeRepository,
LocationRepository,
ProductRepository,
SpeciesRepository,
UserRepository,
)
# Get the project root directory (parent of tests/)
PROJECT_ROOT = Path(__file__).parent.parent
class TestSeedCLI:
"""End-to-end tests for seed command."""
def test_seed_command_success(self, tmp_path):
"""Should seed data via CLI and exit 0."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
assert "seed data loaded" in result.stdout.lower()
def test_seed_creates_users(self, tmp_path):
"""Should create expected users."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
# Verify users were created
db = get_db(str(db_path))
repo = UserRepository(db)
users = repo.list_all()
usernames = {u.username for u in users}
assert "ppetru" in usernames
assert "ines" in usernames
assert "guest" in usernames
def test_seed_creates_locations(self, tmp_path):
"""Should create expected locations."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
# Verify locations were created
db = get_db(str(db_path))
repo = LocationRepository(db)
locations = repo.list_all()
names = {loc.name for loc in locations}
assert "Strip 1" in names
assert "Strip 4" in names
assert "Nursery 1" in names
assert "Nursery 4" in names
assert len(locations) == 8
def test_seed_creates_species(self, tmp_path):
"""Should create expected species."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
# Verify species were created
db = get_db(str(db_path))
repo = SpeciesRepository(db)
species_list = repo.list_all()
codes = {s.code for s in species_list}
assert "duck" in codes
assert "goose" in codes
assert "sheep" in codes
def test_seed_creates_products(self, tmp_path):
"""Should create expected products."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
# Verify products were created
db = get_db(str(db_path))
repo = ProductRepository(db)
products = repo.list_all()
codes = {p.code for p in products}
assert "egg.duck" in codes
assert "meat" in codes
assert "offal" in codes
assert "fat" in codes
assert "bones" in codes
assert "feathers" in codes
assert "down" in codes
def test_seed_creates_feed_types(self, tmp_path):
"""Should create expected feed types."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
result = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result.returncode == 0, f"stdout: {result.stdout}, stderr: {result.stderr}"
# Verify feed types were created
db = get_db(str(db_path))
repo = FeedTypeRepository(db)
feed_types = repo.list_all()
codes = {f.code for f in feed_types}
assert "starter" in codes
assert "grower" in codes
assert "layer" in codes
def test_seed_is_idempotent(self, tmp_path):
"""Running seed twice should produce same result."""
db_path = tmp_path / "test.db"
env = os.environ.copy()
env["DB_PATH"] = str(db_path)
env["CSRF_SECRET"] = "test-secret-for-csrf"
env["PYTHONPATH"] = str(PROJECT_ROOT / "src")
# Run seed first time
result1 = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result1.returncode == 0, f"stdout: {result1.stdout}, stderr: {result1.stderr}"
# Run seed second time
result2 = subprocess.run(
[sys.executable, "-m", "animaltrack.cli", "seed"],
capture_output=True,
text=True,
env=env,
cwd=str(PROJECT_ROOT),
)
assert result2.returncode == 0, f"stdout: {result2.stdout}, stderr: {result2.stderr}"
# Verify counts haven't doubled
db = get_db(str(db_path))
user_repo = UserRepository(db)
assert len(user_repo.list_all()) == 3
location_repo = LocationRepository(db)
assert len(location_repo.list_all()) == 8
species_repo = SpeciesRepository(db)
assert len(species_repo.list_all()) == 3
product_repo = ProductRepository(db)
assert len(product_repo.list_all()) == 7
feed_repo = FeedTypeRepository(db)
assert len(feed_repo.list_all()) == 3