Skip to content

Commit

Permalink
Add LND mock up classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Reckless-Satoshi committed Nov 6, 2023
1 parent 8808ee8 commit 7e4ddf2
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/django-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ["3.11.6"] # , "3.12"]
python-version: ["3.11.6", "3.12"]

services:
db:
Expand Down Expand Up @@ -63,6 +63,7 @@ jobs:
- name: 'Create .env File'
run: |
mv .env-sample .env
sed -i "s/USE_TOR='True'/USE_TOR='False'/" .env
- name: 'Wait for PostgreSQL to become ready'
run: |
Expand Down
6 changes: 2 additions & 4 deletions api/lightning/lnd.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ def metadata_callback(context, callback):
5: "Insufficient local balance.",
}

is_testnet = lightningstub.GetInfo(lnrpc.GetInfoRequest()).testnet

@classmethod
def get_version(cls):
try:
Expand All @@ -93,8 +91,8 @@ def decode_payreq(cls, invoice):
@classmethod
def estimate_fee(cls, amount_sats, target_conf=2, min_confs=1):
"""Returns estimated fee for onchain payouts"""

if cls.is_testnet:
is_testnet = lightningstub.GetInfo(lnrpc.GetInfoRequest()).testnet
if is_testnet:
dummy_address = "tb1qehyqhruxwl2p5pt52k6nxj4v8wwc3f3pg7377x"
else:
dummy_address = "bc1qgxwaqe4m9mypd7ltww53yv3lyxhcfnhzzvy5j3"
Expand Down
Empty file added tests/mocks/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions tests/mocks/lnd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from unittest.mock import MagicMock


# Mock up of LND gRPC responses
class MockLightningStub:
def GetInfo(self, request):
response = MagicMock()
# Set the testnet attribute to True for testing purposes
response.testnet = True
return response

def EstimateFee(self, request):
response = MagicMock()
response.fee_sat = 1500
response.sat_per_vbyte = 13
return response


class MockInvoicesStub:
pass


class MockRouterStub:
pass


class MockSignerStub:
pass


class MockVersionerStub:
pass
1 change: 1 addition & 0 deletions tests/robots/2/token
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oKrH73YD4ISQ0wzLNyPBeGp2OK7JTKghDfJe
110 changes: 70 additions & 40 deletions tests/test_trade_pipeline.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import json
from datetime import datetime
from decimal import Decimal
from unittest.mock import patch

from decouple import config
from django.contrib.auth.models import User
from django.test import Client, TestCase

from api.models import Currency, Order
from api.tasks import cache_market
from tests.mocks.lnd import (
MockInvoicesStub,
MockLightningStub,
MockRouterStub,
MockSignerStub,
MockVersionerStub,
)


class TradeTest(TestCase):
Expand All @@ -22,15 +30,18 @@ def setUp(self):
User.objects.create_superuser(self.su_name, "[email protected]", self.su_pass)

def test_login_superuser(self):
"""
Test logging in as a superuser.
"""
path = "/coordinator/login/"
data = {"username": self.su_name, "password": self.su_pass}
response = self.client.post(path, data)
self.assertEqual(response.status_code, 302)

def get_robot_auth(self, robot_index):
"""
Crates an AUTH header that embeds token, pub_key and enc_priv_key into a single string
just as requested by the robosats token middleware.
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()
Expand All @@ -44,24 +55,14 @@ def get_robot_auth(self, robot_index):
}
return headers, pub_key, enc_priv_key

def create_robot(self, robot_index):
"""
Creates the robots in /tests/robots/{robot_index}
"""
path = "/api/robot/"
headers, pub_key, enc_priv_key = self.get_robot_auth(robot_index)

response = self.client.get(path, **headers)
def assert_robot(self, response, pub_key, enc_priv_key, expected_nickname):
data = json.loads(response.content.decode())

with open(f"tests/robots/{robot_index}/nickname", "r") as file:
expected_nickname = file.read()

self.assertEqual(response.status_code, 200)
self.assertEqual(
data["nickname"],
expected_nickname,
f"Robot {robot_index} created nickname is not MyopicRacket333",
"Robot created nickname is not MyopicRacket333",
)
self.assertEqual(
data["public_key"], pub_key, "Returned public Kky does not match"
Expand All @@ -86,6 +87,20 @@ def create_robot(self, robot_index):
)
self.assertEqual(data["earned_rewards"], 0, "The new robot's rewards are not 0")

def create_robot(self, robot_index):
"""
Creates the robots in /tests/robots/{robot_index}
"""
path = "/api/robot/"
headers, pub_key, enc_priv_key = self.get_robot_auth(robot_index)

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)

def test_create_robots(self):
"""
Creates two robots to be used in the trade tests
Expand All @@ -97,13 +112,16 @@ def test_cache_market(self):
cache_market()

