mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-26 22:06:08 +03:00
Add testing against API specs with assertResponse
This commit is contained in:
parent
edd9455d7b
commit
b4fe30e733
@ -30,7 +30,7 @@ POSTGRES_HOST='127.0.0.1'
|
||||
POSTGRES_PORT='5432'
|
||||
|
||||
# Tor proxy for remote calls (e.g. fetching prices or sending Telegram messages)
|
||||
USE_TOR='True'
|
||||
USE_TOR=True
|
||||
TOR_PROXY='127.0.0.1:9050'
|
||||
|
||||
# Auto unlock LND password. Only used in development docker-compose environment.
|
||||
@ -166,4 +166,4 @@ MINIMUM_TARGET_CONF = 24
|
||||
SLASHED_BOND_REWARD_SPLIT = 0.5
|
||||
|
||||
# Username for HTLCs escrows
|
||||
ESCROW_USERNAME = 'admin'
|
||||
ESCROW_USERNAME = 'admin'
|
@ -1,3 +1,4 @@
|
||||
# We use custom seeded UUID generation during testing
|
||||
import uuid
|
||||
|
||||
from decouple import config
|
||||
@ -9,6 +10,19 @@ from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
if config("COORDINATOR_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 Order(models.Model):
|
||||
class Types(models.IntegerChoices):
|
||||
@ -44,7 +58,7 @@ class Order(models.Model):
|
||||
NESINV = 4, "Neither escrow locked or invoice submitted"
|
||||
|
||||
# order info
|
||||
reference = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
reference = models.UUIDField(default=custom_uuid, editable=False)
|
||||
status = models.PositiveSmallIntegerField(
|
||||
choices=Status.choices, null=False, default=Status.WFB
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||
from drf_spectacular.utils import OpenApiExample, OpenApiParameter
|
||||
|
||||
from api.serializers import (
|
||||
InfoSerializer,
|
||||
ListOrderSerializer,
|
||||
OrderDetailSerializer,
|
||||
StealthSerializer,
|
||||
@ -322,17 +323,7 @@ class OrderViewSchema:
|
||||
),
|
||||
],
|
||||
"responses": {
|
||||
200: {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"oneOf": [
|
||||
{"type": "str"},
|
||||
{"type": "number"},
|
||||
{"type": "object"},
|
||||
{"type": "boolean"},
|
||||
],
|
||||
},
|
||||
},
|
||||
200: OrderDetailSerializer,
|
||||
400: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -474,6 +465,16 @@ class RobotViewSchema:
|
||||
"type": "integer",
|
||||
"description": "Last order id if present",
|
||||
},
|
||||
"earned_rewards": {
|
||||
"type": "integer",
|
||||
"description": "Satoshis available to be claimed",
|
||||
},
|
||||
"last_login": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": True,
|
||||
"description": "Last time the coordinator saw this robot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -517,6 +518,9 @@ class InfoViewSchema:
|
||||
- on-chain swap fees
|
||||
"""
|
||||
),
|
||||
"responses": {
|
||||
200: InfoSerializer,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,13 +6,19 @@ from .models import MarketTick, Order
|
||||
RETRY_TIME = int(config("RETRY_TIME"))
|
||||
|
||||
|
||||
class VersionSerializer(serializers.Serializer):
|
||||
major = serializers.IntegerField()
|
||||
minor = serializers.IntegerField()
|
||||
patch = serializers.IntegerField()
|
||||
|
||||
|
||||
class InfoSerializer(serializers.Serializer):
|
||||
num_public_buy_orders = serializers.IntegerField()
|
||||
num_public_sell_orders = serializers.IntegerField()
|
||||
book_liquidity = serializers.IntegerField(
|
||||
help_text="Total amount of BTC in the order book"
|
||||
)
|
||||
active_robots_today = serializers.CharField()
|
||||
active_robots_today = serializers.IntegerField()
|
||||
last_day_nonkyc_btc_premium = serializers.FloatField(
|
||||
help_text="Average premium (weighted by volume) of the orders in the last 24h"
|
||||
)
|
||||
@ -23,6 +29,7 @@ class InfoSerializer(serializers.Serializer):
|
||||
help_text="Total volume in BTC since exchange's inception"
|
||||
)
|
||||
lnd_version = serializers.CharField()
|
||||
cln_version = serializers.CharField()
|
||||
robosats_running_commit_hash = serializers.CharField()
|
||||
alternative_site = serializers.CharField()
|
||||
alternative_name = serializers.CharField()
|
||||
@ -35,6 +42,17 @@ class InfoSerializer(serializers.Serializer):
|
||||
current_swap_fee_rate = serializers.FloatField(
|
||||
help_text="Swap fees to perform on-chain transaction (percent)"
|
||||
)
|
||||
version = VersionSerializer()
|
||||
notice_severity = serializers.ChoiceField(
|
||||
choices=[
|
||||
("none", "none"),
|
||||
("warning", "warning"),
|
||||
("success", "success"),
|
||||
("error", "error"),
|
||||
("info", "info"),
|
||||
]
|
||||
)
|
||||
notice_message = serializers.CharField()
|
||||
|
||||
|
||||
class ListOrderSerializer(serializers.ModelSerializer):
|
||||
@ -60,7 +78,7 @@ class ListOrderSerializer(serializers.ModelSerializer):
|
||||
"escrow_duration",
|
||||
"bond_size",
|
||||
"latitude",
|
||||
"longitude"
|
||||
"longitude",
|
||||
)
|
||||
|
||||
|
||||
@ -160,10 +178,13 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
required=False,
|
||||
help_text="Price of the order in the order's currency at the time of request (upto 5 significant digits)",
|
||||
)
|
||||
premium = serializers.IntegerField(
|
||||
premium = serializers.CharField(
|
||||
required=False, help_text="Premium over the CEX price set by the maker"
|
||||
)
|
||||
premium_now = serializers.FloatField(
|
||||
required=False, help_text="Premium over the CEX price at the current time"
|
||||
)
|
||||
premium_percentile = serializers.IntegerField(
|
||||
premium_percentile = serializers.FloatField(
|
||||
required=False,
|
||||
help_text="(Only if `is_maker`) Premium percentile of your order compared to other public orders in the same currency currently in the order book",
|
||||
)
|
||||
@ -253,11 +274,11 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
required=False,
|
||||
help_text="in percentage, the swap fee rate the platform charges",
|
||||
)
|
||||
latitude = serializers.FloatField(
|
||||
latitude = serializers.CharField(
|
||||
required=False,
|
||||
help_text="Latitude of the order for F2F payments",
|
||||
)
|
||||
longitude = serializers.FloatField(
|
||||
longitude = serializers.CharField(
|
||||
required=False,
|
||||
help_text="Longitude of the order for F2F payments",
|
||||
)
|
||||
@ -300,7 +321,11 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
maker_summary = SummarySerializer(required=False)
|
||||
taker_summary = SummarySerializer(required=False)
|
||||
platform_summary = PlatformSummarySerializer(required=True)
|
||||
satoshis_now = serializers.IntegerField(
|
||||
required=False,
|
||||
help_text="Maximum size of the order right now in Satoshis",
|
||||
)
|
||||
platform_summary = PlatformSummarySerializer(required=False)
|
||||
expiry_message = serializers.CharField(
|
||||
required=False,
|
||||
help_text="The reason the order expired (message associated with the `expiry_reason`)",
|
||||
@ -338,7 +363,9 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
"payment_method",
|
||||
"is_explicit",
|
||||
"premium",
|
||||
"premium_now",
|
||||
"satoshis",
|
||||
"satoshis_now",
|
||||
"maker",
|
||||
"taker",
|
||||
"escrow_duration",
|
||||
@ -350,7 +377,6 @@ class OrderDetailSerializer(serializers.ModelSerializer):
|
||||
"maker_status",
|
||||
"taker_status",
|
||||
"price_now",
|
||||
"premium",
|
||||
"premium_percentile",
|
||||
"num_similar_orders",
|
||||
"tg_enabled",
|
||||
@ -441,7 +467,7 @@ class OrderPublicSerializer(serializers.ModelSerializer):
|
||||
"satoshis_now",
|
||||
"bond_size",
|
||||
"latitude",
|
||||
"longitude"
|
||||
"longitude",
|
||||
)
|
||||
|
||||
|
||||
@ -482,7 +508,7 @@ class MakeOrderSerializer(serializers.ModelSerializer):
|
||||
"escrow_duration",
|
||||
"bond_size",
|
||||
"latitude",
|
||||
"longitude"
|
||||
"longitude",
|
||||
)
|
||||
|
||||
|
||||
|
23
api/urls.py
23
api/urls.py
@ -20,19 +20,20 @@ from .views import (
|
||||
urlpatterns = [
|
||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path("", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
||||
path("make/", MakerView.as_view()),
|
||||
path("make/", MakerView.as_view(), name="make"),
|
||||
path(
|
||||
"order/",
|
||||
OrderView.as_view({"get": "get", "post": "take_update_confirm_dispute_cancel"}),
|
||||
name="order",
|
||||
),
|
||||
path("robot/", RobotView.as_view()),
|
||||
path("book/", BookView.as_view()),
|
||||
path("info/", InfoView.as_view()),
|
||||
path("price/", PriceView.as_view()),
|
||||
path("limits/", LimitView.as_view()),
|
||||
path("reward/", RewardView.as_view()),
|
||||
path("historical/", HistoricalView.as_view()),
|
||||
path("ticks/", TickView.as_view()),
|
||||
path("stealth/", StealthView.as_view()),
|
||||
path("chat/", ChatView.as_view({"get": "get", "post": "post"})),
|
||||
path("robot/", RobotView.as_view(), name="robot"),
|
||||
path("book/", BookView.as_view(), name="book"),
|
||||
path("info/", InfoView.as_view({"get": "get"}), name="info"),
|
||||
path("price/", PriceView.as_view(), name="price"),
|
||||
path("limits/", LimitView.as_view(), name="limits"),
|
||||
path("reward/", RewardView.as_view(), name="reward"),
|
||||
path("historical/", HistoricalView.as_view(), name="historical"),
|
||||
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"),
|
||||
]
|
||||
|
@ -724,7 +724,7 @@ class BookView(ListAPIView):
|
||||
return Response(book_data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class InfoView(ListAPIView):
|
||||
class InfoView(viewsets.ViewSet):
|
||||
serializer_class = InfoSerializer
|
||||
|
||||
@extend_schema(**InfoViewSchema.get)
|
||||
|
@ -140,7 +140,7 @@ paths:
|
||||
description: ''
|
||||
/api/info/:
|
||||
get:
|
||||
operationId: info_list
|
||||
operationId: info_retrieve
|
||||
description: |2
|
||||
|
||||
Get general info (overview) about the exchange.
|
||||
@ -172,9 +172,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Info'
|
||||
$ref: '#/components/schemas/Info'
|
||||
description: ''
|
||||
/api/limits/:
|
||||
get:
|
||||
@ -563,13 +561,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
oneOf:
|
||||
- type: str
|
||||
- type: number
|
||||
- type: object
|
||||
- type: boolean
|
||||
$ref: '#/components/schemas/OrderDetail'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
@ -1103,7 +1095,7 @@ components:
|
||||
type: integer
|
||||
description: Total amount of BTC in the order book
|
||||
active_robots_today:
|
||||
type: string
|
||||
type: integer
|
||||
last_day_nonkyc_btc_premium:
|
||||
type: number
|
||||
format: double
|
||||
@ -1119,6 +1111,8 @@ components:
|
||||
description: Total volume in BTC since exchange's inception
|
||||
lnd_version:
|
||||
type: string
|
||||
cln_version:
|
||||
type: string
|
||||
robosats_running_commit_hash:
|
||||
type: string
|
||||
alternative_site:
|
||||
@ -1147,12 +1141,19 @@ components:
|
||||
type: number
|
||||
format: double
|
||||
description: Swap fees to perform on-chain transaction (percent)
|
||||
version:
|
||||
$ref: '#/components/schemas/Version'
|
||||
notice_severity:
|
||||
$ref: '#/components/schemas/NoticeSeverityEnum'
|
||||
notice_message:
|
||||
type: string
|
||||
required:
|
||||
- active_robots_today
|
||||
- alternative_name
|
||||
- alternative_site
|
||||
- bond_size
|
||||
- book_liquidity
|
||||
- cln_version
|
||||
- current_swap_fee_rate
|
||||
- last_day_nonkyc_btc_premium
|
||||
- last_day_volume
|
||||
@ -1162,10 +1163,13 @@ components:
|
||||
- network
|
||||
- node_alias
|
||||
- node_id
|
||||
- notice_message
|
||||
- notice_severity
|
||||
- num_public_buy_orders
|
||||
- num_public_sell_orders
|
||||
- robosats_running_commit_hash
|
||||
- taker_fee
|
||||
- version
|
||||
ListOrder:
|
||||
type: object
|
||||
properties:
|
||||
@ -1355,6 +1359,20 @@ components:
|
||||
required:
|
||||
- currency
|
||||
- id
|
||||
NoticeSeverityEnum:
|
||||
enum:
|
||||
- none
|
||||
- warning
|
||||
- success
|
||||
- error
|
||||
- info
|
||||
type: string
|
||||
description: |-
|
||||
* `none` - none
|
||||
* `warning` - warning
|
||||
* `success` - success
|
||||
* `error` - error
|
||||
* `info` - info
|
||||
NullEnum:
|
||||
enum:
|
||||
- null
|
||||
@ -1952,6 +1970,19 @@ components:
|
||||
nullable: true
|
||||
required:
|
||||
- action
|
||||
Version:
|
||||
type: object
|
||||
properties:
|
||||
major:
|
||||
type: integer
|
||||
minor:
|
||||
type: integer
|
||||
patch:
|
||||
type: integer
|
||||
required:
|
||||
- major
|
||||
- minor
|
||||
- patch
|
||||
securitySchemes:
|
||||
tokenAuth:
|
||||
type: apiKey
|
||||
|
@ -275,9 +275,9 @@ MAX_PUBLIC_ORDER_DURATION = 24
|
||||
MIN_PUBLIC_ORDER_DURATION = 0.166
|
||||
|
||||
# Bond size as percentage (%)
|
||||
DEFAULT_BOND_SIZE = 3
|
||||
MIN_BOND_SIZE = 2
|
||||
MAX_BOND_SIZE = 15
|
||||
DEFAULT_BOND_SIZE = float(3)
|
||||
MIN_BOND_SIZE = float(2)
|
||||
MAX_BOND_SIZE = float(15)
|
||||
|
||||
# Default time to provide a valid invoice and the trade escrow MINUTES
|
||||
INVOICE_AND_ESCROW_DURATION = 180
|
||||
|
2004
tests/api_specs.yaml
Normal file
2004
tests/api_specs.yaml
Normal file
File diff suppressed because it is too large
Load Diff
28
tests/test_api.py
Normal file
28
tests/test_api.py
Normal file
@ -0,0 +1,28 @@
|
||||
import urllib.request
|
||||
|
||||
from openapi_tester.schema_tester import SchemaTester
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
# Update api specs to the newest from a running django server (if any)
|
||||
try:
|
||||
urllib.request.urlretrieve(
|
||||
"http://127.0.0.1:8000/api/schema", "tests/api_specs.yaml"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Could not fetch current API specs: {e}")
|
||||
print("Using previously existing api_specs.yaml definitions")
|
||||
|
||||
schema_tester = SchemaTester(schema_file_path="tests/api_specs.yaml")
|
||||
|
||||
|
||||
class BaseAPITestCase(APITestCase):
|
||||
@staticmethod
|
||||
def assertResponse(response: Response, **kwargs) -> None:
|
||||
"""helper to run validate_response and pass kwargs to it"""
|
||||
|
||||
# List of endpoints with no available OpenAPI schema
|
||||
skip_paths = ["/coordinator/login/"]
|
||||
|
||||
if response.request["PATH_INFO"] not in skip_paths:
|
||||
schema_tester.validate_response(response=response, **kwargs)
|
@ -4,10 +4,12 @@ from unittest.mock import patch
|
||||
from decouple import config
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
|
||||
from tests.mocks.cln import MockNodeStub
|
||||
from tests.mocks.lnd import MockVersionerStub
|
||||
from tests.test_api import BaseAPITestCase
|
||||
|
||||
FEE = config("FEE", cast=float, default=0.2)
|
||||
NODE_ID = config("NODE_ID", cast=str, default="033b58d7......")
|
||||
@ -18,7 +20,7 @@ NOTICE_SEVERITY = config("NOTICE_SEVERITY", cast=str, default="none")
|
||||
NOTICE_MESSAGE = config("NOTICE_MESSAGE", cast=str, default="")
|
||||
|
||||
|
||||
class CoordinatorInfoTest(TestCase):
|
||||
class CoordinatorInfoTest(BaseAPITestCase):
|
||||
su_pass = "12345678"
|
||||
su_name = config("ESCROW_USERNAME", cast=str, default="admin")
|
||||
|
||||
@ -32,12 +34,14 @@ class CoordinatorInfoTest(TestCase):
|
||||
@patch("api.lightning.cln.node_pb2_grpc.NodeStub", MockNodeStub)
|
||||
@patch("api.lightning.lnd.verrpc_pb2_grpc.VersionerStub", MockVersionerStub)
|
||||
def test_info(self):
|
||||
path = "/api/info/"
|
||||
path = reverse("info")
|
||||
|
||||
response = self.client.get(path)
|
||||
data = json.loads(response.content.decode())
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertResponse(response)
|
||||
|
||||
self.assertEqual(data["num_public_buy_orders"], 0)
|
||||
self.assertEqual(data["num_public_sell_orders"], 0)
|
||||
self.assertEqual(data["book_liquidity"], 0)
|
||||
|
@ -5,7 +5,7 @@ from unittest.mock import patch
|
||||
|
||||
from decouple import config
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from api.management.commands.follow_invoices import Command as FollowInvoices
|
||||
from api.models import Currency, Order
|
||||
@ -15,39 +15,84 @@ from tests.mocks.lnd import ( # MockRouterStub,; MockSignerStub,; MockVersioner
|
||||
MockInvoicesStub,
|
||||
MockLightningStub,
|
||||
)
|
||||
from tests.test_api import BaseAPITestCase
|
||||
|
||||
|
||||
class TradeTest(TestCase):
|
||||
def read_file(file_path):
|
||||
"""
|
||||
Read a file and return its content.
|
||||
"""
|
||||
with open(file_path, "r") as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
class TradeTest(BaseAPITestCase):
|
||||
su_pass = "12345678"
|
||||
su_name = config("ESCROW_USERNAME", cast=str, default="admin")
|
||||
|
||||
def setUp(self):
|
||||
maker_form_with_range = {
|
||||
"type": Order.Types.BUY,
|
||||
"currency": 1,
|
||||
"has_range": True,
|
||||
"min_amount": 21,
|
||||
"max_amount": 101.7,
|
||||
"payment_method": "Advcash Cash F2F",
|
||||
"is_explicit": False,
|
||||
"premium": 3.34,
|
||||
"public_duration": 69360,
|
||||
"escrow_duration": 8700,
|
||||
"bond_size": 3.5,
|
||||
"latitude": 34.7455,
|
||||
"longitude": 135.503,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""
|
||||
Create a superuser. The superuser is the escrow party.
|
||||
Set up initial data for the test case.
|
||||
"""
|
||||
self.client = Client()
|
||||
User.objects.create_superuser(self.su_name, "super@user.com", self.su_pass)
|
||||
# Create super user
|
||||
User.objects.create_superuser(cls.su_name, "super@user.com", cls.su_pass)
|
||||
|
||||
# Fetch currency prices from external APIs
|
||||
cache_market()
|
||||
|
||||
def test_login_superuser(self):
|
||||
"""
|
||||
Test logging in as a superuser.
|
||||
Test the login functionality for the superuser.
|
||||
"""
|
||||
path = "/coordinator/login/"
|
||||
path = reverse("admin:login")
|
||||
data = {"username": self.su_name, "password": self.su_pass}
|
||||
response = self.client.post(path, data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertResponse(response)
|
||||
|
||||
def test_cache_market(self):
|
||||
"""
|
||||
Test if the cache_market() call during test setup worked
|
||||
"""
|
||||
usd = Currency.objects.get(id=1)
|
||||
self.assertIsInstance(
|
||||
usd.exchange_rate,
|
||||
Decimal,
|
||||
f"Exchange rate is not a Decimal. Got {type(usd.exchange_rate)}",
|
||||
)
|
||||
self.assertGreater(
|
||||
usd.exchange_rate, 0, "Exchange rate is not higher than zero"
|
||||
)
|
||||
self.assertIsInstance(
|
||||
usd.timestamp, datetime, "External price timestamp is not a datetime"
|
||||
)
|
||||
|
||||
def get_robot_auth(self, robot_index, first_encounter=False):
|
||||
"""
|
||||
Create an AUTH header that embeds token, pub_key, and enc_priv_key into a single string
|
||||
as requested by the robosats token middleware.
|
||||
"""
|
||||
with open(f"tests/robots/{robot_index}/b91_token", "r") as file:
|
||||
b91_token = file.read()
|
||||
with open(f"tests/robots/{robot_index}/pub_key", "r") as file:
|
||||
pub_key = file.read()
|
||||
with open(f"tests/robots/{robot_index}/enc_priv_key", "r") as file:
|
||||
enc_priv_key = file.read()
|
||||
|
||||
b91_token = read_file(f"tests/robots/{robot_index}/b91_token")
|
||||
pub_key = read_file(f"tests/robots/{robot_index}/pub_key")
|
||||
enc_priv_key = read_file(f"tests/robots/{robot_index}/enc_priv_key")
|
||||
|
||||
# First time a robot authenticated, it is registered by the backend, so pub_key and enc_priv_key is needed
|
||||
if first_encounter:
|
||||
@ -59,14 +104,21 @@ class TradeTest(TestCase):
|
||||
|
||||
return headers, pub_key, enc_priv_key
|
||||
|
||||
def assert_robot(self, response, pub_key, enc_priv_key, expected_nickname):
|
||||
def assert_robot(self, response, pub_key, enc_priv_key, robot_index):
|
||||
"""
|
||||
Assert that the robot is created correctly.
|
||||
"""
|
||||
nickname = read_file(f"tests/robots/{robot_index}/nickname")
|
||||
|
||||
data = json.loads(response.content.decode())
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertResponse(response)
|
||||
|
||||
self.assertEqual(
|
||||
data["nickname"],
|
||||
expected_nickname,
|
||||
"Robot created nickname is not MyopicRacket333",
|
||||
nickname,
|
||||
f"Robot created nickname is not {nickname}",
|
||||
)
|
||||
self.assertEqual(
|
||||
data["public_key"], pub_key, "Returned public Kky does not match"
|
||||
@ -95,67 +147,37 @@ class TradeTest(TestCase):
|
||||
"""
|
||||
Creates the robots in /tests/robots/{robot_index}
|
||||
"""
|
||||
path = "/api/robot/"
|
||||
path = reverse("robot")
|
||||
headers, pub_key, enc_priv_key = self.get_robot_auth(robot_index, True)
|
||||
|
||||
response = self.client.get(path, **headers)
|
||||
|
||||
with open(f"tests/robots/{robot_index}/nickname", "r") as file:
|
||||
expected_nickname = file.read()
|
||||
|
||||
self.assert_robot(response, pub_key, enc_priv_key, expected_nickname)
|
||||
self.assert_robot(response, pub_key, enc_priv_key, robot_index)
|
||||
|
||||
def test_create_robots(self):
|
||||
"""
|
||||
Creates two robots to be used in the trade tests
|
||||
Test the creation of two robots to be used in the trade tests
|
||||
"""
|
||||
self.create_robot(robot_index=1)
|
||||
self.create_robot(robot_index=2)
|
||||
|
||||
def test_cache_market(self):
|
||||
cache_market()
|
||||
|
||||
usd = Currency.objects.get(id=1)
|
||||
self.assertIsInstance(
|
||||
usd.exchange_rate,
|
||||
Decimal,
|
||||
f"Exchange rate is not a Decimal. Got {type(usd.exchange_rate)}",
|
||||
)
|
||||
self.assertGreater(
|
||||
usd.exchange_rate, 0, "Exchange rate is not higher than zero"
|
||||
)
|
||||
self.assertIsInstance(
|
||||
usd.timestamp, datetime, "External price timestamp is not a datetime"
|
||||
)
|
||||
|
||||
def create_order(self, maker_form, robot_index=1):
|
||||
# Requisites
|
||||
# Cache market prices
|
||||
self.test_cache_market()
|
||||
path = "/api/make/"
|
||||
def make_order(self, maker_form, robot_index=1):
|
||||
"""
|
||||
Create an order for the test.
|
||||
"""
|
||||
path = reverse("make")
|
||||
# Get valid robot auth headers
|
||||
headers, _, _ = self.get_robot_auth(robot_index, True)
|
||||
|
||||
response = self.client.post(path, maker_form, **headers)
|
||||
return response
|
||||
|
||||
def test_create_order(self):
|
||||
maker_form = {
|
||||
"type": Order.Types.BUY,
|
||||
"currency": 1,
|
||||
"has_range": True,
|
||||
"min_amount": 21,
|
||||
"max_amount": 101.7,
|
||||
"payment_method": "Advcash Cash F2F",
|
||||
"is_explicit": False,
|
||||
"premium": 3.34,
|
||||
"public_duration": 69360,
|
||||
"escrow_duration": 8700,
|
||||
"bond_size": 3.5,
|
||||
"latitude": 34.7455,
|
||||
"longitude": 135.503,
|
||||
}
|
||||
response = self.create_order(maker_form, robot_index=1)
|
||||
def test_make_order(self):
|
||||
"""
|
||||
Test the creation of an order.
|
||||
"""
|
||||
maker_form = self.maker_form_with_range
|
||||
response = self.make_order(maker_form, robot_index=1)
|
||||
data = json.loads(response.content.decode())
|
||||
|
||||
# Checks
|
||||
@ -237,7 +259,7 @@ class TradeTest(TestCase):
|
||||
@patch("api.lightning.lnd.lightning_pb2_grpc.LightningStub", MockLightningStub)
|
||||
@patch("api.lightning.lnd.invoices_pb2_grpc.InvoicesStub", MockInvoicesStub)
|
||||
def get_order(self, order_id, robot_index=1, first_encounter=False):
|
||||
path = "/api/order/"
|
||||
path = reverse("order")
|
||||
params = f"?order_id={order_id}"
|
||||
headers, _, _ = self.get_robot_auth(robot_index, first_encounter)
|
||||
response = self.client.get(path + params, **headers)
|
||||
@ -246,22 +268,10 @@ class TradeTest(TestCase):
|
||||
|
||||
def test_get_order_created(self):
|
||||
# Make an order
|
||||
maker_form = {
|
||||
"type": Order.Types.BUY,
|
||||
"currency": 1,
|
||||
"has_range": True,
|
||||
"min_amount": 21,
|
||||
"max_amount": 101.7,
|
||||
"payment_method": "Advcash Cash F2F",
|
||||
"is_explicit": False,
|
||||
"premium": 3.34,
|
||||
"public_duration": 69360,
|
||||
"escrow_duration": 8700,
|
||||
"bond_size": 3.5,
|
||||
"latitude": 34.7455,
|
||||
"longitude": 135.503,
|
||||
}
|
||||
order_made_response = self.create_order(maker_form, robot_index=1)
|
||||
maker_form = self.maker_form_with_range
|
||||
robot_index = 1
|
||||
|
||||
order_made_response = self.make_order(maker_form, robot_index)
|
||||
order_made_data = json.loads(order_made_response.content.decode())
|
||||
|
||||
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
|
||||
@ -284,7 +294,9 @@ class TradeTest(TestCase):
|
||||
self.assertEqual(data["status_message"], Order.Status(Order.Status.WFB).label)
|
||||
self.assertFalse(data["is_fiat_sent"])
|
||||
self.assertFalse(data["is_disputed"])
|
||||
self.assertEqual(data["ur_nick"], "MyopicRacket333")
|
||||
self.assertEqual(
|
||||
data["ur_nick"], read_file(f"tests/robots/{robot_index}/nickname")
|
||||
)
|
||||
self.assertTrue(isinstance(data["satoshis_now"], int))
|
||||
self.assertFalse(data["maker_locked"])
|
||||
self.assertFalse(data["taker_locked"])
|
||||
@ -302,9 +314,9 @@ class TradeTest(TestCase):
|
||||
follow_invoices = FollowInvoices()
|
||||
follow_invoices.follow_hold_invoices()
|
||||
|
||||
def create_and_publish_order(self, maker_form, robot_index=1):
|
||||
def make_and_publish_order(self, maker_form, robot_index=1):
|
||||
# Make an order
|
||||
order_made_response = self.create_order(maker_form, robot_index=1)
|
||||
order_made_response = self.make_order(maker_form, robot_index)
|
||||
order_made_data = json.loads(order_made_response.content.decode())
|
||||
|
||||
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
|
||||
@ -318,23 +330,9 @@ class TradeTest(TestCase):
|
||||
return response
|
||||
|
||||
def test_publish_order(self):
|
||||
maker_form = {
|
||||
"type": Order.Types.BUY,
|
||||
"currency": 1,
|
||||
"has_range": True,
|
||||
"min_amount": 21,
|
||||
"max_amount": 101.7,
|
||||
"payment_method": "Advcash Cash F2F",
|
||||
"is_explicit": False,
|
||||
"premium": 3.34,
|
||||
"public_duration": 69360,
|
||||
"escrow_duration": 8700,
|
||||
"bond_size": 3.5,
|
||||
"latitude": 34.7455,
|
||||
"longitude": 135.503,
|
||||
}
|
||||
maker_form = self.maker_form_with_range
|
||||
# Get order
|
||||
response = self.create_and_publish_order(maker_form)
|
||||
response = self.make_and_publish_order(maker_form)
|
||||
data = json.loads(response.content.decode())
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@ -353,3 +351,66 @@ class TradeTest(TestCase):
|
||||
self.assertFalse(public_data["is_participant"])
|
||||
self.assertTrue(isinstance(public_data["price_now"], float))
|
||||
self.assertTrue(isinstance(data["satoshis_now"], int))
|
||||
|
||||
@patch("api.lightning.cln.hold_pb2_grpc.HoldStub", MockHoldStub)
|
||||
@patch("api.lightning.lnd.lightning_pb2_grpc.LightningStub", MockLightningStub)
|
||||
@patch("api.lightning.lnd.invoices_pb2_grpc.InvoicesStub", MockInvoicesStub)
|
||||
def take_order(self, order_id, amount, robot_index=2):
|
||||
path = reverse("order")
|
||||
params = f"?order_id={order_id}"
|
||||
headers, _, _ = self.get_robot_auth(robot_index, first_encounter=True)
|
||||
body = {"action": "take", "amount": amount}
|
||||
response = self.client.post(path + params, body, **headers)
|
||||
|
||||
return response
|
||||
|
||||
def make_and_take_order(
|
||||
self, maker_form, take_amount=80, maker_index=1, taker_index=2
|
||||
):
|
||||
response_published = self.make_and_publish_order(maker_form, maker_index)
|
||||
data_publised = json.loads(response_published.content.decode())
|
||||
response = self.take_order(data_publised["id"], take_amount, taker_index)
|
||||
return response
|
||||
|
||||
# def test_make_and_take_order(self):
|
||||
# maker_index = 1
|
||||
# taker_index = 2
|
||||
# maker_form = self.maker_form_with_range
|
||||
# self.create_robot(taker_index) #### WEEEE SHOULD NOT BE NEEDED >??? WHY ROBOT HAS NO LOGIN TIME??
|
||||
# response = self.make_and_take_order(maker_form, 80, maker_index, taker_index)
|
||||
# data = json.loads(response.content.decode())
|
||||
|
||||
# print(data)
|
||||
|
||||
# self.assertEqual(
|
||||
# data["ur_nick"], read_file(f"tests/robots/{taker_index}/nickname")
|
||||
# )
|
||||
# self.assertEqual(
|
||||
# data["taker_nick"], read_file(f"tests/robots/{taker_index}/nickname")
|
||||
# )
|
||||
# self.assertEqual(
|
||||
# data["maker_nick"], read_file(f"tests/robots/{maker_index}/nickname")
|
||||
# )
|
||||
# self.assertFalse(data["is_maker"])
|
||||
# self.assertTrue(data["is_taker"])
|
||||
# self.assertTrue(data["is_participant"])
|
||||
|
||||
# a = {
|
||||
# "maker_status": "Active",
|
||||
# "taker_status": "Active",
|
||||
# "price_now": 38205.0,
|
||||
# "premium_now": 3.34,
|
||||
# "satoshis_now": 266196,
|
||||
# "is_buyer": False,
|
||||
# "is_seller": True,
|
||||
# "taker_nick": "EquivalentWool707",
|
||||
# "status_message": "Waiting for taker bond",
|
||||
# "is_fiat_sent": False,
|
||||
# "is_disputed": False,
|
||||
# "ur_nick": "EquivalentWool707",
|
||||
# "maker_locked": True,
|
||||
# "taker_locked": False,
|
||||
# "escrow_locked": False,
|
||||
# "bond_invoice": "lntb73280n1pj5uypwpp5vklcx3s3c66ltz5v7kglppke5n3u6sa6h8m6whe278lza7rwfc7qd2j2pshjmt9de6zqun9vejhyetwvdjn5gp3vgcxgvfkv43z6e3cvyez6dpkxejj6cnxvsmj6c3exsuxxden89skzv3j9cs9g6rfwvs8qcted4jkuapq2ay5cnpqgefy2326g5syjn3qt984253q2aq5cnz92skzqcmgv43kkgr0dcs9ymmzdafkzarnyp5kvgr5dpjjqmr0vd4jqampwvs8xatrvdjhxumxw4kzugzfwss8w6tvdssxyefqw4hxcmmrddjkggpgveskjmpfyp6kumr9wdejq7t0w5sxx6r9v96zqmmjyp3kzmnrv4kzqatwd9kxzar9wfskcmre9ccqz2sxqzfvsp5hkz0dnvja244hc8jwmpeveaxtjd4ddzuqlpqc5zxa6tckr8py50s9qyyssqdcl6w2rhma7k3v904q4tuz68z82d6x47dgflk6m8jdtgt9dg3n9304axv8qvd66dq39sx7yu20sv5pyguv9dnjw3385y8utadxxsqtsqpf7p3w",
|
||||
# "bond_satoshis": 7328,
|
||||
# }
|
||||
|
Loading…
Reference in New Issue
Block a user