Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/hearts/hearts_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
bp = Blueprint(bp_name, __name__, url_prefix=bp_url_prefix)


@bp.route("/hearts/leaderboard", methods=["GET"])
def get_hearts_leaderboard():
limit = request.args.get("limit", 10, type=int)
limit = min(max(limit, 1), 50)
res = hearts_service.get_hearts_leaderboard(limit)
return {"leaderboard": res}


# Used to provide profile details - user must be logged in
@bp.route("/hearts", methods=["GET"])
@auth.require_user
Expand Down
25 changes: 25 additions & 0 deletions services/hearts_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,31 @@ def get_hearts_for_all_users():
return result


def get_hearts_leaderboard(limit: int = 10) -> list:
users = fetch_users()
result = []

for user in users:
total_hearts = 0

if user.history:
for key in user.history:
if "certificates" in key:
continue
for subkey in user.history[key]:
total_hearts += user.history[key][subkey]

if total_hearts > 0:
result.append({
"name": user.name,
"totalHearts": total_hearts,
"userId": user.id,
"profileImage": user.profile_image,
})

result.sort(key=lambda x: x["totalHearts"], reverse=True)
return result[:limit]



def save_hearts(user_id, hearts_json):
Expand Down
86 changes: 86 additions & 0 deletions test/services/test_hearts_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import sys
from types import ModuleType
from unittest.mock import MagicMock, patch

# Pre-mock all problematic modules before any imports
_mock_modules = [
"db.db", "db.firestore", "db.mem", "db.interface",
"google.cloud", "google.cloud.storage", "google.cloud.firestore",
"common.utils.firebase", "common.utils.slack", "common.utils.cdn",
"openai", "PIL", "PIL.ImageFont", "PIL.ImageDraw", "PIL.Image",
"PIL.ImageEnhance",
]
for mod_name in _mock_modules:
if mod_name not in sys.modules:
sys.modules[mod_name] = MagicMock()

# Now safe to import
from services.hearts_service import get_hearts_leaderboard


def _make_user(name, user_id, profile_image, history):
user = MagicMock()
user.name = name
user.id = user_id
user.profile_image = profile_image
user.history = history
return user


USERS = [
_make_user("Alice", "u1", "img1.png", {
"how": {"code_reliability": 3, "standups_completed": 2},
"what": {"documentation": 1},
}),
_make_user("Bob", "u2", "img2.png", {
"how": {"code_reliability": 1},
"what": {"documentation": 0.5},
}),
_make_user("Carol", "u3", "img3.png", {
"how": {"code_reliability": 0},
"what": {"documentation": 0},
}),
_make_user("Dave", "u4", "img4.png", {
"how": {"code_reliability": 10},
"what": {"unit_test_writing": 5},
"certificates": ["cert1.png"],
}),
_make_user("Eve", "u5", None, {}),
]


@patch("services.hearts_service.fetch_users", return_value=USERS)
def test_sort_order_descending(mock_fetch):
result = get_hearts_leaderboard(limit=50)
hearts = [entry["totalHearts"] for entry in result]
assert hearts == sorted(hearts, reverse=True)


@patch("services.hearts_service.fetch_users", return_value=USERS)
def test_zero_hearts_excluded(mock_fetch):
result = get_hearts_leaderboard(limit=50)
names = [entry["name"] for entry in result]
assert "Carol" not in names
assert "Eve" not in names


@patch("services.hearts_service.fetch_users", return_value=USERS)
def test_certificates_excluded_from_count(mock_fetch):
result = get_hearts_leaderboard(limit=50)
dave = next(e for e in result if e["name"] == "Dave")
# Only how + what should count: 10 + 5 = 15, not certificates
assert dave["totalHearts"] == 15


@patch("services.hearts_service.fetch_users", return_value=USERS)
def test_limit_respected(mock_fetch):
result = get_hearts_leaderboard(limit=2)
assert len(result) == 2


@patch("services.hearts_service.fetch_users", return_value=USERS)
def test_correct_return_fields(mock_fetch):
result = get_hearts_leaderboard(limit=50)
expected_keys = {"name", "totalHearts", "userId", "profileImage"}
for entry in result:
assert set(entry.keys()) == expected_keys