first test pass

This commit is contained in:
Sayyid Hamid Mahdavi 2025-11-06 14:11:00 +03:30
parent c6ad3cb82d
commit 450aadd6da
6 changed files with 173 additions and 28 deletions

View file

@ -0,0 +1,29 @@
# Generated by Django 5.1.7 on 2025-11-06 08:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('promotions', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='promotion',
name='event',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.event'),
),
migrations.AlterField(
model_name='plan',
name='processor',
field=models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='processor'),
),
migrations.AlterField(
model_name='recipient',
name='promotion_type',
field=models.CharField(choices=[], db_index=True, max_length=64, verbose_name='promotion type'),
),
]

View file

@ -45,6 +45,7 @@ class EventSaver(BaseModel):
if created:
return event
else:
# TODO: good exception
raise Exception(_('already saved'))
else:
@ -104,11 +105,11 @@ class Plan(BaseModel):
def process_event(self, event=None):
# TODO: processor
self.promote(event=event)
return self.promote_all(event=event)
def promote(self, event=None):
def promote_all(self, event=None):
for recipient in self.recipients.all():
recipient.promote(plan=self, event=event)
yield recipient.promote(plan=self, event=event)
def reserve_promotion_amount(self, amount):
updated = False
@ -139,14 +140,13 @@ class Recipient(BaseModel):
def get_recipient_uuid(self, plan=None, event=None):
try:
model_name, key = self.recipient_uuid_field.split('::')
model_name, key = self.recipient_uuid_field.split(':')
if model_name == 'event':
obj = event
elif model_name == 'plan':
obj = plan
else:
raise NotImplement
edError()
raise NotImplementedError()
return obj.data.get(key)
except Exception as e:
@ -160,7 +160,7 @@ class Recipient(BaseModel):
# TODO: get base amount from policy_choice
base_amount = 0
try:
model_name, key = self.base_amount_field.split('::')
model_name, key = self.base_amount_field.split(':')
if model_name == 'event':
obj = event
elif model_name == 'plan':
@ -188,7 +188,8 @@ class Recipient(BaseModel):
if reserved:
promotion, created = Promotion.objects.get_or_create(
promotion_amount=promotion_amount,
user_id=self.get_recipient_uuid(plan, event),
user_uuid=self.get_recipient_uuid(plan, event),
event=event,
plan=plan,
recipient=self,
base_amount=promotion_amount,
@ -196,9 +197,11 @@ class Recipient(BaseModel):
if created:
promotion.promote()
else:
raise
raise Exception('promotion object not created')
else:
raise
raise Exception('promotion reward not reserved')
return promotion
class PromotionQuerySet(models.QuerySet):
@ -217,6 +220,7 @@ class Promotion(BaseModel):
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
related_name='+', null=True, blank=True)
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
event = models.ForeignKey(Event, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
recipient = models.ForeignKey(Recipient, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
base_amount = models.IntegerField(null=True, blank=True)
@ -250,7 +254,7 @@ class Promotion(BaseModel):
self.change_state(from_states=[Promotion.StateChoices.CREATED], to_state=Promotion.StateChoices.ASSIGNED,
same_ok=False, raise_exception=True)
payee_uuid = self.user_id
payee_uuid = self.user_uuid
data = {
"uuid": str(self.uuid),
@ -274,7 +278,7 @@ class Promotion(BaseModel):
verify_response = deposit_to_user_wallet_verify(str(self.uuid))
logger.debug(f'verify response: {verify_response}')
try:
if verify_response.state != 5:
if verify_response.state == 5:
self.change_state(from_states=[Promotion.StateChoices.ASSIGNED],
to_state=Promotion.StateChoices.PROMOTED)
else:

View file

@ -4,19 +4,19 @@ from .models import Promotion, Plan, Event
class PromotionSerializer(serializers.ModelSerializer):
base_amount = serializers.IntegerField(required=True)
promotion_amount = serializers.IntegerField(read_only=True)
# base_amount = serializers.IntegerField(required=True)
# promotion_amount = serializers.IntegerField(read_only=True)
user = serializers.UUIDField(source='user_uuid')
class Meta:
model = Promotion
fields = (
"uuid",
"base_amount",
"promotion_amount",
"plan",
"user_uuid",
"application",
"state",
"user",
)
read_only_fields = ('user_uuid', 'application', "promotion_amount",)
class EventSerializer(serializers.ModelSerializer):
@ -58,3 +58,19 @@ class PlanPromotSerializer(serializers.ModelSerializer):
"application",
)
read_only_fields = ('user', 'application')
class PromoteSerializer(serializers.ModelSerializer):
label = serializers.CharField(write_only=True)
data = serializers.JSONField(write_only=True)
promotions = PromotionSerializer(many=True, read_only=True)
class Meta:
model = Promotion
fields = (
"promotions",
"label",
"data",
)

View file

@ -1,5 +1,6 @@
import uuid
from datetime import timedelta
from unittest.mock import patch
from django.urls import reverse
from django.utils import timezone
@ -15,6 +16,49 @@ Application = get_application_model()
application_uuid_hex = 'b14e8b86-8f4a-44d9-b29d-badceb470053'
def mock_submit_deposit_success(data):
import uuid as sys_uuid
class Tmp():
uuid = sys_uuid.uuid4()
data = Tmp()
return data
def mock_verify_deposit_success(uuid):
import uuid as sys_uuid
class Tmp():
uuid = sys_uuid.uuid4()
state = 5
data = Tmp()
return data
def mock_submit_withdraw_success(data):
import uuid as sys_uuid
class Tmp():
uuid = sys_uuid.uuid4()
data = Tmp()
return data
def mock_verify_withdraw_success(uuid):
import uuid as sys_uuid
class Tmp():
uuid = sys_uuid.uuid4()
state = 5
data = Tmp()
return data
class ApplicationApiFlowsTests(APITestCase):
client_id = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
application_uuid = uuid.UUID(application_uuid_hex)
@ -33,6 +77,21 @@ class ApplicationApiFlowsTests(APITestCase):
]
def setUp(self):
self.submit_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_submit',
mock_submit_deposit_success)
self.verify_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_verify',
mock_verify_deposit_success)
# self.submit_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_submit',
# mock_submit_withdraw_success)
# self.verify_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_verify',
# mock_verify_withdraw_success)
self.submit_deposit_success_patcher.start()
self.verify_deposit_success_patcher.start()
# self.submit_withdraw_success_patcher.start()
# self.verify_withdraw_success_patcher.start()
self.user, _ = User.objects.get_or_create(pk=self.user_uuid)
@ -86,8 +145,8 @@ class ApplicationApiFlowsTests(APITestCase):
label = self.first_ad_view_reward_label,
plan = self.first_ad_view_reward_plan,
wallet_uuid = self.user_uuid,
recipient_uuid_field="",
base_amount_field="",
recipient_uuid_field="event:user",
base_amount_field="event:base_amount",
)
@ -118,14 +177,18 @@ class ApplicationApiFlowsTests(APITestCase):
'label': self.first_ad_view_reward_label,
"data": {
'user': self.user_uuid,
'ad': self.user_uuid
'base_amount':800
}
}
response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
# response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
response = self.client.post(reverse('promotions:promote', kwargs={"plan":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
print(response.json())
# event = Event.objects.get(user=self.user, label=self.first_ad_view_reward_label)
# event.analyze()
# print(event)
self.first_ad_view_reward_plan.refresh_from_db()
self.assertEqual(self.first_ad_view_reward_plan.balance, 10000 - 800)
response = self.client.post(reverse('promotions:promote', kwargs={"plan": self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
print(response.json())
return
def test_referral_reward_success(self):

View file

@ -5,6 +5,8 @@ from utils.router import ProfileRouter
from . import views
from django.urls import NoReverseMatch, path, re_path, include
from .views import ApplicationPromoteUserApiView
app_name = 'promotions'
router = DefaultRouter()
@ -16,4 +18,5 @@ router.register(r'plans', views.UserViewSet, basename='user')
urlpatterns = [
path('api/v1/', include(router.urls), name='events'),
path('api/v1/promote/<plan>/', ApplicationPromoteUserApiView.as_view(), name='promote'),
]

View file

@ -1,13 +1,15 @@
from drf_spectacular.utils import extend_schema
from rest_framework import mixins
from rest_framework import mixins, status
from rest_framework.decorators import action
from rest_framework import exceptions
from rest_framework.generics import CreateAPIView, get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
from apps.gooyal_oauth2.utils import get_application
from .models import Plan, Promotion, EventSaver
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer, PromoteSerializer
from .tasks import analyze_event_task
from ..users.models import User
@ -145,8 +147,36 @@ class UserViewSet(mixins.RetrieveModelMixin,
raise exceptions.ValidationError(str(e))
plan: Plan = self.get_object()
plan.process_event(event)
return
class ApplicationPromoteUserApiView(CreateAPIView):
model = Promotion
serializer_class = PromoteSerializer
def get_plan(self):
plan_uuid = self.kwargs.get('plan')
plan = get_object_or_404(Plan, uuid=plan_uuid)
return plan
def create(self, request, *args, **kwargs):
plan = self.get_plan()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
application = get_application(self.request)
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
# try:
event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
# except Exception as e:
# raise exceptions.ValidationError(str(e))
result_list = plan.process_event(event)
data["promotions"] = result_list
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)