feat: enhance weekly report and realtime notifications
Weekly Report (fix-weekly-report): - Remove 5-task limit, show all tasks per category - Add blocked tasks with blocker_reason and blocked_since - Add next week tasks (due in coming week) - Add assignee_name, completed_at, days_overdue to task details - Frontend collapsible sections for each task category - 8 new tests for enhanced report content Realtime Notifications (fix-realtime-notifications): - SQLAlchemy event-based notification publishing - Redis Pub/Sub for multi-process broadcast - Fix soft rollback handler stacking issue - Fix ping scheduling drift (send immediately when interval expires) - Frontend NotificationContext with WebSocket reconnection Spec Fixes: - Add missing ## Purpose sections to 5 specs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from app.models import User, Space, Project, Task, TaskStatus, ScheduledReport, ReportHistory
|
||||
from app.models import User, Space, Project, Task, TaskStatus, ScheduledReport, ReportHistory, Blocker
|
||||
from app.services.report_service import ReportService
|
||||
|
||||
|
||||
@@ -258,3 +258,186 @@ class TestReportAPI:
|
||||
data = response.json()
|
||||
assert data["id"] == report.id
|
||||
assert "content" in data
|
||||
|
||||
|
||||
class TestWeeklyReportContent:
|
||||
"""Tests for enhanced weekly report content (blocked/next_week tasks)."""
|
||||
|
||||
def test_blocked_tasks_included(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that blocked tasks are included in weekly stats."""
|
||||
# Create a task with blocker
|
||||
blocked_task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="Blocked Task",
|
||||
status_id=test_statuses["in_progress"].id,
|
||||
created_by=test_user.id,
|
||||
assignee_id=test_user.id,
|
||||
)
|
||||
db.add(blocked_task)
|
||||
db.flush()
|
||||
|
||||
# Create an unresolved blocker
|
||||
blocker = Blocker(
|
||||
id=str(uuid.uuid4()),
|
||||
task_id=blocked_task.id,
|
||||
reported_by=test_user.id,
|
||||
reason="Waiting for external dependency",
|
||||
)
|
||||
db.add(blocker)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert stats["summary"]["blocked_count"] == 1
|
||||
assert len(stats["projects"]) == 1
|
||||
assert stats["projects"][0]["blocked_count"] == 1
|
||||
assert len(stats["projects"][0]["blocked_tasks"]) == 1
|
||||
assert stats["projects"][0]["blocked_tasks"][0]["title"] == "Blocked Task"
|
||||
assert stats["projects"][0]["blocked_tasks"][0]["blocker_reason"] == "Waiting for external dependency"
|
||||
|
||||
def test_resolved_blocker_not_included(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that resolved blockers are not counted."""
|
||||
# Create a task with resolved blocker
|
||||
task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="Previously Blocked Task",
|
||||
status_id=test_statuses["in_progress"].id,
|
||||
created_by=test_user.id,
|
||||
)
|
||||
db.add(task)
|
||||
db.flush()
|
||||
|
||||
# Create a resolved blocker
|
||||
blocker = Blocker(
|
||||
id=str(uuid.uuid4()),
|
||||
task_id=task.id,
|
||||
reported_by=test_user.id,
|
||||
reason="Was blocked",
|
||||
resolved_by=test_user.id,
|
||||
resolved_at=datetime.utcnow(),
|
||||
resolution_note="Fixed",
|
||||
)
|
||||
db.add(blocker)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert stats["summary"]["blocked_count"] == 0
|
||||
assert stats["projects"][0]["blocked_count"] == 0
|
||||
|
||||
def test_next_week_tasks_included(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that next week tasks are included in weekly stats."""
|
||||
# Calculate next week dates
|
||||
week_start = ReportService.get_week_start()
|
||||
next_week_date = week_start + timedelta(days=10) # Next week
|
||||
|
||||
# Create a task due next week
|
||||
next_week_task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="Next Week Task",
|
||||
status_id=test_statuses["todo"].id,
|
||||
due_date=next_week_date,
|
||||
created_by=test_user.id,
|
||||
assignee_id=test_user.id,
|
||||
)
|
||||
db.add(next_week_task)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert stats["summary"]["next_week_count"] == 1
|
||||
assert len(stats["projects"][0]["next_week_tasks"]) == 1
|
||||
assert stats["projects"][0]["next_week_tasks"][0]["title"] == "Next Week Task"
|
||||
|
||||
def test_completed_task_not_in_next_week(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that completed tasks are not included in next week list."""
|
||||
week_start = ReportService.get_week_start()
|
||||
next_week_date = week_start + timedelta(days=10)
|
||||
|
||||
# Create a completed task due next week
|
||||
task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="Done Next Week Task",
|
||||
status_id=test_statuses["done"].id,
|
||||
due_date=next_week_date,
|
||||
created_by=test_user.id,
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert stats["summary"]["next_week_count"] == 0
|
||||
|
||||
def test_task_details_include_assignee_name(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that task details include assignee name."""
|
||||
task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="Assigned Task",
|
||||
status_id=test_statuses["in_progress"].id,
|
||||
created_by=test_user.id,
|
||||
assignee_id=test_user.id,
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert len(stats["projects"][0]["in_progress_tasks"]) == 1
|
||||
assert stats["projects"][0]["in_progress_tasks"][0]["assignee_name"] == "Report User"
|
||||
|
||||
def test_overdue_days_calculated(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that days_overdue is correctly calculated."""
|
||||
# Create task overdue by 5 days
|
||||
overdue_task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title="5 Days Overdue",
|
||||
status_id=test_statuses["todo"].id,
|
||||
due_date=datetime.utcnow() - timedelta(days=5),
|
||||
created_by=test_user.id,
|
||||
)
|
||||
db.add(overdue_task)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert len(stats["projects"][0]["overdue_tasks"]) == 1
|
||||
assert stats["projects"][0]["overdue_tasks"][0]["days_overdue"] >= 5
|
||||
|
||||
def test_full_task_lists_no_limit(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that task lists have no 5-item limit."""
|
||||
# Create 10 completed tasks
|
||||
for i in range(10):
|
||||
task = Task(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
title=f"Completed Task {i}",
|
||||
status_id=test_statuses["done"].id,
|
||||
created_by=test_user.id,
|
||||
)
|
||||
task.updated_at = datetime.utcnow()
|
||||
db.add(task)
|
||||
db.commit()
|
||||
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
assert stats["summary"]["completed_count"] == 10
|
||||
assert len(stats["projects"][0]["completed_tasks"]) == 10 # No limit
|
||||
|
||||
def test_summary_includes_all_counts(self, db, test_user, test_project, test_statuses):
|
||||
"""Test that summary includes all new count fields."""
|
||||
stats = ReportService.get_weekly_stats(db, test_user.id)
|
||||
|
||||
summary = stats["summary"]
|
||||
assert "completed_count" in summary
|
||||
assert "in_progress_count" in summary
|
||||
assert "overdue_count" in summary
|
||||
assert "blocked_count" in summary
|
||||
assert "next_week_count" in summary
|
||||
assert "total_tasks" in summary
|
||||
|
||||
Reference in New Issue
Block a user