Add testing against API specs with assertResponse

This commit is contained in:
Reckless_Satoshi 2023-11-11 15:48:54 +00:00 committed by Reckless_Satoshi
parent edd9455d7b
commit b4fe30e733
12 changed files with 2324 additions and 151 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

28
tests/test_api.py Normal file
View 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)

View File

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

View File

@ -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,
# }