usd = Currency.objects.get(id=1)
self.assertTrue(
isinstance(usd.exchange_rate, Decimal), "Exchange rate is not decimal"
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.assertLess(0, usd.exchange_rate, "Exchange rate is not higher than zero")
self.assertTrue(
isinstance(usd.timestamp, datetime),
"Externa price timestamp is not datetime",
self.assertIsInstance(
usd.timestamp, datetime, "External price timestamp is not a datetime"
)

def test_create_order(
Expand Down Expand Up @@ -147,32 +165,34 @@ def test_create_order(
data = json.loads(response.content.decode())

# Checks
self.assertTrue(isinstance(data["id"], int), "Order ID is not an integer")
self.assertIsInstance(data["id"], int, "Order ID is not an integer")
self.assertEqual(
data["status"],
Order.Status.WFB,
"Newly created order status is not 'Waiting for maker bond'",
)
self.assertTrue(
isinstance(datetime.fromisoformat(data["created_at"]), datetime),
self.assertIsInstance(
datetime.fromisoformat(data["created_at"]),
datetime,
"Order creation timestamp is not datetime",
)
self.assertTrue(
isinstance(datetime.fromisoformat(data["expires_at"]), datetime),
self.assertIsInstance(
datetime.fromisoformat(data["expires_at"]),
datetime,
"Order expiry time is not datetime",
)
self.assertEqual(
data["type"], Order.Types.BUY, "Buy order is not of type value BUY"
)
self.assertEqual(data["currency"], 1, "Order for USD is not of currency USD")
self.assertIsNone(
data["amount"], "Order with range has a non null simple amount"
data["amount"], "Order with range has a non-null simple amount"
)
self.assertTrue(data["has_range"], "Order with range has a False has_range")
self.assertEqual(
self.assertAlmostEqual(
float(data["min_amount"]), min_amount, "Order min amount does not match"
)
self.assertEqual(
self.assertAlmostEqual(
float(data["max_amount"]), max_amount, "Order max amount does not match"
)
self.assertEqual(
Expand All @@ -185,31 +205,41 @@ def test_create_order(
escrow_duration,
"Order escrow duration does not match",
)
self.assertEqual(
self.assertAlmostEqual(
float(data["bond_size"]), bond_size, "Order bond size does not match"
)
self.assertEqual(
self.assertAlmostEqual(
float(data["latitude"]), latitude, "Order latitude does not match"
)
self.assertEqual(
self.assertAlmostEqual(
float(data["longitude"]), longitude, "Order longitude does not match"
)
self.assertEqual(
self.assertAlmostEqual(
float(data["premium"]), premium, "Order premium does not match"
)
self.assertFalse(
data["is_explicit"], "Relative pricing order has True is_explicit"
)
self.assertIsNone(
data["satoshis"], "Relative pricing order has non null Satoshis"
data["satoshis"], "Relative pricing order has non-null Satoshis"
)
self.assertIsNone(data["taker"], "New order's taker is not null")

with open(f"tests/robots/{robot_index}/nickname", "r") as file:
maker_nickname = file.read()
maker_id = User.objects.get(username=maker_nickname).id
self.assertEqual(
data["maker"],
maker_id,
"Maker user ID is not that of robot index {robot_index}",
@patch("api.lightning.lightning_pb2_grpc.LightningStub", MockLightningStub)
@patch("api.lightning.invoices_pb2_grpc.InvoicesStub", MockInvoicesStub)
@patch("api.lightning.router_pb2_grpc.RouterStub", MockRouterStub)
@patch("api.lightning.signer_pb2_grpc.SignerStub", MockSignerStub)
@patch("api.lightning.verrpc_pb2_grpc.VersionerStub", MockVersionerStub)
def test_maker_bond_locked(self):
self.test_create_order(
robot_index=1,
payment_method="Cash F2F",
min_amount=80,
max_amount=500,
premium=5,
public_duration=86000,
escrow_duration=8000,
bond_size=2,
latitude=0,
longitude=0,
)

0 comments on commit 7e4ddf2

Please sign in to comment.