mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-22 12:53:16 +00:00
Tests
This commit is contained in:
@ -11,7 +11,9 @@ RUN apt-get update -qq && \
|
|||||||
libpq-dev \
|
libpq-dev \
|
||||||
curl \
|
curl \
|
||||||
build-essential \
|
build-essential \
|
||||||
gnupg2
|
gnupg2 \
|
||||||
|
pkg-config \
|
||||||
|
libsecp256k1-dev
|
||||||
|
|
||||||
RUN python -m pip install --upgrade pip
|
RUN python -m pip install --upgrade pip
|
||||||
|
|
||||||
|
18
api/nostr.py
18
api/nostr.py
@ -2,9 +2,9 @@ import pygeohash
|
|||||||
import hashlib
|
import hashlib
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from secp256k1 import PrivateKey, PublicKey, ALL_FLAGS
|
from secp256k1 import PrivateKey
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, Kind, Tag
|
from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, Kind, Tag, PublicKey
|
||||||
from api.models import Order
|
from api.models import Order
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
|
||||||
@ -113,23 +113,23 @@ class Nostr:
|
|||||||
return ["onchain", "lightning"]
|
return ["onchain", "lightning"]
|
||||||
else:
|
else:
|
||||||
return ["lightning"]
|
return ["lightning"]
|
||||||
|
return False
|
||||||
|
|
||||||
def is_valid_public_key(public_key_hex):
|
def is_valid_public_key(public_key_hex):
|
||||||
try:
|
try:
|
||||||
public_key_bytes = bytes.fromhex(public_key_hex)
|
PublicKey.from_hex(public_key_hex)
|
||||||
PublicKey(public_key_bytes, raw=True)
|
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def sign_message(text: str) -> str:
|
def sign_message(text: str) -> str:
|
||||||
try:
|
try:
|
||||||
private_key = config("NOSTR_NSEC", cast=str)
|
keys = Keys.parse(config("NOSTR_NSEC", cast=str))
|
||||||
privkey = PrivateKey(
|
secret_key_hex = keys.secret_key().to_hex()
|
||||||
bytes.fromhex(private_key), raw=True, ctx_flags=ALL_FLAGS
|
private_key = PrivateKey(bytes.fromhex(secret_key_hex))
|
||||||
|
signature = private_key.schnorr_sign(
|
||||||
|
text.encode("utf-8"), bip340tag=None, raw=True
|
||||||
)
|
)
|
||||||
hashed_message = hashlib.sha256(text.encode("utf-8")).digest()
|
|
||||||
signature = privkey.schnorr_sign(hashed_message)
|
|
||||||
|
|
||||||
return signature.hex()
|
return signature.hex()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -1046,9 +1046,6 @@ class ReviewView(APIView):
|
|||||||
|
|
||||||
@extend_schema(**ReviewViewSchema.post)
|
@extend_schema(**ReviewViewSchema.post)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
if config("NOSTR_NSEC", cast=str, default="") == "":
|
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
|
@ -493,6 +493,10 @@ paths:
|
|||||||
- `17` - Maker lost dispute
|
- `17` - Maker lost dispute
|
||||||
- `18` - Taker lost dispute
|
- `18` - Taker lost dispute
|
||||||
|
|
||||||
|
The client can use `cancel_status` to cancel the order only
|
||||||
|
if it is in the specified status. The server will
|
||||||
|
return an error without cancelling the trade otherwise.
|
||||||
|
|
||||||
Note that there are penalties involved for cancelling a order
|
Note that there are penalties involved for cancelling a order
|
||||||
mid-trade so use this action carefully:
|
mid-trade so use this action carefully:
|
||||||
|
|
||||||
@ -652,6 +656,44 @@ paths:
|
|||||||
timestamp: '2022-09-13T14:32:40.591774Z'
|
timestamp: '2022-09-13T14:32:40.591774Z'
|
||||||
summary: Truncated example. Real response contains all the currencies
|
summary: Truncated example. Real response contains all the currencies
|
||||||
description: ''
|
description: ''
|
||||||
|
/api/review/:
|
||||||
|
post:
|
||||||
|
operationId: review_create
|
||||||
|
description: Generates the token necesary for reviews of robot's latest order
|
||||||
|
summary: Generates a review token
|
||||||
|
tags:
|
||||||
|
- review
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Review'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Review'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Review'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- tokenAuth: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Review'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
bad_request:
|
||||||
|
type: string
|
||||||
|
description: Reason for the failure
|
||||||
|
description: ''
|
||||||
/api/reward/:
|
/api/reward/:
|
||||||
post:
|
post:
|
||||||
operationId: reward_create
|
operationId: reward_create
|
||||||
@ -1775,6 +1817,14 @@ components:
|
|||||||
* `3` - 3
|
* `3` - 3
|
||||||
* `4` - 4
|
* `4` - 4
|
||||||
* `5` - 5
|
* `5` - 5
|
||||||
|
Review:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pubkey:
|
||||||
|
type: string
|
||||||
|
description: Robot's nostr hex pubkey
|
||||||
|
required:
|
||||||
|
- pubkey
|
||||||
StatusEnum:
|
StatusEnum:
|
||||||
enum:
|
enum:
|
||||||
- 0
|
- 0
|
||||||
@ -1975,8 +2025,33 @@ components:
|
|||||||
pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
|
pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
|
||||||
nullable: true
|
nullable: true
|
||||||
cancel_status:
|
cancel_status:
|
||||||
allOf:
|
nullable: true
|
||||||
|
description: |-
|
||||||
|
Status the order should have for it to be cancelled.
|
||||||
|
|
||||||
|
* `0` - Waiting for maker bond
|
||||||
|
* `1` - Public
|
||||||
|
* `2` - Paused
|
||||||
|
* `3` - Waiting for taker bond
|
||||||
|
* `4` - Cancelled
|
||||||
|
* `5` - Expired
|
||||||
|
* `6` - Waiting for trade collateral and buyer invoice
|
||||||
|
* `7` - Waiting only for seller trade collateral
|
||||||
|
* `8` - Waiting only for buyer invoice
|
||||||
|
* `9` - Sending fiat - In chatroom
|
||||||
|
* `10` - Fiat sent - In chatroom
|
||||||
|
* `11` - In dispute
|
||||||
|
* `12` - Collaboratively cancelled
|
||||||
|
* `13` - Sending satoshis to buyer
|
||||||
|
* `14` - Successful trade
|
||||||
|
* `15` - Failed lightning network routing
|
||||||
|
* `16` - Wait for dispute resolution
|
||||||
|
* `17` - Maker lost dispute
|
||||||
|
* `18` - Taker lost dispute
|
||||||
|
oneOf:
|
||||||
- $ref: '#/components/schemas/StatusEnum'
|
- $ref: '#/components/schemas/StatusEnum'
|
||||||
|
- $ref: '#/components/schemas/BlankEnum'
|
||||||
|
- $ref: '#/components/schemas/NullEnum'
|
||||||
required:
|
required:
|
||||||
- action
|
- action
|
||||||
Version:
|
Version:
|
||||||
|
1
tests/robots/1/nostr_pubkey
Normal file
1
tests/robots/1/nostr_pubkey
Normal file
@ -0,0 +1 @@
|
|||||||
|
aa19b79461dbd92673900cd50f51934ec648f8dbb79cb2e3e59929e798d41e86
|
1
tests/robots/2/nostr_pubkey
Normal file
1
tests/robots/2/nostr_pubkey
Normal file
@ -0,0 +1 @@
|
|||||||
|
b905e7adcee82cc7fe3ba05c8f0922c9098a872f71db4ba70c216e4396d73be8
|
1
tests/robots/3/nostr_pubkey
Normal file
1
tests/robots/3/nostr_pubkey
Normal file
@ -0,0 +1 @@
|
|||||||
|
86127e010ee323f30c7935e70393a9acef7f269b88cc3b1610e336becfbfc8f2
|
@ -965,6 +965,46 @@ class TradeTest(BaseAPITestCase):
|
|||||||
|
|
||||||
self.assert_order_logs(data["id"])
|
self.assert_order_logs(data["id"])
|
||||||
|
|
||||||
|
def test_review_order(self):
|
||||||
|
"""
|
||||||
|
Tests a trade review token generation after the trade ends
|
||||||
|
"""
|
||||||
|
trade = Trade(self.client)
|
||||||
|
trade.publish_order()
|
||||||
|
|
||||||
|
trade.get_review()
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
|
||||||
|
trade.take_order()
|
||||||
|
trade.take_order_third()
|
||||||
|
trade.lock_taker_bond()
|
||||||
|
|
||||||
|
trade.get_review(trade.maker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
trade.get_review(trade.taker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
|
||||||
|
trade.lock_escrow(trade.taker_index)
|
||||||
|
trade.submit_payout_address(trade.maker_index)
|
||||||
|
trade.confirm_fiat(trade.maker_index)
|
||||||
|
trade.confirm_fiat(trade.taker_index)
|
||||||
|
|
||||||
|
trade.process_payouts(mine_a_block=True)
|
||||||
|
|
||||||
|
trade.get_review(trade.maker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 200)
|
||||||
|
nostr_pubkey = read_file(f"tests/robots/{trade.maker_index}/nostr_pubkey")
|
||||||
|
data = trade.response.json()
|
||||||
|
self.assertEqual(data["pubkey"], nostr_pubkey)
|
||||||
|
self.assertIsInstance(data["token"], str)
|
||||||
|
|
||||||
|
trade.get_review(trade.taker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 200)
|
||||||
|
nostr_pubkey = read_file(f"tests/robots/{trade.taker_index}/nostr_pubkey")
|
||||||
|
data = trade.response.json()
|
||||||
|
self.assertEqual(data["pubkey"], nostr_pubkey)
|
||||||
|
self.assertIsInstance(data["token"], str)
|
||||||
|
|
||||||
def test_cancel_public_order(self):
|
def test_cancel_public_order(self):
|
||||||
"""
|
"""
|
||||||
Tests the cancellation of a public order
|
Tests the cancellation of a public order
|
||||||
@ -993,6 +1033,9 @@ class TradeTest(BaseAPITestCase):
|
|||||||
f"❌ Hey {maker_nick}, you have cancelled your public order with ID {trade.order_id}.",
|
f"❌ Hey {maker_nick}, you have cancelled your public order with ID {trade.order_id}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trade.get_review()
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
|
||||||
def test_cancel_public_order_by_taker(self):
|
def test_cancel_public_order_by_taker(self):
|
||||||
"""
|
"""
|
||||||
Tests the cancellation of a public order by a pretaker
|
Tests the cancellation of a public order by a pretaker
|
||||||
@ -1121,7 +1164,8 @@ class TradeTest(BaseAPITestCase):
|
|||||||
self.assertResponse(trade.response)
|
self.assertResponse(trade.response)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
trade.response.json()["bad_request"], "This order has been cancelled by the maker"
|
trade.response.json()["bad_request"],
|
||||||
|
"This order has been cancelled by the maker",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_cancel_order_different_cancel_status(self):
|
def test_cancel_order_different_cancel_status(self):
|
||||||
@ -1147,7 +1191,7 @@ class TradeTest(BaseAPITestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
trade.response.json()["bad_request"],
|
trade.response.json()["bad_request"],
|
||||||
f"Current order status is {Order.Status.PAU}, not {Order.Status.PUB}."
|
f"Current order status is {Order.Status.PAU}, not {Order.Status.PUB}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cancel order to avoid leaving pending HTLCs after a successful test
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
@ -1278,6 +1322,9 @@ class TradeTest(BaseAPITestCase):
|
|||||||
f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.",
|
f"😪 Hey {data['maker_nick']}, your order with ID {str(trade.order_id)} has expired without a taker.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trade.get_review(trade.maker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
|
||||||
def test_taken_order_expires(self):
|
def test_taken_order_expires(self):
|
||||||
"""
|
"""
|
||||||
Tests the expiration of a public order
|
Tests the expiration of a public order
|
||||||
@ -1708,6 +1755,11 @@ class TradeTest(BaseAPITestCase):
|
|||||||
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
f"⚖️ Hey {data['taker_nick']}, a dispute has been opened on your order with ID {str(trade.order_id)}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trade.get_review(trade.maker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
trade.get_review(trade.taker_index)
|
||||||
|
self.assertEqual(trade.response.status_code, 400)
|
||||||
|
|
||||||
def test_ticks(self):
|
def test_ticks(self):
|
||||||
"""
|
"""
|
||||||
Tests the historical ticks serving endpoint after creating a contract
|
Tests the historical ticks serving endpoint after creating a contract
|
||||||
|
@ -113,6 +113,16 @@ class Trade:
|
|||||||
headers = self.get_robot_auth(robot_index, first_encounter)
|
headers = self.get_robot_auth(robot_index, first_encounter)
|
||||||
self.response = self.client.get(path + params, **headers)
|
self.response = self.client.get(path + params, **headers)
|
||||||
|
|
||||||
|
def get_review(self, robot_index=1):
|
||||||
|
"""
|
||||||
|
Generates coordinator's review signature
|
||||||
|
"""
|
||||||
|
path = reverse("review")
|
||||||
|
headers = self.get_robot_auth(robot_index)
|
||||||
|
nostr_pubkey = read_file(f"tests/robots/{robot_index}/nostr_pubkey")
|
||||||
|
body = {"pubkey": nostr_pubkey}
|
||||||
|
self.response = self.client.post(path, body, **headers)
|
||||||
|
|
||||||
@patch("api.tasks.send_notification.delay", send_notification)
|
@patch("api.tasks.send_notification.delay", send_notification)
|
||||||
def cancel_order(self, robot_index=1, cancel_status=None):
|
def cancel_order(self, robot_index=1, cancel_status=None):
|
||||||
path = reverse("order")
|
path = reverse("order")
|
||||||
|
Reference in New Issue
Block a user