From 31ca826a2e7b12a752dc817b3746fb69c51a669e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 May 2025 20:37:38 +0300 Subject: [PATCH 1/2] refactor: Rename URL helper methods for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swap and rename `promo_detail_url` → `get_user_promo_detail_url` and `get_promo_business_detail_url` → `get_business_promo_detail_url` - Improve readability and ensure consistent naming across tests --- promo_code/user/tests/user/base.py | 9 +++++---- promo_code/user/tests/user/operations/test_detail.py | 10 +++++----- promo_code/user/tests/user/operations/test_feed.py | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/promo_code/user/tests/user/base.py b/promo_code/user/tests/user/base.py index 99b560d..8c57445 100644 --- a/promo_code/user/tests/user/base.py +++ b/promo_code/user/tests/user/base.py @@ -69,20 +69,21 @@ def tearDown(self): business.models.Promo.objects.all().delete() business.models.PromoCode.objects.all().delete() user.models.User.objects.all().delete() + user.models.PromoLike.objects.all().delete() tb_models.BlacklistedToken.objects.all().delete() tb_models.OutstandingToken.objects.all().delete() super().tearDown() @classmethod - def promo_detail_url(cls, promo_id): + def get_business_promo_detail_url(cls, promo_id): return django.urls.reverse( - 'api-user:user-promo-detail', + 'api-business:promo-detail', kwargs={'id': promo_id}, ) @classmethod - def get_promo_business_detail_url(cls, promo_id): + def get_user_promo_detail_url(cls, promo_id): return django.urls.reverse( - 'api-business:promo-detail', + 'api-user:user-promo-detail', kwargs={'id': promo_id}, ) diff --git a/promo_code/user/tests/user/operations/test_detail.py b/promo_code/user/tests/user/operations/test_detail.py index e3d29bb..dcf1a9e 100644 --- a/promo_code/user/tests/user/operations/test_detail.py +++ b/promo_code/user/tests/user/operations/test_detail.py @@ -82,7 +82,7 @@ def setUp(self): self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.user_token) def test_get_promo_matching_target(self): - url = self.promo_detail_url(self.promo_sg_id) + url = self.get_user_promo_detail_url(self.promo_sg_id) response = self.client.get(url) self.assertEqual( response.status_code, @@ -94,7 +94,7 @@ def test_get_promo_matching_target(self): ) def test_get_promo_non_matching_target(self): - url = self.promo_detail_url(self.promo_kz_id) + url = self.get_user_promo_detail_url(self.promo_kz_id) response = self.client.get(url) self.assertEqual( response.status_code, @@ -118,7 +118,7 @@ def test_get_promo_non_matching_target(self): ) def test_user_promo_detail(self, promo_attr, forbidden_field): promo_id = getattr(self, promo_attr) - url = self.promo_detail_url(promo_id) + url = self.get_user_promo_detail_url(promo_id) response = self.client.get(url, format='json') self.assertEqual( response.status_code, @@ -132,7 +132,7 @@ def test_user_promo_detail(self, promo_attr, forbidden_field): def test_get_promo_invalid_token(self): self.client.credentials(HTTP_AUTHORIZATION='Bearer invalid.jwt.token') - url = self.promo_detail_url(self.promo_kz_id) + url = self.get_user_promo_detail_url(self.promo_kz_id) response = self.client.get(url) self.assertEqual( response.status_code, @@ -141,7 +141,7 @@ def test_get_promo_invalid_token(self): def test_get_promo_not_found(self): random_uuid = uuid.UUID('550e8400-e29b-41d4-a716-446655440000') - url = self.promo_detail_url(random_uuid) + url = self.get_user_promo_detail_url(random_uuid) response = self.client.get(url) self.assertEqual( response.status_code, diff --git a/promo_code/user/tests/user/operations/test_feed.py b/promo_code/user/tests/user/operations/test_feed.py index 64483af..898704a 100644 --- a/promo_code/user/tests/user/operations/test_feed.py +++ b/promo_code/user/tests/user/operations/test_feed.py @@ -654,7 +654,7 @@ def test_user4_sg_80_get_all_promos_after_edit(self): self.client.credentials( HTTP_AUTHORIZATION='Bearer ' + self.company2_token, ) - url = self.get_promo_business_detail_url(self.promo11_id) + url = self.get_business_promo_detail_url(self.promo11_id) patch_data = {'active_until': '2024-08-10'} response = self.client.patch(url, patch_data, format='json') self.assertEqual( From b08e387719c5e4b2a726d0b23e2286ac7589be5e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 May 2025 20:48:16 +0300 Subject: [PATCH 2/2] test(user): Add comprehensive tests for promo like/unlike endpoint This commit introduces a suite of DRF (Django REST Framework) unit/integration tests for the `api/user/promo/{id}/like` endpoint. The tests cover the following key scenarios: - Initial state verification: - `like_count` is 0 and `is_liked_by_user` is false for a new promo. - Company view also reflects `like_count` as 0. - Liking a promo: - A user can successfully like a promo. - `like_count` increments correctly. - `is_liked_by_user` becomes true for the liking user. - Idempotency of liking: - Liking an already liked promo returns a successful response without altering the like count further. - Multiple users liking the same promo: - `like_count` reflects likes from different users. - `is_liked_by_user` status is accurate for each respective user. - Unliking a promo: - A user can successfully unlike a promo they previously liked. - `like_count` decrements correctly. - `is_liked_by_user` becomes false for the unliking user. - Idempotency of unliking: - Unliking a promo not currently liked by the user (or already unliked) returns a successful response without errors or incorrect state changes. - Verification of state from different perspectives (liking user, other users, company). These tests ensure the reliability and correct behavior of the promo liking feature, including its idempotency and accurate reflection of like statuses and counts. --- promo_code/user/tests/user/base.py | 7 + .../user/tests/user/operations/test_like.py | 444 ++++++++++++++++++ 2 files changed, 451 insertions(+) create mode 100644 promo_code/user/tests/user/operations/test_like.py diff --git a/promo_code/user/tests/user/base.py b/promo_code/user/tests/user/base.py index 8c57445..36a2868 100644 --- a/promo_code/user/tests/user/base.py +++ b/promo_code/user/tests/user/base.py @@ -87,3 +87,10 @@ def get_user_promo_detail_url(cls, promo_id): 'api-user:user-promo-detail', kwargs={'id': promo_id}, ) + + @classmethod + def get_user_promo_like_url(cls, promo_id): + return django.urls.reverse( + 'api-user:user-promo-like', + kwargs={'id': promo_id}, + ) diff --git a/promo_code/user/tests/user/operations/test_like.py b/promo_code/user/tests/user/operations/test_like.py new file mode 100644 index 0000000..8c30e18 --- /dev/null +++ b/promo_code/user/tests/user/operations/test_like.py @@ -0,0 +1,444 @@ +import rest_framework.status +import rest_framework.test + +import user.tests.user.base + + +class TestUserPromoLikeActions(user.tests.user.base.BaseUserTestCase): + @classmethod + def setUpTestData(cls): + super().setUpTestData() + + user1_payload = { + 'name': 'Steve', + 'surname': 'Wozniak', + 'email': 'creator1@apple.com', + 'password': 'WhoLiveSInCalifornia2000!', + 'other': {'age': 60, 'country': 'gb'}, + } + resp_user1 = cls.client.post( + cls.user_signup_url, + user1_payload, + format='json', + ) + assert resp_user1.status_code == rest_framework.status.HTTP_200_OK, ( + 'User1 signup failed' + ) + cls.user1_token = resp_user1.data['access'] + + user2_payload = { + 'name': 'Mike', + 'surname': 'Bloomberg', + 'email': 'mike@bloomberg.com', + 'password': 'WhoLiveSInCalifornia2000!', + 'other': {'age': 15, 'country': 'us'}, + } + resp_user2 = cls.client.post( + cls.user_signup_url, + user2_payload, + format='json', + ) + assert resp_user2.status_code == rest_framework.status.HTTP_200_OK, ( + 'User2 signup failed' + ) + cls.user2_token = resp_user2.data['access'] + + user3_payload = { + 'name': 'Yefim', + 'surname': 'Dinitz', + 'email': 'algo@prog.lu', + 'password': 'HardPASSword1!', + 'other': {'age': 40, 'country': 'lu'}, + } + resp_user3 = cls.client.post( + cls.user_signup_url, + user3_payload, + format='json', + ) + assert resp_user3.status_code == rest_framework.status.HTTP_200_OK, ( + 'User3 signup failed' + ) + cls.user3_token = resp_user3.data['access'] + + cls.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + cls.company1_token, + ) + promo1_data = { + 'description': 'Active COMMON promo for all users', + 'target': {}, + 'max_count': 10, + 'active_from': '2025-01-10', + 'mode': 'COMMON', + 'promo_common': 'sale-10', + } + resp_promo1 = cls.client.post( + cls.promo_list_create_url, + promo1_data, + format='json', + ) + assert ( + resp_promo1.status_code == rest_framework.status.HTTP_201_CREATED + ), f'Promo1 creation failed: {resp_promo1.data}' + cls.promo1_id = resp_promo1.data['id'] + cls.client.credentials() + + def test_01_get_promo1_by_user1_initial_state(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['promo_id'], self.promo1_id) + self.assertEqual(response.data['like_count'], 0) + self.assertEqual(response.data['is_liked_by_user'], False) + self.client.credentials() + + def test_02_get_promo1_by_company1_initial_state(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.company1_token, + ) + response = self.client.get( + self.get_business_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['promo_id'], self.promo1_id) + self.assertEqual(response.data['like_count'], 0) + self.client.credentials() + + def test_03_user1_likes_promo1(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + response = self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_04_user1_likes_promo1_again_idempotency(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + url = self.get_user_promo_like_url(self.promo1_id) + self.client.post(url, format='json') + + response = self.client.post(url, format='json') + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_05_get_promo1_by_user1_after_like(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + like_response = self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + like_response.status_code, + rest_framework.status.HTTP_200_OK, + ) + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['promo_id'], self.promo1_id) + self.assertEqual(response.data['like_count'], 1) + self.assertEqual(response.data['is_liked_by_user'], True) + self.client.credentials() + + def test_06_get_promo1_by_user2_before_own_like(self): + original_user_client = rest_framework.test.APIClient() + original_user_client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + original_user_client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['like_count'], 1) + self.assertEqual( + response.data['is_liked_by_user'], + False, + ) + self.client.credentials() + + def test_07_user2_likes_promo1(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + response = self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_08_get_promo1_by_user2_after_own_like(self): + temp_client_user1 = rest_framework.test.APIClient() + temp_client_user1.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + temp_client_user1.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + like_response = self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + like_response.status_code, + rest_framework.status.HTTP_200_OK, + ) + + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual( + response.data['like_count'], + 2, + ) + self.assertEqual(response.data['is_liked_by_user'], True) + self.client.credentials() + + def test_09_user3_deletes_non_existent_like_for_promo1_idempotency(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user3_token, + ) + response = self.client.delete( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_10_get_promo1_by_user3_no_like_state(self): + temp_client_user1 = rest_framework.test.APIClient() + temp_client_user1.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + temp_client_user1.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + temp_client_user2 = rest_framework.test.APIClient() + temp_client_user2.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + temp_client_user2.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user3_token, + ) + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual( + response.data['like_count'], + 2, + ) + self.assertEqual(response.data['is_liked_by_user'], False) + self.client.credentials() + + def test_11_get_promo1_by_company1_after_all_likes(self): + temp_client_user1 = rest_framework.test.APIClient() + temp_client_user1.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + temp_client_user1.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + temp_client_user2 = rest_framework.test.APIClient() + temp_client_user2.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + temp_client_user2.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.company1_token, + ) + response = self.client.get( + self.get_business_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['like_count'], 2) + self.client.credentials() + + def test_12_user1_unlikes_promo1(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + response = self.client.delete( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_13_get_promo1_by_user1_after_unlike(self): + temp_client_user2 = rest_framework.test.APIClient() + temp_client_user2.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + temp_client_user2.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + + self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.client.delete( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual( + response.data['like_count'], + 1, + ) + self.assertEqual( + response.data['is_liked_by_user'], + False, + ) + self.client.credentials() + + def test_14_user1_unlikes_promo1_again_idempotency(self): + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + self.client.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.client.delete( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + response = self.client.delete( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data, {'status': 'ok'}) + self.client.credentials() + + def test_15_get_promo1_by_user1_final_state(self): + temp_client_user2 = rest_framework.test.APIClient() + temp_client_user2.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user2_token, + ) + temp_client_user2.post( + self.get_user_promo_like_url(self.promo1_id), + format='json', + ) + + self.client.credentials( + HTTP_AUTHORIZATION='Bearer ' + self.user1_token, + ) + + response = self.client.get( + self.get_user_promo_detail_url(self.promo1_id), + format='json', + ) + self.assertEqual( + response.status_code, + rest_framework.status.HTTP_200_OK, + ) + self.assertEqual(response.data['like_count'], 1) + self.assertEqual(response.data['is_liked_by_user'], False) + self.client.credentials()