- Add comprehensive IssueActivityTracker service with ActivityType enum and IssueActivity dataclass - Implement full CLI interface with log, show, list, summary, delete, and import-activities commands - Support activity logging with automatic period detection and cost allocation integration - Add activity retrieval by issue, by period, with filtering and pagination - Include activity summaries with statistics and breakdowns across issues and time periods - Support bulk operations for activity import from JSON/CSV formats - Integrate with existing finance schema using cost_periods and issue_activity_log tables - Add 28 comprehensive test cases covering all functionality with 100% pass rate - Enable both table and JSON output formats for all CLI commands 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
627 lines
24 KiB
Python
627 lines
24 KiB
Python
"""
|
|
Tests for Issue #113 - Issue Activity Tracking Implementation
|
|
|
|
This module contains comprehensive tests for the issue activity tracking
|
|
service and CLI commands that log, retrieve, and manage issue activities
|
|
for cost allocation and project management.
|
|
"""
|
|
|
|
import pytest
|
|
import sqlite3
|
|
from datetime import datetime, date
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
from pathlib import Path
|
|
import tempfile
|
|
import json
|
|
import csv
|
|
import io
|
|
from contextlib import redirect_stdout
|
|
|
|
from markitect.issues.activity_tracker import IssueActivityTracker, ActivityType, IssueActivity
|
|
from markitect.issues.activity_commands import activity
|
|
|
|
|
|
class TestActivityType:
|
|
"""Test suite for ActivityType enumeration."""
|
|
|
|
def test_activity_type_values(self):
|
|
"""Test that all expected activity types are available."""
|
|
expected_types = {
|
|
"created", "modified", "closed", "reopened", "commented", "status_changed"
|
|
}
|
|
actual_types = {at.value for at in ActivityType}
|
|
assert actual_types == expected_types
|
|
|
|
def test_activity_type_enumeration(self):
|
|
"""Test that ActivityType can be constructed from string values."""
|
|
assert ActivityType("created") == ActivityType.CREATED
|
|
assert ActivityType("modified") == ActivityType.MODIFIED
|
|
assert ActivityType("closed") == ActivityType.CLOSED
|
|
|
|
|
|
class TestIssueActivity:
|
|
"""Test suite for IssueActivity dataclass."""
|
|
|
|
def test_issue_activity_creation(self):
|
|
"""Test that IssueActivity objects can be created properly."""
|
|
activity = IssueActivity(
|
|
id=1,
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
activity_date=date.today(),
|
|
activity_details="Issue created"
|
|
)
|
|
|
|
assert activity.id == 1
|
|
assert activity.issue_id == 59
|
|
assert activity.activity_type == ActivityType.CREATED
|
|
assert activity.activity_date == date.today()
|
|
assert activity.activity_details == "Issue created"
|
|
|
|
def test_issue_activity_defaults(self):
|
|
"""Test that IssueActivity has proper default values."""
|
|
activity = IssueActivity()
|
|
|
|
assert activity.id is None
|
|
assert activity.issue_id is None
|
|
assert activity.activity_type is None
|
|
assert activity.activity_date is None
|
|
assert activity.period_id is None
|
|
assert activity.activity_details is None
|
|
assert activity.created_at is None
|
|
|
|
|
|
class TestIssueActivityTracker:
|
|
"""Test suite for IssueActivityTracker service."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures with temporary database."""
|
|
self.temp_db = tempfile.NamedTemporaryFile(suffix='.db', delete=False)
|
|
self.temp_db.close()
|
|
self.db_path = self.temp_db.name
|
|
self.tracker = IssueActivityTracker(self.db_path)
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test fixtures."""
|
|
Path(self.db_path).unlink(missing_ok=True)
|
|
|
|
def test_tracker_initialization(self):
|
|
"""Test that tracker initializes properly with database."""
|
|
assert self.tracker.db_path == self.db_path
|
|
assert self.tracker.finance_models is not None
|
|
|
|
# Verify database schema was created
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='issue_activity_log'")
|
|
assert cursor.fetchone() is not None
|
|
|
|
def test_log_activity_basic(self):
|
|
"""Test logging a basic activity."""
|
|
activity_id = self.tracker.log_activity(
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
activity_details="Test issue created"
|
|
)
|
|
|
|
assert activity_id is not None
|
|
|
|
# Verify activity was stored
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT * FROM issue_activity_log WHERE id = ?", (activity_id,))
|
|
row = cursor.fetchone()
|
|
|
|
assert row is not None
|
|
assert row[1] == 59 # issue_id
|
|
assert row[2] == "created" # activity_type
|
|
assert row[5] == "Test issue created" # activity_details
|
|
|
|
def test_log_activity_with_custom_date(self):
|
|
"""Test logging activity with custom date."""
|
|
custom_date = date(2025, 10, 1)
|
|
|
|
activity_id = self.tracker.log_activity(
|
|
issue_id=59,
|
|
activity_type=ActivityType.MODIFIED,
|
|
activity_date=custom_date
|
|
)
|
|
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT activity_date FROM issue_activity_log WHERE id = ?", (activity_id,))
|
|
stored_date = cursor.fetchone()[0]
|
|
|
|
assert stored_date == "2025-10-01"
|
|
|
|
def test_log_activity_with_period_id(self):
|
|
"""Test logging activity with specific period ID."""
|
|
# First create a cost period
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
INSERT INTO cost_periods (period_start, period_end, total_costs)
|
|
VALUES ('2025-10-01', '2025-10-31', 1000.00)
|
|
""")
|
|
period_id = cursor.lastrowid
|
|
|
|
activity_id = self.tracker.log_activity(
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
period_id=period_id
|
|
)
|
|
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT period_id FROM issue_activity_log WHERE id = ?", (activity_id,))
|
|
stored_period_id = cursor.fetchone()[0]
|
|
|
|
assert stored_period_id == period_id
|
|
|
|
def test_get_issue_activities(self):
|
|
"""Test retrieving activities for a specific issue."""
|
|
# Log multiple activities
|
|
self.tracker.log_activity(59, ActivityType.CREATED, activity_details="Created")
|
|
self.tracker.log_activity(59, ActivityType.MODIFIED, activity_details="Modified")
|
|
self.tracker.log_activity(60, ActivityType.CREATED, activity_details="Different issue")
|
|
|
|
activities = self.tracker.get_issue_activities(59)
|
|
|
|
assert len(activities) == 2
|
|
assert all(a.issue_id == 59 for a in activities)
|
|
assert activities[0].activity_type in [ActivityType.CREATED, ActivityType.MODIFIED]
|
|
|
|
def test_get_issue_activities_with_limit(self):
|
|
"""Test retrieving activities with limit and offset."""
|
|
# Log multiple activities
|
|
for i in range(5):
|
|
self.tracker.log_activity(59, ActivityType.MODIFIED, activity_details=f"Update {i}")
|
|
|
|
activities = self.tracker.get_issue_activities(59, limit=2, offset=1)
|
|
|
|
assert len(activities) == 2
|
|
|
|
def test_get_activities_by_period(self):
|
|
"""Test retrieving activities by cost period."""
|
|
# Create a cost period
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
INSERT INTO cost_periods (period_start, period_end, total_costs)
|
|
VALUES ('2025-10-01', '2025-10-31', 1000.00)
|
|
""")
|
|
period_id = cursor.lastrowid
|
|
|
|
# Log activities in different periods
|
|
self.tracker.log_activity(59, ActivityType.CREATED, period_id=period_id)
|
|
self.tracker.log_activity(60, ActivityType.MODIFIED, period_id=period_id)
|
|
# Log activity outside the period date range
|
|
self.tracker.log_activity(61, ActivityType.CLOSED, activity_date=date(2025, 11, 1))
|
|
|
|
activities = self.tracker.get_activities_by_period(period_id)
|
|
|
|
assert len(activities) == 2
|
|
assert all(a.period_id == period_id for a in activities)
|
|
|
|
def test_get_activities_by_period_with_type_filter(self):
|
|
"""Test retrieving activities by period with type filtering."""
|
|
# Create period
|
|
with self.tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
INSERT INTO cost_periods (period_start, period_end, total_costs)
|
|
VALUES ('2025-10-01', '2025-10-31', 1000.00)
|
|
""")
|
|
period_id = cursor.lastrowid
|
|
|
|
# Log various activities
|
|
self.tracker.log_activity(59, ActivityType.CREATED, period_id=period_id)
|
|
self.tracker.log_activity(60, ActivityType.MODIFIED, period_id=period_id)
|
|
self.tracker.log_activity(61, ActivityType.CLOSED, period_id=period_id)
|
|
|
|
activities = self.tracker.get_activities_by_period(
|
|
period_id,
|
|
activity_types=[ActivityType.CREATED, ActivityType.CLOSED]
|
|
)
|
|
|
|
assert len(activities) == 2
|
|
assert all(a.activity_type in [ActivityType.CREATED, ActivityType.CLOSED] for a in activities)
|
|
|
|
def test_get_activity_summary_basic(self):
|
|
"""Test basic activity summary generation."""
|
|
# Log some test activities
|
|
self.tracker.log_activity(59, ActivityType.CREATED)
|
|
self.tracker.log_activity(59, ActivityType.MODIFIED)
|
|
self.tracker.log_activity(60, ActivityType.CREATED)
|
|
|
|
summary = self.tracker.get_activity_summary()
|
|
|
|
assert summary['total_activities'] == 3
|
|
assert summary['unique_issues'] == 2
|
|
assert 'created' in summary['activities_by_type']
|
|
assert 'modified' in summary['activities_by_type']
|
|
assert summary['activities_by_type']['created'] == 2
|
|
assert summary['activities_by_type']['modified'] == 1
|
|
|
|
def test_get_activity_summary_with_filters(self):
|
|
"""Test activity summary with date and issue filters."""
|
|
today = date.today()
|
|
yesterday = date(today.year, today.month, today.day - 1) if today.day > 1 else date(today.year, today.month - 1, 28)
|
|
|
|
# Log activities on different dates
|
|
self.tracker.log_activity(59, ActivityType.CREATED, activity_date=yesterday)
|
|
self.tracker.log_activity(59, ActivityType.MODIFIED, activity_date=today)
|
|
self.tracker.log_activity(60, ActivityType.CREATED, activity_date=today)
|
|
|
|
# Test issue filter
|
|
summary = self.tracker.get_activity_summary(issue_id=59)
|
|
assert summary['total_activities'] == 2
|
|
assert summary['unique_issues'] == 1
|
|
|
|
# Test date filter
|
|
summary = self.tracker.get_activity_summary(start_date=today)
|
|
assert summary['total_activities'] == 2
|
|
|
|
def test_delete_activity(self):
|
|
"""Test deleting an activity record."""
|
|
activity_id = self.tracker.log_activity(59, ActivityType.CREATED)
|
|
|
|
# Verify activity exists
|
|
activities = self.tracker.get_issue_activities(59)
|
|
assert len(activities) == 1
|
|
|
|
# Delete activity
|
|
result = self.tracker.delete_activity(activity_id)
|
|
assert result is True
|
|
|
|
# Verify activity is gone
|
|
activities = self.tracker.get_issue_activities(59)
|
|
assert len(activities) == 0
|
|
|
|
def test_delete_nonexistent_activity(self):
|
|
"""Test deleting non-existent activity returns False."""
|
|
result = self.tracker.delete_activity(99999)
|
|
assert result is False
|
|
|
|
def test_bulk_log_activities(self):
|
|
"""Test logging multiple activities in one transaction."""
|
|
activities_data = [
|
|
{
|
|
'issue_id': 59,
|
|
'activity_type': 'created',
|
|
'activity_details': 'Bulk created'
|
|
},
|
|
{
|
|
'issue_id': 60,
|
|
'activity_type': 'modified',
|
|
'activity_details': 'Bulk modified'
|
|
}
|
|
]
|
|
|
|
activity_ids = self.tracker.bulk_log_activities(activities_data)
|
|
|
|
assert len(activity_ids) == 2
|
|
assert all(isinstance(aid, int) for aid in activity_ids)
|
|
|
|
# Verify activities were created
|
|
activities_59 = self.tracker.get_issue_activities(59)
|
|
activities_60 = self.tracker.get_issue_activities(60)
|
|
|
|
assert len(activities_59) == 1
|
|
assert len(activities_60) == 1
|
|
assert activities_59[0].activity_details == 'Bulk created'
|
|
assert activities_60[0].activity_details == 'Bulk modified'
|
|
|
|
def test_bulk_log_activities_validation(self):
|
|
"""Test bulk logging validates required fields."""
|
|
invalid_data = [
|
|
{'issue_id': 59}, # Missing activity_type
|
|
{'activity_type': 'created'} # Missing issue_id
|
|
]
|
|
|
|
with pytest.raises(ValueError, match="must have 'issue_id' and 'activity_type'"):
|
|
self.tracker.bulk_log_activities(invalid_data)
|
|
|
|
|
|
class TestActivityCommands:
|
|
"""Test suite for activity CLI commands."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.temp_db = tempfile.NamedTemporaryFile(suffix='.db', delete=False)
|
|
self.temp_db.close()
|
|
self.db_path = self.temp_db.name
|
|
|
|
# Initialize database with test data
|
|
tracker = IssueActivityTracker(self.db_path)
|
|
tracker.log_activity(59, ActivityType.CREATED, activity_details="Test issue created")
|
|
tracker.log_activity(59, ActivityType.MODIFIED, activity_details="Test issue modified")
|
|
|
|
def teardown_method(self):
|
|
"""Clean up test fixtures."""
|
|
Path(self.db_path).unlink(missing_ok=True)
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_log_command_basic(self, mock_tracker_class):
|
|
"""Test the log command with basic parameters."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
mock_tracker.log_activity.return_value = 123
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['log', '59', 'created'])
|
|
|
|
assert result.exit_code == 0
|
|
assert "✅ Logged created activity for issue #59" in result.output
|
|
mock_tracker.log_activity.assert_called_once()
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_log_command_with_details(self, mock_tracker_class):
|
|
"""Test the log command with activity details."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
mock_tracker.log_activity.return_value = 123
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['log', '59', 'created', '--details', 'Test details'])
|
|
|
|
assert result.exit_code == 0
|
|
assert "Test details" in result.output
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_show_command(self, mock_tracker_class):
|
|
"""Test the show command for displaying issue activities."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
|
|
mock_activities = [
|
|
IssueActivity(
|
|
id=1,
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
activity_date=date.today(),
|
|
activity_details="Test activity",
|
|
created_at=datetime.now()
|
|
)
|
|
]
|
|
mock_tracker.get_issue_activities.return_value = mock_activities
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['show', '59'])
|
|
|
|
assert result.exit_code == 0
|
|
assert "📋 Activities for Issue #59" in result.output
|
|
assert "Test activity" in result.output
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_show_command_json_format(self, mock_tracker_class):
|
|
"""Test the show command with JSON output format."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
|
|
mock_activities = [
|
|
IssueActivity(
|
|
id=1,
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
activity_date=date.today(),
|
|
activity_details="Test activity",
|
|
created_at=datetime.now()
|
|
)
|
|
]
|
|
mock_tracker.get_issue_activities.return_value = mock_activities
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['show', '59', '--format', 'json'])
|
|
|
|
assert result.exit_code == 0
|
|
# Should be valid JSON
|
|
output_data = json.loads(result.output.strip())
|
|
assert len(output_data) == 1
|
|
assert output_data[0]['issue_id'] == 59
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_summary_command(self, mock_tracker_class):
|
|
"""Test the summary command."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
|
|
mock_summary = {
|
|
'total_activities': 5,
|
|
'unique_issues': 3,
|
|
'activities_by_type': {'created': 3, 'modified': 2},
|
|
'date_range': {'start': '2025-10-01', 'end': '2025-10-04'},
|
|
'filters': {'issue_id': None, 'start_date': None, 'end_date': None}
|
|
}
|
|
mock_tracker.get_activity_summary.return_value = mock_summary
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['summary'])
|
|
|
|
assert result.exit_code == 0
|
|
assert "📊 Issue Activity Summary" in result.output
|
|
assert "Total Activities: 5" in result.output
|
|
assert "Unique Issues: 3" in result.output
|
|
|
|
@patch('markitect.issues.activity_commands.IssueActivityTracker')
|
|
def test_delete_command(self, mock_tracker_class):
|
|
"""Test the delete command."""
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
mock_tracker.delete_activity.return_value = True
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
# Auto-confirm the deletion
|
|
result = runner.invoke(activity, ['delete', '123'], input='y\n')
|
|
|
|
assert result.exit_code == 0
|
|
assert "✅ Deleted activity #123" in result.output
|
|
mock_tracker.delete_activity.assert_called_once_with(123)
|
|
|
|
def test_import_activities_json(self):
|
|
"""Test importing activities from JSON file."""
|
|
# Create test JSON file
|
|
test_data = [
|
|
{
|
|
'issue_id': 59,
|
|
'activity_type': 'created',
|
|
'activity_details': 'Imported activity'
|
|
}
|
|
]
|
|
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
|
json.dump(test_data, f)
|
|
json_file_path = f.name
|
|
|
|
try:
|
|
with patch('markitect.issues.activity_commands.IssueActivityTracker') as mock_tracker_class:
|
|
mock_tracker = Mock()
|
|
mock_tracker_class.return_value = mock_tracker
|
|
mock_tracker.bulk_log_activities.return_value = [1]
|
|
|
|
from click.testing import CliRunner
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(activity, ['import-activities', json_file_path])
|
|
|
|
assert result.exit_code == 0
|
|
assert "✅ Successfully imported 1 activities" in result.output
|
|
finally:
|
|
Path(json_file_path).unlink(missing_ok=True)
|
|
|
|
|
|
class TestActivityIntegration:
|
|
"""Integration tests for the complete activity tracking system."""
|
|
|
|
def setup_method(self):
|
|
"""Set up integration test fixtures."""
|
|
self.temp_db = tempfile.NamedTemporaryFile(suffix='.db', delete=False)
|
|
self.temp_db.close()
|
|
self.db_path = self.temp_db.name
|
|
|
|
def teardown_method(self):
|
|
"""Clean up integration test fixtures."""
|
|
Path(self.db_path).unlink(missing_ok=True)
|
|
|
|
def test_full_activity_lifecycle(self):
|
|
"""Test the complete lifecycle of activity tracking."""
|
|
tracker = IssueActivityTracker(self.db_path)
|
|
|
|
# 1. Log initial activity
|
|
activity_id = tracker.log_activity(
|
|
issue_id=59,
|
|
activity_type=ActivityType.CREATED,
|
|
activity_details="Issue created for testing"
|
|
)
|
|
assert activity_id is not None
|
|
|
|
# 2. Log follow-up activities (with slight time differences to ensure ordering)
|
|
import time
|
|
time.sleep(0.1)
|
|
tracker.log_activity(59, ActivityType.MODIFIED, activity_details="Updated description")
|
|
time.sleep(0.1)
|
|
tracker.log_activity(59, ActivityType.COMMENTED, activity_details="Added comment")
|
|
time.sleep(0.1)
|
|
tracker.log_activity(59, ActivityType.CLOSED, activity_details="Resolved issue")
|
|
|
|
# 3. Retrieve issue history
|
|
activities = tracker.get_issue_activities(59)
|
|
assert len(activities) == 4
|
|
|
|
# Verify all expected activity types are present
|
|
activity_types = [a.activity_type.value for a in activities]
|
|
expected_types = {'closed', 'commented', 'modified', 'created'}
|
|
assert set(activity_types) == expected_types
|
|
|
|
# 4. Generate summary
|
|
summary = tracker.get_activity_summary(issue_id=59)
|
|
assert summary['total_activities'] == 4
|
|
assert summary['unique_issues'] == 1
|
|
assert len(summary['activities_by_type']) == 4
|
|
|
|
# 5. Clean up - delete an activity
|
|
deleted = tracker.delete_activity(activity_id)
|
|
assert deleted is True
|
|
|
|
# Verify deletion
|
|
remaining_activities = tracker.get_issue_activities(59)
|
|
assert len(remaining_activities) == 3
|
|
|
|
def test_multi_issue_activity_tracking(self):
|
|
"""Test activity tracking across multiple issues."""
|
|
tracker = IssueActivityTracker(self.db_path)
|
|
|
|
# Log activities for multiple issues
|
|
issues = [59, 60, 61, 62]
|
|
for issue_id in issues:
|
|
tracker.log_activity(issue_id, ActivityType.CREATED)
|
|
if issue_id % 2 == 0: # Even issues get modified
|
|
tracker.log_activity(issue_id, ActivityType.MODIFIED)
|
|
|
|
# Test overall summary
|
|
summary = tracker.get_activity_summary()
|
|
assert summary['total_activities'] == 6 # 4 created + 2 modified
|
|
assert summary['unique_issues'] == 4
|
|
assert summary['activities_by_type']['created'] == 4
|
|
assert summary['activities_by_type']['modified'] == 2
|
|
|
|
# Test individual issue tracking
|
|
for issue_id in issues:
|
|
activities = tracker.get_issue_activities(issue_id)
|
|
expected_count = 2 if issue_id % 2 == 0 else 1
|
|
assert len(activities) == expected_count
|
|
|
|
def test_cost_period_integration(self):
|
|
"""Test integration with cost period functionality."""
|
|
tracker = IssueActivityTracker(self.db_path)
|
|
|
|
# Create cost periods
|
|
with tracker.finance_models.get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("""
|
|
INSERT INTO cost_periods (period_start, period_end, total_costs)
|
|
VALUES ('2025-01-01', '2025-03-31', 5000.00)
|
|
""")
|
|
q1_period_id = cursor.lastrowid
|
|
|
|
cursor.execute("""
|
|
INSERT INTO cost_periods (period_start, period_end, total_costs)
|
|
VALUES ('2025-04-01', '2025-06-30', 6000.00)
|
|
""")
|
|
q2_period_id = cursor.lastrowid
|
|
|
|
periods = {'Q1 2025': q1_period_id, 'Q2 2025': q2_period_id}
|
|
|
|
# Log activities in different periods
|
|
tracker.log_activity(59, ActivityType.CREATED, period_id=periods['Q1 2025'])
|
|
tracker.log_activity(60, ActivityType.MODIFIED, period_id=periods['Q1 2025'])
|
|
tracker.log_activity(61, ActivityType.CLOSED, period_id=periods['Q2 2025'])
|
|
|
|
# Test period-based retrieval
|
|
q1_activities = tracker.get_activities_by_period(periods['Q1 2025'])
|
|
q2_activities = tracker.get_activities_by_period(periods['Q2 2025'])
|
|
|
|
assert len(q1_activities) == 2
|
|
assert len(q2_activities) == 1
|
|
assert all(a.period_id == periods['Q1 2025'] for a in q1_activities)
|
|
assert all(a.period_id == periods['Q2 2025'] for a in q2_activities)
|
|
|
|
# Test filtering by activity type within period
|
|
q1_created = tracker.get_activities_by_period(
|
|
periods['Q1 2025'],
|
|
activity_types=[ActivityType.CREATED]
|
|
)
|
|
assert len(q1_created) == 1
|
|
assert q1_created[0].activity_type == ActivityType.CREATED |