Skip to content
Open
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
2 changes: 1 addition & 1 deletion tahrir/endpoints/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def get_recent_assertions():
begin = request.args.get("begin", 0, type=int)
limit = min(request.args.get("limit", 40, type=int), 40)

assertions = list(g.tahrirdb.get_all_assertions(begin, limit))
assertions = list(g.tahrirdb.get_all_assertions().offset(begin).limit(limit).all())
if not assertions:
return jsonify([])

Expand Down
17 changes: 10 additions & 7 deletions tahrir/endpoints/invitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from flask import abort, g, jsonify

from ..app import csrf, oidc
from ..utils.user import get_person
from . import blueprint as bp


Expand Down Expand Up @@ -34,15 +33,19 @@ def claim_invitation(invitation_id: str):
return jsonify({"message": f"You have earned badge {claim.badge_id!r}"})


@bp.route("/api/invitations/<string:user_id>", methods=["GET"])
def get_invitations(user_id: str):
@csrf.exempt
@oidc.require_login
@bp.route("/api/invitations", methods=["GET"])
def get_invitations():
"""Endpoint to list all invitations created by a given user."""

# TODO - Modify the endpoint to require authentication
# TODO - Remove the user_id path parameter requirement
person = get_person(user_id)
email = g.oidc_user.email
if not email:
return abort(401, "Unauthorized")

person = g.oidc_user.person
if not person:
return abort(404, f"User {user_id!r} not found")
return abort(404, "User not found")

invitations = g.tahrirdb.get_invitations(person_id=person.id)

