Files
markitect-main/tests/test_cost_cli_commands.py
tegwick dab6b9fdef
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat: implement cost report template generator with Claude session tracking (issue #119)
Comprehensive cost tracking system implementation including:

- Cost report generator with multiple formats (summary, detailed, audit)
- Full CLI integration with cost management commands
- Claude session cost tracking and estimation
- Professional markdown reports with frontmatter/contentmatter
- Automatic cost note generation for issue implementations
- Complete test coverage (33 test cases)
- Database integration with finance schema initialization

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 01:31:36 +02:00

393 lines
14 KiB
Python

"""
Tests for MarkiTect cost tracking CLI commands.
This module tests the command-line interface for cost management including:
- Cost report generation commands
- Cost item management commands
- Category management commands
- Period cost calculations
"""
import pytest
import tempfile
import os
import json
from datetime import date
from decimal import Decimal
from click.testing import CliRunner
from markitect.finance.cli import cost_commands
from markitect.finance.cost_manager import CostItemManager, CostItem
from markitect.finance.models import FinanceModels
class TestCostCLICommands:
"""Test suite for cost tracking CLI commands."""
@pytest.fixture
def temp_db(self):
"""Create temporary database for testing."""
fd, path = tempfile.mkstemp(suffix='.db')
os.close(fd)
yield path
os.unlink(path)
@pytest.fixture
def setup_test_data(self, temp_db):
"""Setup test database with sample cost data."""
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
cost_manager = CostItemManager(temp_db)
# Get categories
infra_cat = cost_manager.get_category_by_name('Infrastructure')
software_cat = cost_manager.get_category_by_name('Software')
# Create sample cost items
cost_items = [
CostItem(
category_id=infra_cat['id'],
name='Test Server',
cost_type='monthly',
amount_eur=Decimal('25.00'),
starting_from_date=date(2025, 1, 1)
),
CostItem(
category_id=software_cat['id'],
name='Test Software',
cost_type='one_time',
amount_eur=Decimal('50.00'),
starting_from_date=date(2025, 1, 15)
)
]
for item in cost_items:
cost_manager.create_cost_item(item)
return temp_db
@pytest.fixture
def runner(self):
"""Create Click test runner."""
return CliRunner()
def test_cost_report_generate_summary(self, runner, setup_test_data):
"""Test cost report generate command with summary format."""
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', '2025-01',
'--format', 'summary',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Cost Summary Report - January 2025" in result.output
assert "€75.00" in result.output # 25 + 50
assert "frontmatter" not in result.output.lower() # Should be properly formatted
def test_cost_report_generate_detailed(self, runner, setup_test_data):
"""Test cost report generate command with detailed format."""
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', '2025-01',
'--format', 'detailed',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Detailed Cost Report - January 2025" in result.output
assert "Infrastructure" in result.output
assert "Software" in result.output
assert "Test Server" in result.output
assert "Test Software" in result.output
def test_cost_report_generate_audit(self, runner, setup_test_data):
"""Test cost report generate command with audit format."""
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', '2025-01',
'--format', 'audit',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Cost Audit Report - January 2025" in result.output
assert "Audit Summary" in result.output
assert "Transaction History" in result.output
def test_cost_report_generate_with_output_file(self, runner, setup_test_data):
"""Test saving report to output file."""
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.md') as f:
output_path = f.name
try:
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', '2025-01',
'--output', output_path,
'--database', setup_test_data
])
assert result.exit_code == 0
assert f"Report saved to: {output_path}" in result.output
# Verify file was created
assert os.path.exists(output_path)
with open(output_path, 'r') as f:
content = f.read()
assert "Cost Summary Report" in content
finally:
if os.path.exists(output_path):
os.unlink(output_path)
def test_cost_report_generate_invalid_period(self, runner, setup_test_data):
"""Test report generation with invalid period format."""
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', 'invalid-period',
'--database', setup_test_data
])
assert result.exit_code == 1
assert "Period must be in YYYY-MM format" in result.output
def test_cost_report_generate_default_database(self, runner):
"""Test report generation with default database path from config."""
result = runner.invoke(cost_commands, [
'report', 'generate',
'--period', '2025-01'
])
# Should succeed with default config and empty database
assert result.exit_code == 0
assert "Cost Summary Report - January 2025" in result.output
assert "€0.00" in result.output # Empty database shows zero costs
def test_cost_report_template_show(self, runner):
"""Test cost report template show command."""
result = runner.invoke(cost_commands, [
'report', 'template', '--show'
])
assert result.exit_code == 0
assert "Summary Report Template" in result.output
assert "Description" in result.output
assert "Frontmatter Fields" in result.output
def test_cost_report_template_different_formats(self, runner):
"""Test template show for different formats."""
formats = ['summary', 'detailed', 'audit']
for format_type in formats:
result = runner.invoke(cost_commands, [
'report', 'template', '--show', '--format', format_type
])
assert result.exit_code == 0
assert f"{format_type.title()} Report Template" in result.output
def test_cost_item_add(self, runner, temp_db):
"""Test adding new cost item via CLI."""
# Initialize database
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
result = runner.invoke(cost_commands, [
'item', 'add', 'Test Item',
'--category', 'Infrastructure',
'--amount', '15.50',
'--type', 'monthly',
'--database', temp_db
])
assert result.exit_code == 0
assert "✅ Created cost item 'Test Item'" in result.output
# Verify item was created
cost_manager = CostItemManager(temp_db)
items = cost_manager.list_cost_items()
assert len(items) == 1
assert items[0]['name'] == 'Test Item'
assert float(items[0]['amount_eur']) == 15.50
def test_cost_item_add_with_description_and_date(self, runner, temp_db):
"""Test adding cost item with description and start date."""
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
result = runner.invoke(cost_commands, [
'item', 'add', 'Test Item',
'--category', 'Software',
'--amount', '99.99',
'--type', 'one_time',
'--description', 'Test description',
'--start-date', '2025-01-15',
'--database', temp_db
])
assert result.exit_code == 0
assert "✅ Created cost item 'Test Item'" in result.output
def test_cost_item_add_invalid_category(self, runner, temp_db):
"""Test adding item with non-existent category."""
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
result = runner.invoke(cost_commands, [
'item', 'add', 'Test Item',
'--category', 'NonExistent',
'--amount', '10.00',
'--type', 'monthly',
'--database', temp_db
])
assert result.exit_code == 1
assert "Category 'NonExistent' not found" in result.output
assert "Available categories:" in result.output
def test_cost_item_list(self, runner, setup_test_data):
"""Test listing cost items."""
result = runner.invoke(cost_commands, [
'item', 'list',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Test Server" in result.output
assert "Test Software" in result.output
assert "€25.00" in result.output
assert "€50.00" in result.output
def test_cost_item_list_with_filters(self, runner, setup_test_data):
"""Test listing cost items with filters."""
# Filter by category
result = runner.invoke(cost_commands, [
'item', 'list',
'--category', 'Infrastructure',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Test Server" in result.output
assert "Test Software" not in result.output
# Filter by type
result = runner.invoke(cost_commands, [
'item', 'list',
'--type', 'monthly',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Test Server" in result.output
assert "Test Software" not in result.output
def test_cost_category_list(self, runner, setup_test_data):
"""Test listing cost categories."""
result = runner.invoke(cost_commands, [
'category', 'list',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Infrastructure" in result.output
assert "Software" in result.output
assert "Total: 8 categories" in result.output # Default categories
def test_cost_category_add(self, runner, temp_db):
"""Test adding new cost category."""
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
result = runner.invoke(cost_commands, [
'category', 'add', 'Custom Category',
'--description', 'Custom test category',
'--database', temp_db
])
assert result.exit_code == 0
assert "✅ Created category 'Custom Category'" in result.output
# Verify category was created
cost_manager = CostItemManager(temp_db)
categories = cost_manager.list_categories()
category_names = [cat['name'] for cat in categories]
assert 'Custom Category' in category_names
def test_cost_calculate(self, runner, setup_test_data):
"""Test cost calculation command."""
result = runner.invoke(cost_commands, [
'calculate',
'--period', '2025-01',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Cost Calculation - January 2025" in result.output
assert "Monthly Recurring: €25.00" in result.output
assert "One-time Expenses: €50.00" in result.output
assert "Total Period Cost: €75.00" in result.output
assert "Active Cost Items: 2" in result.output
def test_cost_calculate_current_month(self, runner, setup_test_data):
"""Test cost calculation for current month (default)."""
result = runner.invoke(cost_commands, [
'calculate',
'--database', setup_test_data
])
assert result.exit_code == 0
assert "Cost Calculation" in result.output
# Should default to current month
def test_cost_calculate_invalid_period(self, runner, setup_test_data):
"""Test cost calculation with invalid period."""
result = runner.invoke(cost_commands, [
'calculate',
'--period', 'invalid',
'--database', setup_test_data
])
assert result.exit_code == 1
assert "Period must be in YYYY-MM format" in result.output
def test_cost_item_add_invalid_date_format(self, runner, temp_db):
"""Test adding item with invalid date format."""
finance_models = FinanceModels(temp_db)
finance_models.initialize_finance_schema()
result = runner.invoke(cost_commands, [
'item', 'add', 'Test Item',
'--category', 'Infrastructure',
'--amount', '10.00',
'--type', 'monthly',
'--start-date', 'invalid-date',
'--database', temp_db
])
assert result.exit_code == 1
assert "Start date must be in YYYY-MM-DD format" in result.output
def test_help_commands(self, runner):
"""Test help output for cost commands."""
# Test main cost help
result = runner.invoke(cost_commands, ['--help'])
assert result.exit_code == 0
assert "Cost tracking and financial reporting commands" in result.output
# Test report help
result = runner.invoke(cost_commands, ['report', '--help'])
assert result.exit_code == 0
assert "Generate cost reports" in result.output
# Test item help
result = runner.invoke(cost_commands, ['item', '--help'])
assert result.exit_code == 0
assert "Manage cost items" in result.output
# Test category help
result = runner.invoke(cost_commands, ['category', '--help'])
assert result.exit_code == 0
assert "Manage cost categories" in result.output