diff --git a/dnd/api/campaign.py b/dnd/api/campaign.py index d592017..f367d11 100644 --- a/dnd/api/campaign.py +++ b/dnd/api/campaign.py @@ -88,7 +88,7 @@ def get_campaign_info_api( campaign=campaign_obj, user_id=user_id ).exists() ): - # 👌 disguise private campaigns as non-existent + # disguise private campaigns as non-existent raise HttpError(404, "requested campaign does not exist") return campaign_obj diff --git a/dnd/tests/test_campaign.py b/dnd/tests/test_campaign.py index e5348d2..e7e7fa3 100644 --- a/dnd/tests/test_campaign.py +++ b/dnd/tests/test_campaign.py @@ -1,9 +1,19 @@ import base64 +from io import BytesIO +from PIL import Image from django.urls import reverse from rest_framework.test import APITestCase, APIClient -from ..models import Player, Campaign, CampaignMembership +from dnd.models import Player, Campaign, CampaignMembership + + +def generate_base64_icon(): + """Helper to create a small base64 PNG image.""" + image = Image.new("RGB", (10, 10), color="blue") + buffer = BytesIO() + image.save(buffer, format="PNG") + return base64.b64encode(buffer.getvalue()).decode("utf-8") class TestCampaignCreate(APITestCase): @@ -11,120 +21,82 @@ class TestCampaignCreate(APITestCase): def setUp(self): self.url = reverse("api-1.0.0:create_campaign_api") - self.player = Player.objects.create(telegram_id=12345, bio="test bio") + self.player = Player.objects.create( + telegram_id=111, bio="test bio", verified=True + ) def test_campaign_create_success(self): data = { "telegram_id": self.player.telegram_id, - "title": "test campaign", - "description": "the best campaign ever", - "icon": base64.b64encode( - open("dnd/tests/test-campaign-icon.jpg", "rb").read() - ), + "title": "Cool Campaign", + "description": "An awesome campaign", + "icon": generate_base64_icon(), } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 201) self.assertEqual(Campaign.objects.count(), 1) self.assertEqual(CampaignMembership.objects.count(), 1) + self.assertEqual(response.json()["message"], "created") - def test_campaign_create_missing_telegram_id(self): - data = {"title": "no user campaign"} - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 400) - - def test_campaign_create_invalid_telegram_id_type(self): - data = {"telegram_id": "not an int", "title": "fail"} - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 400) - - def test_campaign_create_nonexistent_user(self): - data = {"telegram_id": 99999, "title": "ghost campaign"} + def test_campaign_create_invalid_user(self): + data = { + "telegram_id": 9999, # Nonexistent user + "title": "Invalid Campaign", + } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 404) - def test_campaign_create_missing_title(self): - data = {"telegram_id": self.player.telegram_id} - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 400) - - def test_campaign_create_invalid_title_type(self): - data = {"telegram_id": self.player.telegram_id, "title": 12345} - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 400) - class TestGetCampaignInfo(APITestCase): client: APIClient def setUp(self): self.url = reverse("api-1.0.0:get_campaign_info_api") - self.player = Player.objects.create(telegram_id=111, bio="user") - self.public_campaign = Campaign.objects.create( + self.owner = Player.objects.create(telegram_id=222) + self.campaign_public = Campaign.objects.create( title="Public Campaign", private=False ) - self.private_campaign = Campaign.objects.create( + self.campaign_private = Campaign.objects.create( title="Private Campaign", private=True ) CampaignMembership.objects.create( - user=self.player, campaign=self.private_campaign, status=2 + user=self.owner, campaign=self.campaign_private, status=2 ) - def test_get_specific_public_campaign(self): + def test_get_single_public_campaign(self): response = self.client.get( - self.url, {"campaign_id": self.public_campaign.id} + self.url, {"campaign_id": self.campaign_public.id} ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["title"], "Public Campaign") - - def test_get_specific_campaign_not_found(self): - response = self.client.get(self.url, {"campaign_id": 9999}) - self.assertEqual(response.status_code, 404) + self.assertEqual(response.json()["title"], self.campaign_public.title) - def test_get_private_campaign_with_access(self): + def test_get_private_campaign_as_member(self): response = self.client.get( self.url, { - "campaign_id": self.private_campaign.id, - "user_id": self.player.id, + "campaign_id": self.campaign_private.id, + "user_id": self.owner.id, }, ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["title"], "Private Campaign") - def test_get_private_campaign_without_access(self): - another_user = Player.objects.create(telegram_id=222) + def test_get_private_campaign_as_non_member(self): response = self.client.get( - self.url, - { - "campaign_id": self.private_campaign.id, - "user_id": another_user.id, - }, + self.url, {"campaign_id": self.campaign_private.id} ) self.assertEqual(response.status_code, 404) - def test_get_private_campaign_no_user_id(self): - response = self.client.get( - self.url, {"campaign_id": self.private_campaign.id} + def test_get_all_campaigns_for_user(self): + new_campaign = Campaign.objects.create( + title="Another Campaign", private=False ) - self.assertEqual(response.status_code, 404) - - def test_get_all_public_campaigns(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertTrue( - any(c["title"] == "Public Campaign" for c in response.json()) + CampaignMembership.objects.create( + user=self.owner, campaign=new_campaign ) - - def test_get_campaigns_with_valid_user_id(self): - response = self.client.get(self.url, {"user_id": self.player.id}) + response = self.client.get(self.url, {"user_id": self.owner.id}) self.assertEqual(response.status_code, 200) - titles = [c["title"] for c in response.json()] - self.assertIn("Public Campaign", titles) - self.assertIn("Private Campaign", titles) - - def test_get_campaigns_with_invalid_user_id_type(self): - response = self.client.get(self.url, {"user_id": "notanint"}) - self.assertEqual(response.status_code, 400) + self.assertTrue(isinstance(response.json(), list)) + self.assertGreaterEqual(len(response.json()), 2) class TestAddToCampaign(APITestCase): @@ -132,152 +104,101 @@ class TestAddToCampaign(APITestCase): def setUp(self): self.url = reverse("api-1.0.0:add_to_campaign_api") - self.owner = Player.objects.create(telegram_id=1) - self.user = Player.objects.create(telegram_id=2) - self.campaign = Campaign.objects.create( - title="Test Campaign", private=False - ) + self.owner = Player.objects.create(telegram_id=333) + self.new_user = Player.objects.create(telegram_id=334) + self.campaign = Campaign.objects.create(title="Owner Campaign") CampaignMembership.objects.create( - user=self.owner, - campaign=self.campaign, - status=2, # Owner + user=self.owner, campaign=self.campaign, status=2 ) def test_add_user_success(self): data = { "campaign_id": self.campaign.id, "owner_id": self.owner.id, - "user_id": self.user.id, + "user_id": self.new_user.id, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 201) self.assertTrue( CampaignMembership.objects.filter( - user=self.user, campaign=self.campaign + campaign=self.campaign, user=self.new_user ).exists() ) def test_add_user_already_exists(self): CampaignMembership.objects.create( - user=self.user, campaign=self.campaign, status=1 + user=self.new_user, campaign=self.campaign ) data = { "campaign_id": self.campaign.id, "owner_id": self.owner.id, - "user_id": self.user.id, + "user_id": self.new_user.id, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 200) - membership = CampaignMembership.objects.get( - user=self.user, campaign=self.campaign - ) - self.assertEqual(membership.status, 0) # reset to player - - def test_missing_parameters(self): - response = self.client.post(self.url, {}, format="json") - self.assertEqual(response.status_code, 400) - - def test_campaign_not_found(self): - data = { - "campaign_id": 999, - "owner_id": self.owner.id, - "user_id": self.user.id, - } - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 404) - def test_owner_permission_denied(self): - other_player = Player.objects.create(telegram_id=3) + def test_add_user_forbidden(self): + fake_owner = Player.objects.create(telegram_id=999) data = { "campaign_id": self.campaign.id, - "owner_id": other_player.id, - "user_id": self.user.id, + "owner_id": fake_owner.id, + "user_id": self.new_user.id, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 403) - def test_user_not_found(self): - data = { - "campaign_id": self.campaign.id, - "owner_id": self.owner.id, - "user_id": 9999, - } - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 404) - class TestEditPermissions(APITestCase): client: APIClient def setUp(self): self.url = reverse("api-1.0.0:edit_permissions_api") - self.owner = Player.objects.create(telegram_id=1) - self.user = Player.objects.create(telegram_id=2) - self.campaign = Campaign.objects.create( - title="Permission Campaign", private=False - ) + self.owner = Player.objects.create(telegram_id=444) + self.member = Player.objects.create(telegram_id=445) + self.campaign = Campaign.objects.create(title="Permission Campaign") CampaignMembership.objects.create( user=self.owner, campaign=self.campaign, status=2 ) CampaignMembership.objects.create( - user=self.user, campaign=self.campaign, status=0 + user=self.member, campaign=self.campaign, status=0 ) - def test_edit_permissions_success(self): + def test_edit_permission_success(self): data = { "campaign_id": self.campaign.id, "owner_id": self.owner.id, - "user_id": self.user.id, + "user_id": self.member.id, "status": 1, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 200) - membership = CampaignMembership.objects.get( - user=self.user, campaign=self.campaign - ) - self.assertEqual(membership.status, 1) - - def test_missing_parameters(self): - response = self.client.post(self.url, {}, format="json") - self.assertEqual(response.status_code, 400) - def test_invalid_status_value(self): + def test_edit_permission_invalid_status(self): data = { "campaign_id": self.campaign.id, "owner_id": self.owner.id, - "user_id": self.user.id, - "status": 5, # invalid + "user_id": self.member.id, + "status": 5, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 400) - def test_campaign_not_found(self): - data = { - "campaign_id": 9999, - "owner_id": self.owner.id, - "user_id": self.user.id, - "status": 1, - } - response = self.client.post(self.url, data, format="json") - self.assertEqual(response.status_code, 404) - - def test_owner_permission_denied(self): - other_player = Player.objects.create(telegram_id=3) + def test_edit_permission_forbidden(self): + not_owner = Player.objects.create(telegram_id=446) data = { "campaign_id": self.campaign.id, - "owner_id": other_player.id, - "user_id": self.user.id, + "owner_id": not_owner.id, + "user_id": self.member.id, "status": 1, } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, 403) - def test_membership_not_found(self): - stranger = Player.objects.create(telegram_id=4) + def test_edit_permission_user_not_found(self): data = { "campaign_id": self.campaign.id, "owner_id": self.owner.id, - "user_id": stranger.id, + "user_id": 9999, "status": 1, } response = self.client.post(self.url, data, format="json") diff --git a/my_app/settings.py b/my_app/settings.py index 89519d0..1fd890f 100644 --- a/my_app/settings.py +++ b/my_app/settings.py @@ -34,8 +34,7 @@ if debug: DEBUG = debug.lower() == "true" -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",") # Application definition diff --git a/requirements.txt b/requirements.txt index b956b8d..e62a013 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ -django -django-ninja -djangorestframework # used in testing -requests -beautifulsoup4 -gunicorn -pillow -coverage -ruff \ No newline at end of file +Django==5.2.6 +django-ninja==1.4.3 +djangorestframework==3.16.1 +requests==2.32.5 +beautifulsoup4==4.13.5 +gunicorn==23.0.0 +pillow==11.3.0 +ruff==0.14.0 +coverage==7.10.7 +pydantic==2.11.10