Notifications Endpoint

This commit is contained in:
KoalaSat 2024-06-07 15:31:22 +02:00
parent 10f88e3b5d
commit eed8c11e63
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
12 changed files with 439 additions and 113 deletions

View File

@ -6,7 +6,7 @@ from django.core.management.base import BaseCommand
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 @@ class Command(BaseCommand):
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 @@ class Command(BaseCommand):
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 @@ class Command(BaseCommand):
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=[

View File

@ -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')),
],
),
]

View File

@ -4,5 +4,14 @@ from .market_tick import MarketTick
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",
]

View File

@ -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}"

View File

@ -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 @@ class Telegram:
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 @@ class Telegram:
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 @@ class Telegram:
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

View File

@ -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",

View File

@ -2,7 +2,7 @@ from decouple import config
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 OrderDetailSerializer(serializers.ModelSerializer):
)
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)

View File

@ -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 Notifications
from api.notifications import Telegram
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

View File

@ -15,6 +15,7 @@ from .views import (
RobotView,
StealthView,
TickView,
NotificationsView,
)
urlpatterns = [
@ -36,4 +37,5 @@ urlpatterns = [
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"),
]

View File

@ -14,8 +14,15 @@ from rest_framework.response import Response
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 @@ from api.oas_schemas import (
RobotViewSchema,
StealthViewSchema,
TickViewSchema,
NotificationSchema,
)
from api.serializers import (
ClaimRewardSerializer,
@ -39,6 +47,7 @@ from api.serializers import (
StealthSerializer,
TickSerializer,
UpdateOrderSerializer,
ListNotificationSerializer,
)
from api.utils import (
compute_avg_premium,
@ -659,7 +668,7 @@ class RobotView(APIView):
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 @@ class BookView(ListAPIView):
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

View File

@ -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:

View File

@ -47,6 +47,14 @@ class TradeTest(BaseAPITestCase):
# 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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
"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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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 @@ class TradeTest(BaseAPITestCase):
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)