Expand Down
4 changes: 2 additions & 2 deletions tahrir/utils/badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def badge_json_generator(badge, withasserts=True):
"image": badge.image,
"tags": [item.strip() for item in badge.tags.split(",") if item.strip() != ""],
"criteria": badge.criteria,
"rarity": raredata["badges"][badge.id]["rare"],
"rarity": raredata["badges"].get(badge.id, {}).get("rare", "common"),
"issuer": badge.issuer.name,
"created_on": badge.created_on.timestamp(),
}
Expand Down Expand Up @@ -100,7 +100,7 @@ def badge_json_generator(badge, withasserts=True):
"tags": [item.strip() for item in badge.tags.split(",") if item.strip() != ""],
"issuer": badge.issuer.name,
"criteria": badge.criteria,
"rarity": raredata["badges"][badge.id]["rare"],
"rarity": raredata["badges"].get(badge.id, {}).get("rare", "common"),
"assertions": [
{
"name": i.person.nickname,
Expand Down
13 changes: 2 additions & 11 deletions tahrir/views/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import json
import os
from datetime import datetime, timezone
from decimal import Decimal, ROUND_UP

from feedgen.feed import FeedGenerator
from flask import abort, current_app, g, jsonify, render_template, request, url_for
from flask import abort, g, jsonify, render_template, request, url_for

from tahrir.utils.badge import badge_json_generator, sort_badges_by_tag
from tahrir.utils.user import get_person
Expand Down Expand Up @@ -162,18 +160,11 @@ def _user_json_generator(person):
serialized = []
classified = {name: [] for name in TAHRIR_DISPLAY_TAGS}

try:
with open(os.path.join(current_app.static_folder, "rarities.json")) as file:
raredata = json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
abort(500, "Mistaken or absent rarities file")

for indx, item in enumerate(assertions):
issued = {"issued": float(item.issued_on.strftime("%s"))}
reason = {"reason": item.issued_for or None}
rarity = {"rarity": raredata["badges"][item.badge.id]["rare"]}
badged = badge_json_generator(item.badge, withasserts=False)
serialized.append({**issued, **badged, **reason, **rarity})
serialized.append({**issued, **badged, **reason})
for name in classified.keys():
if name in item.badge.tags:
classified[name].append(indx)
Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) 2024 Red Hat, Inc.
"""Shared test fixtures."""

import pytest


@pytest.fixture(scope="session")
def app():
from tahrir.app import create_app

app = create_app()
app.config["TESTING"] = True
app.config["WTF_CSRF_ENABLED"] = False
return app
47 changes: 47 additions & 0 deletions tests/test_endpoints_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2024 Red Hat, Inc.
"""Test tahrir.endpoints.users."""

from unittest.mock import MagicMock, patch


def test_search_users_by_string(app):
"""Test that the search endpoint calls get_persons_by_nickname."""
mock_db = MagicMock()
mock_db.get_persons_by_nickname.return_value = {
"users": [],
"total": 0,
"begin": 0,
"limit": 100,
}

with app.test_request_context("/api/users/search/alice"):
with patch("tahrir.endpoints.users.g") as mock_g:
mock_g.tahrirdb = mock_db
from tahrir.endpoints.users import search_users_by_string

with patch("tahrir.endpoints.users.jsonify") as mock_jsonify:
search_users_by_string("alice")
mock_db.get_persons_by_nickname.assert_called_once_with("alice", 0, 100)
mock_jsonify.assert_called_once()


def test_search_users_by_string_with_results(app):
"""Test that results from get_persons_by_nickname are returned."""
mock_result = {
"users": [{"id": 1, "nickname": "alice_wonder", "avatar": None, "rank": None}],
"total": 1,
"begin": 0,
"limit": 100,
}
mock_db = MagicMock()
mock_db.get_persons_by_nickname.return_value = mock_result

with app.test_request_context("/api/users/search/alice"):
with patch("tahrir.endpoints.users.g") as mock_g:
mock_g.tahrirdb = mock_db
from tahrir.endpoints.users import search_users_by_string

with patch("tahrir.endpoints.users.jsonify") as mock_jsonify:
search_users_by_string("alice")
mock_db.get_persons_by_nickname.assert_called_once_with("alice", 0, 100)
mock_jsonify.assert_called_once_with(mock_result)
79 changes: 79 additions & 0 deletions tests/test_invitations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright (c) 2024 Red Hat, Inc.
"""Test tahrir.endpoints.invitations."""

from unittest.mock import MagicMock, patch

import pytest
from werkzeug.exceptions import NotFound, Unauthorized


def test_get_invitations_unauthenticated(app):
"""Test that unauthenticated requests return 401."""
with app.test_request_context("/api/invitations"):
with patch("tahrir.endpoints.invitations.g") as mock_g:
mock_g.oidc_user.email = None
from tahrir.endpoints.invitations import get_invitations

with pytest.raises(Unauthorized):
get_invitations.__wrapped__()


def test_get_invitations_user_not_found(app):
"""Test that 404 is returned when person is not found."""
with app.test_request_context("/api/invitations"):
with patch("tahrir.endpoints.invitations.g") as mock_g:
mock_g.oidc_user.email = "aaronhale@tinystage.test"
mock_g.oidc_user.person = None
from tahrir.endpoints.invitations import get_invitations

with pytest.raises(NotFound):
get_invitations.__wrapped__()


def test_get_invitations_no_invitations(app):
"""Test that 404 is returned when user has no invitations."""
mock_db = MagicMock()
mock_db.get_invitations.return_value = []

with app.test_request_context("/api/invitations"):
with patch("tahrir.endpoints.invitations.g") as mock_g:
mock_g.tahrirdb = mock_db
mock_g.oidc_user.email = "aaronhale@tinystage.test"
mock_g.oidc_user.person = MagicMock(id=1)
from tahrir.endpoints.invitations import get_invitations

with pytest.raises(NotFound):
get_invitations.__wrapped__()


def test_get_invitations_returns_data(app):
"""Test that invitations are returned correctly for authenticated user."""
mock_badge = MagicMock()
mock_badge.id = "badge-1"
mock_badge.name = "Cool Badge"
mock_badge.image = "http://example.com/badge.png"

mock_invitation = MagicMock()
mock_invitation.id = "inv-1"
mock_invitation.badge = mock_badge
mock_invitation.created_on = None
mock_invitation.expires_on = None
mock_invitation.expired = False

mock_db = MagicMock()
mock_db.get_invitations.return_value = [mock_invitation]

with app.test_request_context("/api/invitations"):
with patch("tahrir.endpoints.invitations.g") as mock_g:
mock_g.tahrirdb = mock_db
mock_g.oidc_user.email = "aaronhale@tinystage.test"
mock_g.oidc_user.person = MagicMock(id=1)
from tahrir.endpoints.invitations import get_invitations

response = get_invitations.__wrapped__()
assert response.status_code == 200
data = response.get_json()
assert "badge-1" in data
assert data["badge-1"]["name"] == "Cool Badge"
assert len(data["badge-1"]["invitations"]) == 1
assert data["badge-1"]["invitations"][0]["invitation_id"] == "inv-1"