From eed8c11e63fad7f340b07da7d36a81eb6435a6a5 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Fri, 7 Jun 2024 15:31:22 +0200 Subject: [PATCH 01/10] Notifications Endpoint --- api/management/commands/telegram_watcher.py | 10 +- api/migrations/0047_notification.py | 26 +++ api/models/__init__.py | 11 +- api/models/notification.py | 35 ++++ api/notifications.py | 189 +++++++++++--------- api/oas_schemas.py | 15 ++ api/serializers.py | 8 +- api/tasks.py | 32 ++-- api/urls.py | 2 + api/views.py | 44 ++++- docs/assets/schemas/api-latest.yaml | 38 ++++ tests/test_trade_pipeline.py | 142 +++++++++++++++ 12 files changed, 439 insertions(+), 113 deletions(-) create mode 100644 api/migrations/0047_notification.py create mode 100644 api/models/notification.py diff --git a/api/management/commands/telegram_watcher.py b/api/management/commands/telegram_watcher.py index 40e3cb4cb..44ce5c69e 100644 --- a/api/management/commands/telegram_watcher.py +++ b/api/management/commands/telegram_watcher.py @@ -6,7 +6,7 @@ from django.db import transaction from api.models import Robot -from api.notifications import Telegram +from api.notifications import Notifications from api.utils import get_session @@ -17,7 +17,7 @@ class Command(BaseCommand): bot_token = config("TELEGRAM_TOKEN") updates_url = f"https://api.telegram.org/bot{bot_token}/getUpdates" session = get_session() - telegram = Telegram() + notifications = Notifications() def handle(self, *args, **options): offset = 0 @@ -49,7 +49,7 @@ def handle(self, *args, **options): continue parts = message.split(" ") if len(parts) < 2: - self.telegram.send_message( + self.notifications.send_telegram_message( chat_id=result["message"]["from"]["id"], text='You must enable the notifications bot using the RoboSats client. Click on your "Robot robot" -> "Enable Telegram" and follow the link or scan the QR code.', ) @@ -57,7 +57,7 @@ def handle(self, *args, **options): token = parts[-1] robot = Robot.objects.filter(telegram_token=token).first() if not robot: - self.telegram.send_message( + self.notifications.send_telegram_message( chat_id=result["message"]["from"]["id"], text=f'Wops, invalid token! There is no Robot with telegram chat token "{token}"', ) @@ -71,7 +71,7 @@ def handle(self, *args, **options): robot.telegram_lang_code = result["message"]["from"][ "language_code" ] - self.telegram.welcome(robot.user) + self.notifications.welcome(robot.user) robot.telegram_enabled = True robot.save( update_fields=[ diff --git a/api/migrations/0047_notification.py b/api/migrations/0047_notification.py new file mode 100644 index 000000000..24ce5c92e --- /dev/null +++ b/api/migrations/0047_notification.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.6 on 2024-06-14 18:31 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0046_alter_currency_currency'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('title', models.CharField(default=None, max_length=240)), + ('description', models.CharField(blank=True, default=None, max_length=240)), + ('order', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='api.order')), + ('robot', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='api.robot')), + ], + ), + ] diff --git a/api/models/__init__.py b/api/models/__init__.py index d29ac0e45..645a7fac7 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -4,5 +4,14 @@ from .onchain_payment import OnchainPayment from .order import Order from .robot import Robot +from .notification import Notification -__all__ = ["Currency", "LNPayment", "MarketTick", "OnchainPayment", "Order", "Robot"] +__all__ = [ + "Currency", + "LNPayment", + "MarketTick", + "OnchainPayment", + "Order", + "Robot", + "Notification", +] diff --git a/api/models/notification.py b/api/models/notification.py new file mode 100644 index 000000000..790b98dc4 --- /dev/null +++ b/api/models/notification.py @@ -0,0 +1,35 @@ +# We use custom seeded UUID generation during testing +import uuid + +from decouple import config +from api.models import Order, Robot +from django.db import models +from django.utils import timezone + +if config("TESTING", cast=bool, default=False): + import random + import string + + random.seed(1) + chars = string.ascii_lowercase + string.digits + + def custom_uuid(): + return uuid.uuid5(uuid.NAMESPACE_DNS, "".join(random.choices(chars, k=20))) + +else: + custom_uuid = uuid.uuid4 + + +class Notification(models.Model): + # notification info + created_at = models.DateTimeField(default=timezone.now) + + robot = models.ForeignKey(Robot, on_delete=models.CASCADE, default=None) + order = models.ForeignKey(Order, on_delete=models.CASCADE, default=None) + + # notification details + title = models.CharField(max_length=240, null=False, default=None) + description = models.CharField(max_length=240, default=None, blank=True) + + def __str__(self): + return f"{self.title} {self.description}" diff --git a/api/notifications.py b/api/notifications.py index 023191978..847ce0c76 100644 --- a/api/notifications.py +++ b/api/notifications.py @@ -1,12 +1,14 @@ from secrets import token_urlsafe from decouple import config - -from api.models import Order +from api.models import ( + Order, + Notification, +) from api.utils import get_session -class Telegram: +class Notifications: """Simple telegram messages using TG's API""" session = get_session() @@ -29,13 +31,24 @@ def get_context(user): return context - def send_message(self, chat_id, text): + def send_message(self, order, robot, title, description=""): + """Save a message for a user and sends it to Telegram""" + self.save_message(order, robot, title, description) + if robot.telegram_enabled: + self.send_telegram_message(robot.telegram_chat_id, title, description) + + def save_message(self, order, robot, title, description): + """Save a message for a user""" + Notification.objects.create( + title=title, description=description, robot=robot, order=order + ) + + def send_telegram_message(self, chat_id, title, description): """sends a message to a user with telegram notifications enabled""" bot_token = config("TELEGRAM_TOKEN") - + text = f"{title} {description}" message_url = f"https://api.telegram.org/bot{bot_token}/sendMessage?chat_id={chat_id}&text={text}" - # if it fails, it should keep trying while True: try: @@ -49,119 +62,127 @@ def welcome(self, user): lang = user.robot.telegram_lang_code if lang == "es": - text = f"🔔 Hola {user.username}, te enviaré notificaciones sobre tus órdenes en RoboSats." + title = f"🔔 Hola {user.username}, te enviaré notificaciones sobre tus órdenes en RoboSats." else: - text = f"🔔 Hey {user.username}, I will send you notifications about your RoboSats orders." - self.send_message(user.robot.telegram_chat_id, text) + title = f"🔔 Hey {user.username}, I will send you notifications about your RoboSats orders." + self.send_telegram_message(user.robot.telegram_chat_id, title) user.robot.telegram_welcomed = True user.robot.save(update_fields=["telegram_welcomed"]) return def order_taken_confirmed(self, order): - if order.maker.robot.telegram_enabled: - lang = order.maker.robot.telegram_lang_code - if lang == "es": - text = f"✅ Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {order.taker.username}!🥳 Visita http://{self.site}/order/{order.id} para continuar." - else: - text = f"✅ Hey {order.maker.username}, your order was taken by {order.taker.username}!🥳 Visit http://{self.site}/order/{order.id} to proceed with the trade." - self.send_message(order.maker.robot.telegram_chat_id, text) + lang = order.maker.robot.telegram_lang_code + if lang == "es": + title = f"✅ Hey {order.maker.username} ¡Tu orden con ID {order.id} ha sido tomada por {order.taker.username}!🥳" + description = f"Visita http://{self.site}/order/{order.id} para continuar." + else: + title = f"✅ Hey {order.maker.username}, your order was taken by {order.taker.username}!🥳" + description = ( + f"Visit http://{self.site}/order/{order.id} to proceed with the trade." + ) + self.send_message(order, order.maker.robot, title, description) - if order.taker.robot.telegram_enabled: - lang = order.taker.robot.telegram_lang_code - if lang == "es": - text = f"✅ Hey {order.taker.username}, acabas de tomar la orden con ID {order.id}." - else: - text = f"✅ Hey {order.taker.username}, you just took the order with ID {order.id}." - self.send_message(order.taker.robot.telegram_chat_id, text) + lang = order.taker.robot.telegram_lang_code + if lang == "es": + title = f"✅ Hey {order.taker.username}, acabas de tomar la orden con ID {order.id}." + else: + title = f"✅ Hey {order.taker.username}, you just took the order with ID {order.id}." + self.send_message(order, order.taker.robot, title) return def fiat_exchange_starts(self, order): for user in [order.maker, order.taker]: - if user.robot.telegram_enabled: - lang = user.robot.telegram_lang_code - if lang == "es": - text = f"✅ Hey {user.username}, el depósito de garantía y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat. Visita http://{self.site}/order/{order.id} para hablar con tu contraparte." - else: - text = f"✅ Hey {user.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat. Visit http://{self.site}/order/{order.id} to talk with your counterpart." - self.send_message(user.robot.telegram_chat_id, text) + lang = user.robot.telegram_lang_code + if lang == "es": + title = f"✅ Hey {user.username}, el depósito de garantía y el recibo del comprador han sido recibidos. Es hora de enviar el dinero fiat." + description = f"Visita http://{self.site}/order/{order.id} para hablar con tu contraparte." + else: + title = f"✅ Hey {user.username}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat." + description = f"Visit http://{self.site}/order/{order.id} to talk with your counterpart." + self.send_message(order, user.robot, title, description) return def order_expired_untaken(self, order): - if order.maker.robot.telegram_enabled: - lang = order.maker.robot.telegram_lang_code - if lang == "es": - text = f"😪 Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot. Visita http://{self.site}/order/{order.id} para renovarla." - else: - text = f"😪 Hey {order.maker.username}, your order with ID {order.id} has expired without a taker. Visit http://{self.site}/order/{order.id} to renew it." - self.send_message(order.maker.robot.telegram_chat_id, text) + lang = order.maker.robot.telegram_lang_code + if lang == "es": + title = f"😪 Hey {order.maker.username}, tu orden con ID {order.id} ha expirado sin ser tomada por ningún robot." + description = f"Visita http://{self.site}/order/{order.id} para renovarla." + else: + title = f"😪 Hey {order.maker.username}, your order with ID {order.id} has expired without a taker." + description = f"Visit http://{self.site}/order/{order.id} to renew it." + self.send_message(order, order.maker.robot, title, description) return def trade_successful(self, order): for user in [order.maker, order.taker]: - if user.robot.telegram_enabled: - lang = user.robot.telegram_lang_code - if lang == "es": - text = f"🥳 ¡Tu orden con ID {order.id} ha finalizado exitosamente!⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar." - else: - text = f"🥳 Your order with ID {order.id} has finished successfully!⚡ Join us @robosats and help us improve." - self.send_message(user.robot.telegram_chat_id, text) + lang = user.robot.telegram_lang_code + if lang == "es": + title = f"🥳 ¡Tu orden con ID {order.id} ha finalizado exitosamente!" + description = ( + "⚡ Únete a nosotros en @robosats_es y ayúdanos a mejorar." + ) + else: + title = f"🥳 Your order with ID {order.id} has finished successfully!" + description = "⚡ Join us @robosats and help us improve." + self.send_message(order, user.robot, title, description) return def public_order_cancelled(self, order): - if order.maker.robot.telegram_enabled: - lang = order.maker.robot.telegram_lang_code - if lang == "es": - text = f"❌ Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}." - else: - text = f"❌ Hey {order.maker.username}, you have cancelled your public order with ID {order.id}." - self.send_message(order.maker.robot.telegram_chat_id, text) + lang = order.maker.robot.telegram_lang_code + if lang == "es": + title = f"❌ Hey {order.maker.username}, has cancelado tu orden pública con ID {order.id}." + else: + title = f"❌ Hey {order.maker.username}, you have cancelled your public order with ID {order.id}." + self.send_message(order, order.maker.robot, title) return def collaborative_cancelled(self, order): for user in [order.maker, order.taker]: - if user.robot.telegram_enabled: - lang = user.robot.telegram_lang_code - if lang == "es": - text = f"❌ Hey {user.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente." - else: - text = f"❌ Hey {user.username}, your order with ID {str(order.id)} has been collaboratively cancelled." - self.send_message(user.robot.telegram_chat_id, text) + lang = user.robot.telegram_lang_code + if lang == "es": + title = f"❌ Hey {user.username}, tu orden con ID {str(order.id)} fue cancelada colaborativamente." + else: + title = f"❌ Hey {user.username}, your order with ID {str(order.id)} has been collaboratively cancelled." + self.send_message(order, user.robot, title) return def dispute_opened(self, order): for user in [order.maker, order.taker]: - if user.robot.telegram_enabled: - lang = user.robot.telegram_lang_code - if lang == "es": - text = f"⚖️ Hey {user.username}, la orden con ID {str(order.id)} ha entrado en disputa." - else: - text = f"⚖️ Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}." - self.send_message(user.robot.telegram_chat_id, text) + lang = user.robot.telegram_lang_code + if lang == "es": + title = f"⚖️ Hey {user.username}, la orden con ID {str(order.id)} ha entrado en disputa." + else: + title = f"⚖️ Hey {user.username}, a dispute has been opened on your order with ID {str(order.id)}." + self.send_message(order, user.robot, title) admin_chat_id = config("TELEGRAM_COORDINATOR_CHAT_ID") if len(admin_chat_id) == 0: return - coordinator_text = f"There is a new dispute opened for the order with ID {str(order.id)}. Visit http://{self.site}/coordinator/api/order/{str(order.id)}/change to proceed." - self.send_message(admin_chat_id, coordinator_text) + coordinator_text = ( + f"There is a new dispute opened for the order with ID {str(order.id)}." + ) + coordinator_description = f"Visit http://{self.site}/coordinator/api/order/{str(order.id)}/change to proceed." + self.send_telegram_message( + admin_chat_id, coordinator_text, coordinator_description + ) return def order_published(self, order): - if order.maker.robot.telegram_enabled: - lang = order.maker.robot.telegram_lang_code - # In weird cases the order cannot be found (e.g. it is cancelled) - queryset = Order.objects.filter(maker=order.maker) - if len(queryset) == 0: - return - order = queryset.last() - if lang == "es": - text = f"✅ Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes." - else: - text = f"✅ Hey {order.maker.username}, your order with ID {str(order.id)} is public in the order book." - self.send_message(order.maker.robot.telegram_chat_id, text) + lang = order.maker.robot.telegram_lang_code + # In weird cases the order cannot be found (e.g. it is cancelled) + queryset = Order.objects.filter(maker=order.maker) + if len(queryset) == 0: + return + order = queryset.last() + if lang == "es": + title = f"✅ Hey {order.maker.username}, tu orden con ID {str(order.id)} es pública en el libro de ordenes." + else: + title = f"✅ Hey {order.maker.username}, your order with ID {str(order.id)} is public in the order book." + self.send_message(order, order.maker.robot, title) return def new_chat_message(self, order, chat_message): @@ -189,14 +210,12 @@ def new_chat_message(self, order, chat_message): notification_reason = f"(You receive this notification because this was the first in-chat message. You will only be notified again if there is a gap bigger than {TIMEGAP} minutes between messages)" user = chat_message.receiver - if user.robot.telegram_enabled: - text = f"💬 Hey {user.username}, a new chat message in-app was sent to you by {chat_message.sender.username} for order ID {str(order.id)}. {notification_reason}" - self.send_message(user.robot.telegram_chat_id, text) + title = f"💬 Hey {user.username}, a new chat message in-app was sent to you by {chat_message.sender.username} for order ID {str(order.id)}. {notification_reason}" + self.send_message(order, user.robot, title) return def coordinator_cancelled(self, order): - if order.maker.robot.telegram_enabled: - text = f"🛠️ Your order with ID {order.id} has been cancelled by the coordinator {config('COORDINATOR_ALIAS', cast=str, default='NoAlias')} for the upcoming maintenance stop." - self.send_message(order.maker.robot.telegram_chat_id, text) + title = f"🛠️ Your order with ID {order.id} has been cancelled by the coordinator {config('COORDINATOR_ALIAS', cast=str, default='NoAlias')} for the upcoming maintenance stop." + self.send_message(order, order.maker.robot, title) return diff --git a/api/oas_schemas.py b/api/oas_schemas.py index 8b6412e66..220f604dc 100644 --- a/api/oas_schemas.py +++ b/api/oas_schemas.py @@ -378,6 +378,21 @@ class BookViewSchema: } +class NotificationSchema: + get = { + "summary": "Get robot notifications", + "description": "Get a list of notifications sent to the robot.", + "parameters": [ + OpenApiParameter( + name="created_at", + location=OpenApiParameter.QUERY, + description=("Shows notifications created AFTER this date."), + type=str, + ), + ], + } + + class RobotViewSchema: get = { "summary": "Get robot info", diff --git a/api/serializers.py b/api/serializers.py index 4bd41fd24..2f7b2310f 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -2,7 +2,7 @@ from decimal import Decimal from rest_framework import serializers -from .models import MarketTick, Order +from .models import MarketTick, Order, Notification RETRY_TIME = int(config("RETRY_TIME")) @@ -490,6 +490,12 @@ class Meta: ) +class ListNotificationSerializer(serializers.ModelSerializer): + class Meta: + model = Notification + fields = ("title", "description", "order_id") + + class OrderPublicSerializer(serializers.ModelSerializer): maker_nick = serializers.CharField(required=False) maker_hash_id = serializers.CharField(required=False) diff --git a/api/tasks.py b/api/tasks.py index dee5709ad..88e924a86 100644 --- a/api/tasks.py +++ b/api/tasks.py @@ -263,48 +263,44 @@ def send_notification(order_id=None, chat_message_id=None, message=None): chat_message = Message.objects.get(id=chat_message_id) order = chat_message.order - taker_enabled = False if order.taker is None else order.taker.robot.telegram_enabled - if not (order.maker.robot.telegram_enabled or taker_enabled): - return - - from api.notifications import Telegram + from api.notifications import Notifications - telegram = Telegram() + notifications = Notifications() if message == "welcome": - telegram.welcome(order) + notifications.welcome(order) elif message == "order_expired_untaken": - telegram.order_expired_untaken(order) + notifications.order_expired_untaken(order) elif message == "trade_successful": - telegram.trade_successful(order) + notifications.trade_successful(order) elif message == "public_order_cancelled": - telegram.public_order_cancelled(order) + notifications.public_order_cancelled(order) elif message == "taker_expired_b4bond": - telegram.taker_expired_b4bond(order) + notifications.taker_expired_b4bond(order) elif message == "order_published": - telegram.order_published(order) + notifications.order_published(order) elif message == "order_taken_confirmed": - telegram.order_taken_confirmed(order) + notifications.order_taken_confirmed(order) elif message == "fiat_exchange_starts": - telegram.fiat_exchange_starts(order) + notifications.fiat_exchange_starts(order) elif message == "dispute_opened": - telegram.dispute_opened(order) + notifications.dispute_opened(order) elif message == "collaborative_cancelled": - telegram.collaborative_cancelled(order) + notifications.collaborative_cancelled(order) elif message == "new_chat_message": - telegram.new_chat_message(order, chat_message) + notifications.new_chat_message(order, chat_message) elif message == "coordinator_cancelled": - telegram.coordinator_cancelled(order) + notifications.coordinator_cancelled(order) return diff --git a/api/urls.py b/api/urls.py index 7d8b19c68..7264c1e68 100644 --- a/api/urls.py +++ b/api/urls.py @@ -15,6 +15,7 @@ RobotView, StealthView, TickView, + NotificationsView, ) urlpatterns = [ @@ -36,4 +37,5 @@ path("ticks/", TickView.as_view(), name="ticks"), path("stealth/", StealthView.as_view(), name="stealth"), path("chat/", ChatView.as_view({"get": "get", "post": "post"}), name="chat"), + path("notifications/", NotificationsView.as_view(), name="notifications"), ] diff --git a/api/views.py b/api/views.py index 49a21a2c6..2120c6357 100644 --- a/api/views.py +++ b/api/views.py @@ -14,8 +14,15 @@ from rest_framework.views import APIView from api.logics import Logics -from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order -from api.notifications import Telegram +from api.models import ( + Currency, + LNPayment, + MarketTick, + OnchainPayment, + Order, + Notification, +) +from api.notifications import Notifications from api.oas_schemas import ( BookViewSchema, HistoricalViewSchema, @@ -28,6 +35,7 @@ RobotViewSchema, StealthViewSchema, TickViewSchema, + NotificationSchema, ) from api.serializers import ( ClaimRewardSerializer, @@ -39,6 +47,7 @@ StealthSerializer, TickSerializer, UpdateOrderSerializer, + ListNotificationSerializer, ) from api.utils import ( compute_avg_premium, @@ -659,7 +668,7 @@ def get(self, request, format=None): context["last_login"] = user.last_login # Adds/generate telegram token and whether it is enabled - context = {**context, **Telegram.get_context(user)} + context = {**context, **Notifications.get_context(user)} # return active order or last made order if any has_no_active_order, _, order = Logics.validate_already_maker_or_taker( @@ -730,6 +739,35 @@ def get(self, request, format=None): return Response(book_data, status=status.HTTP_200_OK) +class NotificationsView(ListAPIView): + authentication_classes = [TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = ListNotificationSerializer + + @extend_schema(**NotificationSchema.get) + def get(self, request, format=None): + # robot = request.user.robot + queryset = Notification.objects.all().order_by("created_at") + # created_at = request.GET.get("created_at") + + # if created_at: + # created_at = parse_datetime(created_at) + # if not created_at: + # return HttpResponseBadRequest("Invalid date format") + # queryset = queryset.filter(created_at__gte=created_at) + + notification_data = [] + for notification in queryset: + data = self.serializer_class(notification).data + data["title"] = str(notification.title) + data["description"] = str(notification.description) + data["order_id"] = notification.order.id + + notification_data.append(data) + + return Response(notification_data, status=status.HTTP_200_OK) + + class InfoView(viewsets.ViewSet): serializer_class = InfoSerializer diff --git a/docs/assets/schemas/api-latest.yaml b/docs/assets/schemas/api-latest.yaml index 677350290..8047a0e25 100644 --- a/docs/assets/schemas/api-latest.yaml +++ b/docs/assets/schemas/api-latest.yaml @@ -284,6 +284,30 @@ paths: type: string description: Reason for the failure description: '' + /api/notifications/: + get: + operationId: notifications_list + description: Get a list of notifications sent to the robot. + summary: Get robot notifications + parameters: + - in: query + name: created_at + schema: + type: string + description: Shows notifications created AFTER this date. + tags: + - notifications + security: + - tokenAuth: [] + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ListNotification' + description: '' /api/order/: get: operationId: order_retrieve @@ -1070,6 +1094,20 @@ components: - swap_enabled - taker_fee - version + ListNotification: + type: object + properties: + title: + type: string + maxLength: 240 + description: + type: string + maxLength: 240 + order_id: + type: integer + readOnly: true + required: + - order_id ListOrder: type: object properties: diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 32a39c9e8..07cfd9d57 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -47,6 +47,14 @@ def setUpTestData(cls): # Take the first node balances snapshot compute_node_balance() + def get_notifications(self, headers): + """ + Get robot notifications + """ + response = self.client.get(reverse("notifications"), **headers) + self.assertResponse(response) + list(response.json()) + def assert_order_logs(self, order_id): order = Order.objects.get(id=order_id) order_admin = OrderAdmin(model=Order, admin_site=AdminSite()) @@ -239,6 +247,14 @@ def test_make_order(self): self.assertIsNone(data["taker"], "New order's taker is not null") self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 0, + "User has no notification", + ) + def test_make_order_on_blocked_country(self): """ Test the creation of an F2F order on a geoblocked location @@ -347,6 +363,15 @@ def test_publish_order(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 1, + "User has a new order notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_pause_unpause_order(self): """ Tests pausing and unpausing a public order @@ -369,6 +394,10 @@ def test_pause_unpause_order(self): self.assertResponse(trade.response) self.assertEqual(data["status_message"], Order.Status(Order.Status.PUB).label) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -415,6 +444,10 @@ def test_make_and_take_order(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_make_and_lock_contract(self): """ Tests a trade from order creation to taker bond locked. @@ -437,6 +470,16 @@ def test_make_and_lock_contract(self): self.assertTrue(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + notifications_data = list(trade.response.json()) + self.assertEqual( + len(notifications_data), + 3, + "User has a bond locked notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Maker GET trade.get_order(trade.maker_index) data = trade.response.json() @@ -457,6 +500,15 @@ def test_make_and_lock_contract(self): self.assertTrue(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 2, + "User has a bond locked notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Maker cancels order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -483,6 +535,15 @@ def test_trade_to_locked_escrow(self): self.assertTrue(data["taker_locked"]) self.assertTrue(data["escrow_locked"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 3, + "User has a scrow locked notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.taker_index) @@ -506,6 +567,15 @@ def test_trade_to_submitted_address(self): self.assertEqual(data["status_message"], Order.Status(Order.Status.CHA).label) self.assertFalse(data["is_fiat_sent"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 4, + "User has a new order ready notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) @@ -532,6 +602,15 @@ def test_trade_to_submitted_invoice(self): self.assertEqual(data["status_message"], Order.Status(Order.Status.CHA).label) self.assertFalse(data["is_fiat_sent"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 4, + "User has a new order ready notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) @@ -556,6 +635,15 @@ def test_trade_to_confirm_fiat_sent_LN(self): self.assertEqual(data["status_message"], Order.Status(Order.Status.FSE).label) self.assertTrue(data["is_fiat_sent"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 6, + "User has a new fiat sent notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.undo_confirm_sent(trade.maker_index) data = trade.response.json() @@ -595,6 +683,15 @@ def test_trade_to_confirm_fiat_received_LN(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 7, + "User has a new fiat received notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_successful_LN(self): """ Tests a trade from order creation until Sats sent to buyer @@ -702,6 +799,15 @@ def test_collaborative_cancel_order_in_chat(self): "This order has been cancelled collaborativelly", ) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 6, + "User has a new order cancelled notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_created_order_expires(self): """ Tests the expiration of a public order @@ -734,6 +840,15 @@ def test_created_order_expires(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 6, + "User has a new order expired notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_public_order_expires(self): """ Tests the expiration of a public order @@ -767,6 +882,15 @@ def test_public_order_expires(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 6, + "User has a new order expired notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_taken_order_expires(self): """ Tests the expiration of a public order @@ -802,6 +926,15 @@ def test_taken_order_expires(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 6, + "User has a new order expired notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + def test_escrow_locked_expires(self): """ Tests the expiration of a public order @@ -890,6 +1023,15 @@ def test_chat(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {}) # Nothing in the response + maker_headers = trade.get_robot_auth(trade.maker_index) + notifications_data = self.get_notifications(maker_headers) + self.assertEqual( + len(notifications_data), + 8, + "User has a new chat notification", + ) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Get the two chatroom messages as maker response = self.client.get(path + params, **maker_headers) self.assertResponse(response) From cb927f2580d63bb2d19c801b96d4d1f414cddf70 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Sat, 22 Jun 2024 23:01:33 +0200 Subject: [PATCH 02/10] Fix tests --- tests/test_trade_pipeline.py | 77 +++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 07cfd9d57..4570773a2 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -47,14 +47,6 @@ def setUpTestData(cls): # Take the first node balances snapshot compute_node_balance() - def get_notifications(self, headers): - """ - Get robot notifications - """ - response = self.client.get(reverse("notifications"), **headers) - self.assertResponse(response) - list(response.json()) - def assert_order_logs(self, order_id): order = Order.objects.get(id=order_id) order_admin = OrderAdmin(model=Order, admin_site=AdminSite()) @@ -248,7 +240,9 @@ def test_make_order(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 0, @@ -364,7 +358,9 @@ def test_publish_order(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 1, @@ -395,7 +391,9 @@ def test_pause_unpause_order(self): self.assertEqual(data["status_message"], Order.Status(Order.Status.PUB).label) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Cancel order to avoid leaving pending HTLCs after a successful test @@ -445,7 +443,9 @@ def test_make_and_take_order(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) def test_make_and_lock_contract(self): @@ -471,7 +471,9 @@ def test_make_and_lock_contract(self): self.assertFalse(data["escrow_locked"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) notifications_data = list(trade.response.json()) self.assertEqual( len(notifications_data), @@ -501,7 +503,9 @@ def test_make_and_lock_contract(self): self.assertFalse(data["escrow_locked"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 2, @@ -536,7 +540,9 @@ def test_trade_to_locked_escrow(self): self.assertTrue(data["escrow_locked"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 3, @@ -568,7 +574,9 @@ def test_trade_to_submitted_address(self): self.assertFalse(data["is_fiat_sent"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 4, @@ -603,7 +611,9 @@ def test_trade_to_submitted_invoice(self): self.assertFalse(data["is_fiat_sent"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 4, @@ -636,7 +646,9 @@ def test_trade_to_confirm_fiat_sent_LN(self): self.assertTrue(data["is_fiat_sent"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 6, @@ -684,7 +696,9 @@ def test_trade_to_confirm_fiat_received_LN(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 7, @@ -800,7 +814,9 @@ def test_collaborative_cancel_order_in_chat(self): ) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 6, @@ -840,15 +856,6 @@ def test_created_order_expires(self): self.assert_order_logs(data["id"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) - self.assertEqual( - len(notifications_data), - 6, - "User has a new order expired notification", - ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - def test_public_order_expires(self): """ Tests the expiration of a public order @@ -883,7 +890,9 @@ def test_public_order_expires(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 6, @@ -927,7 +936,9 @@ def test_taken_order_expires(self): self.assert_order_logs(data["id"]) maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 6, @@ -1024,7 +1035,9 @@ def test_chat(self): self.assertEqual(response.json(), {}) # Nothing in the response maker_headers = trade.get_robot_auth(trade.maker_index) - notifications_data = self.get_notifications(maker_headers) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) self.assertEqual( len(notifications_data), 8, From b8ceaf08c24520b2d2f7e513d8eb901d8c72fb4e Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Sun, 23 Jun 2024 11:36:11 +0200 Subject: [PATCH 03/10] CR --- api/notifications.py | 4 +- api/views.py | 20 +++-- tests/test_trade_pipeline.py | 158 ++++++++++++++++++++--------------- 3 files changed, 105 insertions(+), 77 deletions(-) diff --git a/api/notifications.py b/api/notifications.py index 847ce0c76..480c6d477 100644 --- a/api/notifications.py +++ b/api/notifications.py @@ -210,8 +210,8 @@ def new_chat_message(self, order, chat_message): notification_reason = f"(You receive this notification because this was the first in-chat message. You will only be notified again if there is a gap bigger than {TIMEGAP} minutes between messages)" user = chat_message.receiver - title = f"💬 Hey {user.username}, a new chat message in-app was sent to you by {chat_message.sender.username} for order ID {str(order.id)}. {notification_reason}" - self.send_message(order, user.robot, title) + title = f"💬 Hey {user.username}, a new chat message in-app was sent to you by {chat_message.sender.username} for order ID {str(order.id)}." + self.send_message(order, user.robot, title, notification_reason) return diff --git a/api/views.py b/api/views.py index 2120c6357..0caa7407a 100644 --- a/api/views.py +++ b/api/views.py @@ -5,6 +5,8 @@ from django.contrib.auth.models import User from django.db.models import Q, Sum from django.utils import timezone +from django.utils.dateparse import parse_datetime +from django.http import HttpResponseBadRequest from drf_spectacular.utils import extend_schema from rest_framework import status, viewsets from rest_framework.authentication import TokenAuthentication @@ -746,15 +748,15 @@ class NotificationsView(ListAPIView): @extend_schema(**NotificationSchema.get) def get(self, request, format=None): - # robot = request.user.robot - queryset = Notification.objects.all().order_by("created_at") - # created_at = request.GET.get("created_at") - - # if created_at: - # created_at = parse_datetime(created_at) - # if not created_at: - # return HttpResponseBadRequest("Invalid date format") - # queryset = queryset.filter(created_at__gte=created_at) + robot = request.user.robot + queryset = Notification.objects.filter(robot=robot).order_by("-created_at") + created_at = request.GET.get("created_at") + + if created_at: + created_at = parse_datetime(created_at) + if not created_at: + return HttpResponseBadRequest("Invalid date format") + queryset = queryset.filter(created_at__gte=created_at) notification_data = [] for notification in queryset: diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 4570773a2..95bea1aaf 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -1,3 +1,5 @@ +import time + from datetime import datetime from decimal import Decimal @@ -352,21 +354,20 @@ def test_publish_order(self): self.assertIsInstance(public_data["price_now"], float) self.assertIsInstance(data["satoshis_now"], int) - # Cancel order to avoid leaving pending HTLCs after a successful test - trade.cancel_order() - - self.assert_order_logs(data["id"]) - maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 1, - "User has a new order notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order with ID {trade.order_id} is public in the order book.", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + + # Cancel order to avoid leaving pending HTLCs after a successful test + trade.cancel_order() + + self.assert_order_logs(data["id"]) def test_pause_unpause_order(self): """ @@ -395,6 +396,10 @@ def test_pause_unpause_order(self): self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order with ID {trade.order_id} is public in the order book.", + ) # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -437,16 +442,29 @@ def test_make_and_take_order(self): self.assertFalse(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) - # Cancel order to avoid leaving pending HTLCs after a successful test - trade.cancel_order() - - self.assert_order_logs(data["id"]) - maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {str(data['maker_nick'])}, your order was taken by {str(data['taker_nick'])}!🥳", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {str(trade.order_id)}.", + ) + + # Cancel order to avoid leaving pending HTLCs after a successful test + trade.cancel_order() + + self.assert_order_logs(data["id"]) def test_make_and_lock_contract(self): """ @@ -475,12 +493,11 @@ def test_make_and_lock_contract(self): self.assertResponse(response) notifications_data = list(response.json()) notifications_data = list(trade.response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 3, - "User has a bond locked notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order wi", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Maker GET trade.get_order(trade.maker_index) @@ -506,12 +523,11 @@ def test_make_and_lock_contract(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 2, - "User has a bond locked notification", + notifications_data[0]["title"], + f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {str(trade.order_id)}.", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Maker cancels order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -543,12 +559,11 @@ def test_trade_to_locked_escrow(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 3, - "User has a scrow locked notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order wit.", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.taker_index) @@ -577,12 +592,11 @@ def test_trade_to_submitted_address(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 4, - "User has a new order ready notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order wi", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) @@ -614,11 +628,6 @@ def test_trade_to_submitted_invoice(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) - self.assertEqual( - len(notifications_data), - 4, - "User has a new order ready notification", - ) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Cancel order to avoid leaving pending HTLCs after a successful test @@ -649,12 +658,11 @@ def test_trade_to_confirm_fiat_sent_LN(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 6, - "User has a new fiat sent notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order w", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) # Cancel order to avoid leaving pending HTLCs after a successful test trade.undo_confirm_sent(trade.maker_index) @@ -699,12 +707,11 @@ def test_trade_to_confirm_fiat_received_LN(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 7, - "User has a new fiat received notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your o", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) def test_successful_LN(self): """ @@ -781,6 +788,17 @@ def test_cancel_public_order(self): data["bad_request"], "This order has been cancelled by the maker" ) + maker_headers = trade.get_robot_auth(trade.maker_index) + maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname") + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"❌ Hey {maker_nick}, you have cancelled your public order with ID {trade.order_id}.", + ) + def test_collaborative_cancel_order_in_chat(self): """ Tests the collaborative cancellation of an order in the chat state @@ -814,15 +832,15 @@ def test_collaborative_cancel_order_in_chat(self): ) maker_headers = trade.get_robot_auth(trade.maker_index) + maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname") response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 6, - "User has a new order cancelled notification", + notifications_data[0]["title"], + f"❌ Hey {maker_nick}, your order with ID {trade.order_id} has been collaboratively cancelled.", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) def test_created_order_expires(self): """ @@ -893,12 +911,11 @@ def test_public_order_expires(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 6, - "User has a new order expired notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your order wit", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) def test_taken_order_expires(self): """ @@ -939,12 +956,11 @@ def test_taken_order_expires(self): response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( - len(notifications_data), - 6, - "User has a new order expired notification", + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) def test_escrow_locked_expires(self): """ @@ -1034,17 +1050,6 @@ def test_chat(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {}) # Nothing in the response - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual( - len(notifications_data), - 8, - "User has a new chat notification", - ) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - # Get the two chatroom messages as maker response = self.client.get(path + params, **maker_headers) self.assertResponse(response) @@ -1055,6 +1060,27 @@ def test_chat(self): self.assertEqual(response.json()["messages"][0]["nick"], maker_nick) self.assertEqual(response.json()["messages"][1]["nick"], taker_nick) + time.sleep(10) + + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + "✅ Hey your order wit", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + "✅ Hey your order wit", + ) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) From 4a7a28b1ff840de90a23598fcebb473e07fc6bda Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Tue, 25 Jun 2024 12:49:23 +0200 Subject: [PATCH 04/10] Check tests --- tests/test_trade_pipeline.py | 248 ++++++++++++++++++++++------------- tests/utils/trade.py | 26 +++- 2 files changed, 182 insertions(+), 92 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 95bea1aaf..805dbaee7 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -1,5 +1,3 @@ -import time - from datetime import datetime from decimal import Decimal @@ -391,16 +389,6 @@ def test_pause_unpause_order(self): self.assertResponse(trade.response) self.assertEqual(data["status_message"], Order.Status(Order.Status.PUB).label) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order with ID {trade.order_id} is public in the order book.", - ) - # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -449,7 +437,7 @@ def test_make_and_take_order(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {str(data['maker_nick'])}, your order was taken by {str(data['taker_nick'])}!🥳", + f"✅ Hey {str(data['maker_nick'])}, your order with ID {trade.order_id} is public in the order book.", ) taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) @@ -458,7 +446,7 @@ def test_make_and_take_order(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {str(trade.order_id)}.", + f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {trade.order_id}.", ) # Cancel order to avoid leaving pending HTLCs after a successful test @@ -488,17 +476,6 @@ def test_make_and_lock_contract(self): self.assertTrue(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - notifications_data = list(trade.response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order wi", - ) - # Maker GET trade.get_order(trade.maker_index) data = trade.response.json() @@ -519,16 +496,6 @@ def test_make_and_lock_contract(self): self.assertTrue(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {str(trade.order_id)}.", - ) - # Maker cancels order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -555,16 +522,6 @@ def test_trade_to_locked_escrow(self): self.assertTrue(data["taker_locked"]) self.assertTrue(data["escrow_locked"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order wit.", - ) - # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.taker_index) @@ -595,7 +552,16 @@ def test_trade_to_submitted_address(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order wi", + f"✅ Hey {data['maker_nick']}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat.", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['taker_nick']}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat.", ) # Cancel order to avoid leaving pending HTLCs after a successful test @@ -625,10 +591,25 @@ def test_trade_to_submitted_invoice(self): self.assertFalse(data["is_fiat_sent"]) maker_headers = trade.get_robot_auth(trade.maker_index) + maker_nick = read_file(f"tests/robots/{trade.maker_index}/nickname") response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {maker_nick}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat.", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + taker_nick = read_file(f"tests/robots/{trade.taker_index}/nickname") + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {taker_nick}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat.", + ) # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) @@ -654,16 +635,6 @@ def test_trade_to_confirm_fiat_sent_LN(self): self.assertEqual(data["status_message"], Order.Status(Order.Status.FSE).label) self.assertTrue(data["is_fiat_sent"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order w", - ) - # Cancel order to avoid leaving pending HTLCs after a successful test trade.undo_confirm_sent(trade.maker_index) data = trade.response.json() @@ -710,7 +681,16 @@ def test_trade_to_confirm_fiat_received_LN(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your o", + f"🥳 Your order with ID {str(trade.order_id)} has finished successfully!", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"🥳 Your order with ID {str(trade.order_id)} has finished successfully!", ) def test_successful_LN(self): @@ -841,6 +821,16 @@ def test_collaborative_cancel_order_in_chat(self): notifications_data[0]["title"], f"❌ Hey {maker_nick}, your order with ID {trade.order_id} has been collaboratively cancelled.", ) + taker_headers = trade.get_robot_auth(trade.taker_index) + taker_nick = read_file(f"tests/robots/{trade.taker_index}/nickname") + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"❌ Hey {taker_nick}, your order with ID {trade.order_id} has been collaboratively cancelled.", + ) def test_created_order_expires(self): """ @@ -880,11 +870,7 @@ def test_public_order_expires(self): """ trade = Trade(self.client) trade.publish_order() - - # Change order expiry to now - order = Order.objects.get(id=trade.response.json()["id"]) - order.expires_at = datetime.now() - order.save() + trade.expire_order() # Make orders expire trade.clean_orders() @@ -914,7 +900,7 @@ def test_public_order_expires(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your order wit", + f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.", ) def test_taken_order_expires(self): @@ -927,9 +913,7 @@ def test_taken_order_expires(self): trade.lock_taker_bond() # Change order expiry to now - order = Order.objects.get(id=trade.response.json()["id"]) - order.expires_at = datetime.now() - order.save() + trade.expire_order() # Make orders expire trade.clean_orders() @@ -961,6 +945,15 @@ def test_taken_order_expires(self): notifications_data[0]["title"], f"✅ Hey {data['maker_nick']}, your or", ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) def test_escrow_locked_expires(self): """ @@ -998,6 +991,25 @@ def test_escrow_locked_expires(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + def test_chat(self): """ Tests the chatting REST functionality @@ -1036,31 +1048,11 @@ def test_chat(self): self.assertTrue(response.json()["peer_connected"]) # Post new message as maker - body = {"PGP_message": message, "order_id": trade.order_id, "offset": 0} - response = self.client.post(path, data=body, **maker_headers) - self.assertResponse(response) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["messages"][0]["message"], message) - self.assertTrue(response.json()["peer_connected"]) - - # Post new message as taker without offset, so response should not have messages. - body = {"PGP_message": message + " 2", "order_id": trade.order_id} - response = self.client.post(path, data=body, **taker_headers) - self.assertResponse(response) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), {}) # Nothing in the response - - # Get the two chatroom messages as maker - response = self.client.get(path + params, **maker_headers) - self.assertResponse(response) - self.assertEqual(response.status_code, 200) - self.assertTrue(response.json()["peer_connected"]) - self.assertEqual(response.json()["messages"][0]["message"], message) - self.assertEqual(response.json()["messages"][1]["message"], message + " 2") - self.assertEqual(response.json()["messages"][0]["nick"], maker_nick) - self.assertEqual(response.json()["messages"][1]["nick"], taker_nick) - - time.sleep(10) + trade.send_chat_message(message, trade.maker_index) + self.assertResponse(trade.response) + self.assertEqual(trade.response.status_code, 200) + self.assertEqual(trade.response.json()["messages"][0]["message"], message) + self.assertTrue(trade.response.json()["peer_connected"]) maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) @@ -1071,6 +1063,13 @@ def test_chat(self): notifications_data[0]["title"], "✅ Hey your order wit", ) + + # Post new message as taker without offset, so response should not have messages. + trade.send_chat_message(message, trade.taker_index) + self.assertResponse(trade.response) + self.assertEqual(trade.response.status_code, 200) + self.assertEqual(trade.response.json(), {}) # Nothing in the response + taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) self.assertResponse(response) @@ -1081,6 +1080,16 @@ def test_chat(self): "✅ Hey your order wit", ) + # Get the two chatroom messages as maker + response = self.client.get(path + params, **maker_headers) + self.assertResponse(response) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.json()["peer_connected"]) + self.assertEqual(response.json()["messages"][0]["message"], message) + self.assertEqual(response.json()["messages"][1]["message"], message + " 2") + self.assertEqual(response.json()["messages"][0]["nick"], maker_nick) + self.assertEqual(response.json()["messages"][1]["nick"], taker_nick) + # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) @@ -1127,6 +1136,25 @@ def test_order_expires_after_only_taker_messaged(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + def test_order_expires_after_only_maker_messaged(self): """ Tests the expiration of an order in chat where taker never messaged @@ -1169,6 +1197,25 @@ def test_order_expires_after_only_maker_messaged(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + def test_withdraw_reward_after_unilateral_cancel(self): """ Tests withdraw rewards as taker after maker cancels order unilaterally @@ -1239,6 +1286,25 @@ def test_order_expires_after_fiat_sent(self): self.assert_order_logs(data["id"]) + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) + self.assertResponse(response) + notifications_data = list(response.json()) + self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + self.assertEqual( + notifications_data[0]["title"], + f"✅ Hey {data['maker_nick']}, your or", + ) + def test_ticks(self): """ Tests the historical ticks serving endpoint after creating a contract diff --git a/tests/utils/trade.py b/tests/utils/trade.py index 16b9b89df..c00b87576 100644 --- a/tests/utils/trade.py +++ b/tests/utils/trade.py @@ -1,5 +1,5 @@ from unittest.mock import patch - +from datetime import datetime from django.urls import reverse from api.management.commands.clean_orders import Command as CleanOrders @@ -119,6 +119,14 @@ def cancel_order(self, robot_index=1): body = {"action": "cancel"} self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) + def send_chat_message(self, message, robot_index=1): + path = reverse("chat") + headers = self.get_robot_auth(robot_index) + body = {"PGP_message": message, "order_id": self.order_id, "offset": 0} + self.response = self.client.post(path, data=body, **headers) + + @patch("api.tasks.send_notification.delay", send_notification) def pause_order(self, robot_index=1): path = reverse("order") params = f"?order_id={self.order_id}" @@ -126,11 +134,13 @@ def pause_order(self, robot_index=1): body = {"action": "pause"} self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) def follow_hold_invoices(self): # A background thread checks every 5 second the status of invoices. We invoke directly during test. follower = FollowInvoices() follower.follow_hold_invoices() + @patch("api.tasks.send_notification.delay", send_notification) def clean_orders(self): # A background thread checks every 5 second order expirations. We invoke directly during test. cleaner = CleanOrders() @@ -160,6 +170,7 @@ def publish_order(self): # Get order self.get_order() + @patch("api.tasks.send_notification.delay", send_notification) def take_order(self): path = reverse("order") params = f"?order_id={self.order_id}" @@ -167,6 +178,7 @@ def take_order(self): body = {"action": "take", "amount": self.take_amount} self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) def lock_taker_bond(self): # Takers's first order fetch. Should trigger maker bond hold invoice generation. self.get_order(self.taker_index) @@ -181,6 +193,7 @@ def lock_taker_bond(self): # Get order self.get_order(self.taker_index) + @patch("api.tasks.send_notification.delay", send_notification) def lock_escrow(self, robot_index): # Takers's order fetch. Should trigger trade escrow bond hold invoice generation. self.get_order(robot_index) @@ -195,6 +208,7 @@ def lock_escrow(self, robot_index): # Get order self.get_order() + @patch("api.tasks.send_notification.delay", send_notification) def submit_payout_address(self, robot_index=1): path = reverse("order") params = f"?order_id={self.order_id}" @@ -213,6 +227,7 @@ def submit_payout_address(self, robot_index=1): } self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) def submit_payout_invoice(self, robot_index=1, routing_budget=0): path = reverse("order") params = f"?order_id={self.order_id}" @@ -234,6 +249,7 @@ def submit_payout_invoice(self, robot_index=1, routing_budget=0): self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) def confirm_fiat(self, robot_index=1): path = reverse("order") params = f"?order_id={self.order_id}" @@ -241,9 +257,17 @@ def confirm_fiat(self, robot_index=1): body = {"action": "confirm"} self.response = self.client.post(path + params, body, **headers) + @patch("api.tasks.send_notification.delay", send_notification) def undo_confirm_sent(self, robot_index=1): path = reverse("order") params = f"?order_id={self.order_id}" headers = self.get_robot_auth(robot_index) body = {"action": "undo_confirm"} self.response = self.client.post(path + params, body, **headers) + + @patch("api.tasks.send_notification.delay", send_notification) + def expire_order(self): + # Change order expiry to now + order = Order.objects.get(id=self.order_id) + order.expires_at = datetime.now() + order.save() From 08613dca1499a70a114af075c2cfa98293c097b4 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Tue, 25 Jun 2024 18:26:42 +0200 Subject: [PATCH 05/10] Fix Tests --- tests/test_trade_pipeline.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 805dbaee7..75e0e2c5c 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -1143,7 +1143,7 @@ def test_order_expires_after_only_taker_messaged(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['maker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) @@ -1152,7 +1152,7 @@ def test_order_expires_after_only_taker_messaged(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) def test_order_expires_after_only_maker_messaged(self): @@ -1204,7 +1204,7 @@ def test_order_expires_after_only_maker_messaged(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['maker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) @@ -1213,7 +1213,7 @@ def test_order_expires_after_only_maker_messaged(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) def test_withdraw_reward_after_unilateral_cancel(self): @@ -1293,7 +1293,7 @@ def test_order_expires_after_fiat_sent(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['maker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) @@ -1302,7 +1302,7 @@ def test_order_expires_after_fiat_sent(self): self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", + f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.", ) def test_ticks(self): From 4d2c4e5f39b200f2def2d7d19907852593f09be3 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Tue, 25 Jun 2024 20:56:21 +0200 Subject: [PATCH 06/10] Fix Chat --- tests/test_trade_pipeline.py | 42 ++++-------------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 75e0e2c5c..547a5d4ba 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -936,25 +936,6 @@ def test_taken_order_expires(self): self.assert_order_logs(data["id"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", - ) - taker_headers = trade.get_robot_auth(trade.taker_index) - response = self.client.get(reverse("notifications"), **taker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", - ) - def test_escrow_locked_expires(self): """ Tests the expiration of a public order @@ -991,25 +972,6 @@ def test_escrow_locked_expires(self): self.assert_order_logs(data["id"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", - ) - taker_headers = trade.get_robot_auth(trade.taker_index) - response = self.client.get(reverse("notifications"), **taker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {data['maker_nick']}, your or", - ) - def test_chat(self): """ Tests the chatting REST functionality @@ -1054,6 +1016,8 @@ def test_chat(self): self.assertEqual(trade.response.json()["messages"][0]["message"], message) self.assertTrue(trade.response.json()["peer_connected"]) + trade.clean_orders() + maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) @@ -1070,6 +1034,8 @@ def test_chat(self): self.assertEqual(trade.response.status_code, 200) self.assertEqual(trade.response.json(), {}) # Nothing in the response + trade.clean_orders() + taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) self.assertResponse(response) From 5862daeb55b0285d3e2d3341dd5911b536dde531 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Tue, 25 Jun 2024 22:46:34 +0200 Subject: [PATCH 07/10] Remove unused notifications --- tests/test_trade_pipeline.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 547a5d4ba..f91152539 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -430,25 +430,6 @@ def test_make_and_take_order(self): self.assertFalse(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {str(data['maker_nick'])}, your order with ID {trade.order_id} is public in the order book.", - ) - taker_headers = trade.get_robot_auth(trade.taker_index) - response = self.client.get(reverse("notifications"), **taker_headers) - self.assertResponse(response) - notifications_data = list(response.json()) - self.assertEqual(notifications_data[0]["order_id"], trade.order_id) - self.assertEqual( - notifications_data[0]["title"], - f"✅ Hey {str(data['taker_nick'])}, you just took the order with ID {trade.order_id}.", - ) - # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() @@ -1016,8 +997,6 @@ def test_chat(self): self.assertEqual(trade.response.json()["messages"][0]["message"], message) self.assertTrue(trade.response.json()["peer_connected"]) - trade.clean_orders() - maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) @@ -1034,8 +1013,6 @@ def test_chat(self): self.assertEqual(trade.response.status_code, 200) self.assertEqual(trade.response.json(), {}) # Nothing in the response - trade.clean_orders() - taker_headers = trade.get_robot_auth(trade.taker_index) response = self.client.get(reverse("notifications"), **taker_headers) self.assertResponse(response) From 326a2f12bd99c1589ea6a8b3c34c6b45fb708224 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Wed, 26 Jun 2024 16:09:51 +0200 Subject: [PATCH 08/10] Fix chat --- tests/test_trade_pipeline.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index f91152539..fdc575cbd 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -997,14 +997,14 @@ def test_chat(self): self.assertEqual(trade.response.json()["messages"][0]["message"], message) self.assertTrue(trade.response.json()["peer_connected"]) - maker_headers = trade.get_robot_auth(trade.maker_index) - response = self.client.get(reverse("notifications"), **maker_headers) + taker_headers = trade.get_robot_auth(trade.taker_index) + response = self.client.get(reverse("notifications"), **taker_headers) self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - "✅ Hey your order wit", + f"💬 Hey {taker_nick}, a new chat message in-app was sent to you by {maker_nick} for order ID {trade.order_id}.", ) # Post new message as taker without offset, so response should not have messages. @@ -1013,14 +1013,14 @@ def test_chat(self): self.assertEqual(trade.response.status_code, 200) self.assertEqual(trade.response.json(), {}) # Nothing in the response - taker_headers = trade.get_robot_auth(trade.taker_index) - response = self.client.get(reverse("notifications"), **taker_headers) + maker_headers = trade.get_robot_auth(trade.maker_index) + response = self.client.get(reverse("notifications"), **maker_headers) self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) self.assertEqual( notifications_data[0]["title"], - "✅ Hey your order wit", + f"💬 Hey {maker_nick}, a new chat message in-app was sent to you by {taker_nick} for order ID {trade.order_id}.", ) # Get the two chatroom messages as maker From 5c08dd3e590c00ab8d0749a16870e24aa962e5a6 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Wed, 26 Jun 2024 22:43:16 +0200 Subject: [PATCH 09/10] Fix chat --- tests/test_trade_pipeline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index fdc575cbd..5b1ab1a28 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -1008,10 +1008,13 @@ def test_chat(self): ) # Post new message as taker without offset, so response should not have messages. - trade.send_chat_message(message, trade.taker_index) + trade.send_chat_message(message + " 2", trade.taker_index) self.assertResponse(trade.response) self.assertEqual(trade.response.status_code, 200) - self.assertEqual(trade.response.json(), {}) # Nothing in the response + self.assertEqual(trade.response.json()["messages"][0]["message"], message) + self.assertEqual( + trade.response.json()["messages"][1]["message"], message + " 2" + ) maker_headers = trade.get_robot_auth(trade.maker_index) response = self.client.get(reverse("notifications"), **maker_headers) From 30bb5bddaad155eb16d5a1a8ec8d9ba1e3cd614d Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Wed, 26 Jun 2024 22:56:33 +0200 Subject: [PATCH 10/10] Fix chat --- tests/test_trade_pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 5b1ab1a28..bf438a482 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -1021,9 +1021,10 @@ def test_chat(self): self.assertResponse(response) notifications_data = list(response.json()) self.assertEqual(notifications_data[0]["order_id"], trade.order_id) + # Does not receive notification because user is online self.assertEqual( notifications_data[0]["title"], - f"💬 Hey {maker_nick}, a new chat message in-app was sent to you by {taker_nick} for order ID {trade.order_id}.", + f"✅ Hey {maker_nick}, the escrow and invoice have been submitted. The fiat exchange starts now via the platform chat.", ) # Get the two chatroom messages as maker