mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-21 18:23:34 +00:00
Tests
This commit is contained in:
@ -11,7 +11,9 @@ RUN apt-get update -qq && \
|
||||
libpq-dev \
|
||||
curl \
|
||||
build-essential \
|
||||
gnupg2
|
||||
gnupg2 \
|
||||
pkg-config \
|
||||
libsecp256k1-dev
|
||||
|
||||
RUN python -m pip install --upgrade pip
|
||||
|
||||
|
18
api/nostr.py
18
api/nostr.py
@ -2,9 +2,9 @@ import pygeohash
|
||||
import hashlib
|
||||
import uuid
|
||||
|
||||
from secp256k1 import PrivateKey, PublicKey, ALL_FLAGS
|
||||
from secp256k1 import PrivateKey
|
||||
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 decouple import config
|
||||
|
||||
@ -113,23 +113,23 @@ class Nostr:
|
||||
return ["onchain", "lightning"]
|
||||
else:
|
||||
return ["lightning"]
|
||||
return False
|
||||
|
||||
def is_valid_public_key(public_key_hex):
|
||||
try:
|
||||
public_key_bytes = bytes.fromhex(public_key_hex)
|
||||
PublicKey(public_key_bytes, raw=True)
|
||||
PublicKey.from_hex(public_key_hex)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def sign_message(text: str) -> str:
|
||||
try:
|
||||
private_key = config("NOSTR_NSEC", cast=str)
|
||||
privkey = PrivateKey(
|
||||
bytes.fromhex(private_key), raw=True, ctx_flags=ALL_FLAGS
|
||||
keys = Keys.parse(config("NOSTR_NSEC", cast=str))
|
||||
secret_key_hex = keys.secret_key().to_hex()
|
||||
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()
|
||||
except Exception:
|
||||
|
@ -1046,9 +1046,6 @@ class ReviewView(APIView):
|
||||
|
||||
@extend_schema(**ReviewViewSchema.post)
|
||||
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)
|
||||
|
||||
if not serializer.is_valid():
|
||||
|
@ -493,6 +493,10 @@ paths:
|
||||
- `17` - Maker 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
|
||||
mid-trade so use this action carefully:
|
||||
|
||||
@ -652,6 +656,44 @@ paths:
|
||||
timestamp: '2022-09-13T14:32:40.591774Z'
|
||||
summary: Truncated example. Real response contains all the currencies
|
||||
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/:
|
||||
post:
|
||||
operationId: reward_create
|
||||
@ -1775,6 +1817,14 @@ components:
|
||||
* `3` - 3
|
||||
* `4` - 4
|
||||
* `5` - 5
|
||||
Review:
|
||||
type: object
|
||||
properties:
|
||||
pubkey:
|
||||
type: string
|
||||
description: Robot's nostr hex pubkey
|
||||
required:
|
||||
- pubkey
|
||||
StatusEnum:
|
||||
enum:
|
||||
- 0
|
||||
@ -1975,8 +2025,33 @@ components:
|
||||
pattern: ^-?\d{0,3}(?:\.\d{0,3})?$
|
||||
nullable: true
|
||||
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/BlankEnum'
|
||||
- $ref: '#/components/schemas/NullEnum'
|
||||
required:
|
||||
- action
|
||||
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"])
|
||||
|
||||
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):
|
||||
"""
|
||||
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}.",
|
||||
)
|
||||
|
||||
trade.get_review()
|
||||
self.assertEqual(trade.response.status_code, 400)
|
||||
|
||||
def test_cancel_public_order_by_taker(self):
|
||||
"""
|
||||
Tests the cancellation of a public order by a pretaker
|
||||
@ -1121,7 +1164,8 @@ class TradeTest(BaseAPITestCase):
|
||||
self.assertResponse(trade.response)
|
||||
|
||||
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):
|
||||
@ -1147,7 +1191,7 @@ class TradeTest(BaseAPITestCase):
|
||||
|
||||
self.assertEqual(
|
||||
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
|
||||
@ -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.",
|
||||
)
|
||||
|
||||
trade.get_review(trade.maker_index)
|
||||
self.assertEqual(trade.response.status_code, 400)
|
||||
|
||||
def test_taken_order_expires(self):
|
||||
"""
|
||||
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)}.",
|
||||
)
|
||||
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
def cancel_order(self, robot_index=1, cancel_status=None):
|
||||
path = reverse("order")
|
||||
|
Reference in New Issue
Block a user