This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Ashfolio is a Phoenix LiveView portfolio management application built with the Ash Framework. It's designed for single-user local deployment with manual price updates via Yahoo Finance integration. The application uses SQLite for data persistence and ETS for price caching.
This project uses just as the task runner. Key commands:
just dev- Setup and start development server (installs deps, migrates DB, starts Phoenix)just setup- Install dependencies and setup database onlyjust server- Start Phoenix server (assumes setup already done)just stop- Stop Phoenix server
just test- Run main test suite (excludes slow seeding tests)just test-all- Run full test suite including seeding testsjust test-file <path>- Run specific test filejust test-verbose- Run tests with detailed output and tracingjust test-coverage- Run tests with coverage reportjust test-watch- Run tests in watch modejust test-failed- Re-run only failed tests
just test-ash- Ash Resource business logic tests (User, Account, Symbol, Transaction)just test-liveview- Phoenix LiveView UI component and interaction testsjust test-calculations- Portfolio calculation and FIFO cost basis testsjust test-market-data- Price fetching, Yahoo Finance, and caching testsjust test-integration- End-to-end workflow and system integration testsjust test-ui- User interface, accessibility, and responsive design tests
just test-fast- Quick tests for development feedback loop (< 100ms)just test-unit- Isolated unit tests with minimal dependenciesjust test-slow- Slower comprehensive tests requiring more setupjust test-external- Tests requiring external APIs (Yahoo Finance, etc.)just test-mocked- Tests using Mox for external service mocking
just test-smoke- Essential tests that must always passjust test-regression- Tests covering previously fixed bugsjust test-edge-cases- Boundary condition and unusual scenario testsjust test-error-handling- Error condition and fault tolerance tests
All test commands support -verbose variants for detailed output (e.g., just test-fast-verbose)
We've implemented comprehensive safeguards after experiencing mass test failures (253 → 0). These tools prevent database issues and provide instant recovery.
# ALWAYS start with health check when debugging tests
just test-health-check # ← START HERE for any test issues
# Run tests with automatic safeguards
just test-safe # ← Use this instead of regular 'just test'
# Emergency recovery (fixes 99% of test database issues)
just test-db-emergency-reset # ← Nuclear option that actually worksDuring development, we experienced a catastrophic test failure where 253 tests suddenly failed due to test database contamination. The root cause was stale data from previous test runs that corrupted the baseline state.
These safeguards prevent this from happening again and provide instant recovery when it does.
- Test database (
data/ashfolio_test.db) is completely separate from development database (data/ashfolio_dev.db) - Uses a global setup pattern where baseline test data is created once in
test_helper.exs - All tests expect a default user, default account, and common symbols to exist
For Test Database Issues (Stale Data, Failures):
# Complete test database reset (recommended approach)
MIX_ENV=test mix ecto.drop && MIX_ENV=test mix ecto.create && MIX_ENV=test mix ecto.migrate
# Restore required global test data
MIX_ENV=test mix run -e "Ashfolio.SQLiteHelpers.setup_global_test_data!()"
# Verify tests are working
just test-fastAlternative Reset (includes sample transactions):
# This creates more data than tests typically need
MIX_ENV=test mix ecto.resetError: "Default user not found"
- Global test data missing from database
- Run
MIX_ENV=test mix run -e "Ashfolio.SQLiteHelpers.setup_global_test_data!()"
Error: Expected X accounts, got Y accounts
- Stale test data contamination from previous runs
- Complete test database reset (see procedures above)
Error: "Database busy" or "database is locked"
- SQLite concurrency issues, test processes accessing database simultaneously
- Tests use
async: falseand retry logic, but reset database if persistent
Mass Test Failures (200+ failures)
- Usually test database contamination or missing global test data
- Complete test database reset procedure above
- Each test creates its own specific data in setup blocks
- Common data (default user, default account, common symbols) created once
- Uses Ecto SQL Sandbox for test isolation
- All tests run with
async: falsedue to SQLite limitations
just reset- Reset database with fresh sample data (full reset)just reseed- Truncate and re-seed (preserves schema)just migrate- Run pending migrationsjust backup- Create timestamped database backupjust db-status- Show table counts and database status
just format- Format Elixir codejust compile- Compile projectjust check- Run format + compile + test
just console- Start IEx consolejust console-web- Start IEx console with Phoenix server
- Elixir 1.14+, Phoenix 1.7+, Ash Framework 3.0+
- SQLite with AshSqlite adapter
- Phoenix LiveView with Tailwind CSS
- Yahoo Finance API via HTTPoison
- ETS for price data
- ExUnit with Mox for mocking
Ashfolio.Portfolio.User- User managementAshfolio.Portfolio.Account- Investment accountsAshfolio.Portfolio.Symbol- Stock/asset symbols with price dataAshfolio.Portfolio.Transaction- All transaction types (BUY, SELL, DIVIDEND, etc.)
AshfolioWeb.DashboardLive- Main portfolio dashboardAshfolioWeb.AccountLive.Index- Account managementAshfolioWeb.TransactionLive.Index- Transaction management
Ashfolio.MarketData.PriceManager- GenServer for price fetching and cachingAshfolio.MarketData.YahooFinance- Yahoo Finance API integration- ETS cache for price data persistence
Ashfolio.Portfolio.Calculator- Main portfolio calculationsAshfolio.Portfolio.HoldingsCalculator- Individual holdings calculations- Both use FIFO cost basis methodology
- Standard CRUD operations
- Price fetching and caching
- Read from Ash Resources, perform calculations, return results
- Main Calculator orchestrates, HoldingsCalculator handles per-symbol logic
When encountering test failures, follow this systematic approach:
# Get overall test health
just test-fast
# Check specific test
just test-file path/to/failing_test.exs- Likely code logic or test-specific issue
- Usually infrastructure issue (database, setup, dependencies)
- Focus on that specific assertion or setup
# Verify test database exists and has proper setup
MIX_ENV=test mix run -e "
{:ok, accounts} = Ashfolio.Portfolio.Account.list()
IO.puts(\"Accounts: #{length(accounts)}\")
{:ok, users} = Ashfolio.Portfolio.User.list()
IO.puts(\"Users: #{length(users)}\")
"- "Default user not found" → Missing global test data
- "Expected X, got Y" → Stale data contamination
- "Database busy/locked" → SQLite concurrency issues
- Protocol/Module errors → Code syntax or compilation issues
- Use test database reset procedures (see Test Database Management section)
- Focus on the failing assertion and surrounding code
- Check test setup blocks and global test data
# Test the specific failing test
just test-file path/to/test.exs
# Run broader test suite to ensure no regressions
just test-fast
# Full verification if needed
just testIntegration test account_management_flow_test.exs failing with account count mismatch
Expected 5 accounts, got 4 - account creation appeared to fail Stale test database data from previous runs contaminated the baseline Complete test database reset + global test data restoration Test passed consistently, 253 → 0 test failures across entire suite
- Integration tests are more sensitive to database state than unit tests
- Always verify test database baseline when debugging integration tests
- Mass test failures often indicate infrastructure issues, not code issues
# === ASSESSMENT & HEALTH CHECKS ===
just test-health-check # 🛡️ Check test database health FIRST
just test-safe # Run tests with automatic health check
just test-fast # Quick health check (372 tests)
just test-file path/to/test.exs # Run specific test file
# === TEST DATABASE SAFEGUARDS (NEW!) ===
just test-db-validate # Validate test database state
just test-db-reset # Safe test database reset
just test-db-emergency-reset # Emergency recovery (for mass failures)
# === TEST DATABASE RESET (Most Common Fix) ===
MIX_ENV=test mix ecto.drop && MIX_ENV=test mix ecto.create && MIX_ENV=test mix ecto.migrate
MIX_ENV=test mix run -e "Ashfolio.SQLiteHelpers.setup_global_test_data!()"
just test-fast # Verify fix
# === DEBUGGING SPECIFIC TESTS ===
just test-file-verbose path/to/test.exs # Run with detailed output
just test-file path/to/test.exs --trace # Run with execution trace
# === DATABASE STATE INSPECTION ===
MIX_ENV=test mix run -e "
user_count = Ashfolio.Repo.aggregate(Ashfolio.Portfolio.User, :count)
account_count = Ashfolio.Repo.aggregate(Ashfolio.Portfolio.Account, :count)
symbol_count = Ashfolio.Repo.aggregate(Ashfolio.Portfolio.Symbol, :count)
IO.puts(\"Users: #{user_count}, Accounts: #{account_count}, Symbols: #{symbol_count}\")
"
# === DEVELOPMENT DATABASE (SEPARATE) ===
just reset # Reset development database
just reseed # Re-seed development databaseAfter experiencing mass test failures (253 → 0), we've implemented several safeguards:
Built into just test-safe
- Database connectivity validation
- Baseline data verification (users, accounts, symbols)
- Clear error messages with fix instructions
just test-db-emergency-reset
- Complete test database rebuild procedure
- Implements the exact fix that resolved our 253-failure crisis
- Includes confirmation prompts for safety
just test-db-validate
- Validates global test data integrity
- Checks for expected baseline records
- Provides detailed validation output
- Tests organized by architectural layers using ExUnit filters
- Separate fast/slow test categories for optimal development workflow
- Clear separation of tests requiring external services, mocks, or GenServers
- Specialized test suites for smoke tests, regression testing, and error handling
:ash_resources,:liveview,:market_data,:calculations,:ui,:pubsub:fast,:slow,:unit,:integration:external_deps,:genserver,:ets_cache,:mocked:smoke,:regression,:edge_cases,:error_handling
just test-fastfor quick feedback (< 100ms tests)just test-ash,just test-liveview,just test-calculationsfor focused developmentjust test-integrationfor end-to-end workflow validationjust test-allincludes all categories including slow seeding tests
- All tests use
async: falsefor SQLite compatibility - Pre-created default user, accounts, and symbols reduce database contention
- Built-in retry patterns for handling SQLite "Database busy" errors
- Proper database isolation using
DataCase.setup_sandbox/1 - Special handling for PriceManager and other GenServer database access
- Complete documentation in
docs/covering all testing patterns - Specialized guides and templates for AI-assisted development
- Step-by-step guides for adopting modular testing patterns
- SQLite-specific patterns and performance optimization techniques
- Development uses seeded sample data for immediate productivity
- Database backups are timestamped and stored in
data/backups/ - Migration files follow Phoenix conventions
- Use
just resetfrequently during development for clean state
- Ash Resources define all business logic and validations
- LiveViews handle UI state and user interactions only
- Separate calculator modules for complex financial calculations
- Market data system is isolated and mockable for testing
- Use LiveComponents for reusable UI elements (FormComponent)
- Handle async operations with
handle_info/2 - Leverage Phoenix PubSub for real-time updates when needed
- Keep LiveView mount/handle_event functions focused and delegate to Ash Resources
- Create resource module in
lib/ashfolio/portfolio/ - Define attributes, relationships, and actions
- Create migration with
mix ash_sqlite.generate_migrations - Update seeds if needed
- Add comprehensive tests
- Create LiveView module in
lib/ashfolio_web/live/ - Add route to
router.ex - Create corresponding template if not using
~Hsigil - Add navigation links to
top_bar.exif needed - Test both unit and integration aspects
- PriceManager is a GenServer - be careful with state management
- Always mock YahooFinance in tests using the behaviour
- ETS cache is process-based - consider supervision tree implications
- Price updates are manual only (no automatic refresh)
- Both Calculator modules must stay in sync for accurate results
- FIFO cost basis is the standard methodology
- All monetary values use Decimal type for precision
- Transaction types: BUY, SELL, DIVIDEND, FEE, INTEREST, LIABILITY