diff --git a/api/admin.py b/api/admin.py index d853d7da..751a2a07 100644 --- a/api/admin.py +++ b/api/admin.py @@ -133,7 +133,13 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): if not obj.logs: return format_html("No logs were recorded") with_hyperlinks = objects_to_hyperlinks(obj.logs) - return format_html(f'{with_hyperlinks}
') + try: + html_logs = format_html( + f'{with_hyperlinks}
' + ) + except Exception as e: + html_logs = f"An error occurred while formatting the parsed logs as HTML. Exception {e}" + return html_logs actions = [ "cancel_public_order", diff --git a/api/utils.py b/api/utils.py index 0eed88d9..85b5dbcf 100644 --- a/api/utils.py +++ b/api/utils.py @@ -488,11 +488,19 @@ def objects_to_hyperlinks(logs: str) -> str: Used to format pretty logs for the Order admin panel. """ objects = ["LNPayment", "Robot", "Order", "OnchainPayment", "MarketTick"] - for obj in objects: - logs = re.sub( - rf"{obj}\(([0-9a-fA-F\-A-F]+),\s*([^)]+)\)", - lambda m: f'{m.group(2)}', - logs, - flags=re.DOTALL, - ) + try: + for obj in objects: + logs = re.sub( + rf"{obj}\(([0-9a-fA-F\-A-F]+),\s*([^)]+)\)", + lambda m: f'{m.group(2)}', + logs, + flags=re.DOTALL, + ) + + except re.error as e: + print("Error occurred:", e.msg) + print("Pattern:", e.pattern) + print("Position:", e.pos) + logs = f"An error occurred while parsing the logs. Exception {e}" + return logs diff --git a/docs/assets/schemas/api-latest.yaml b/docs/assets/schemas/api-latest.yaml index 055f0434..ebb473f4 100644 --- a/docs/assets/schemas/api-latest.yaml +++ b/docs/assets/schemas/api-latest.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: RoboSats REST API - version: 0.5.3 + version: 0.5.4 x-logo: url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png backgroundColor: '#FFFFFF' diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index a3671d50..4205b98c 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -7,6 +7,7 @@ from django.urls import reverse from api.models import Currency, Order from api.tasks import cache_market +from django.contrib.admin.sites import AdminSite from control.models import BalanceLog from control.tasks import compute_node_balance, do_accounting from tests.test_api import BaseAPITestCase @@ -14,6 +15,8 @@ from tests.utils.node import add_invoice, set_up_regtest_network from tests.utils.pgp import sign_message from tests.utils.trade import Trade +from api.admin import OrderAdmin + def read_file(file_path): """ @@ -44,6 +47,15 @@ class TradeTest(BaseAPITestCase): # Take the first node balances snapshot compute_node_balance() + def assert_order_logs(self, order_id): + order = Order.objects.get(id=order_id) + order_admin = OrderAdmin(model=Order, admin_site=AdminSite()) + try: + result = order_admin._logs(order) + self.assertIsInstance(result, str) + except Exception as e: + self.fail(f"Exception occurred: {e}") + def test_login_superuser(self): """ Test the login functionality for the superuser. @@ -225,6 +237,7 @@ class TradeTest(BaseAPITestCase): data["satoshis"], "Relative pricing order has non-null Satoshis" ) self.assertIsNone(data["taker"], "New order's taker is not null") + self.assert_order_logs(data["id"]) def test_get_order_created(self): """ @@ -270,6 +283,8 @@ class TradeTest(BaseAPITestCase): # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() + self.assert_order_logs(data["id"]) + def test_publish_order(self): """ Tests a trade from order creation to published (maker bond locked). @@ -298,6 +313,8 @@ class TradeTest(BaseAPITestCase): # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() + self.assert_order_logs(data["id"]) + def test_pause_unpause_order(self): """ Tests pausing and unpausing a public order @@ -323,6 +340,8 @@ class TradeTest(BaseAPITestCase): # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() + self.assert_order_logs(data["id"]) + def test_make_and_take_order(self): """ Tests a trade from order creation to taken. @@ -362,6 +381,8 @@ class TradeTest(BaseAPITestCase): # Cancel order to avoid leaving pending HTLCs after a successful test trade.cancel_order() + self.assert_order_logs(data["id"]) + def test_make_and_lock_contract(self): """ Tests a trade from order creation to taker bond locked. @@ -407,6 +428,8 @@ class TradeTest(BaseAPITestCase): # Maker cancels order to avoid leaving pending HTLCs after a successful test trade.cancel_order() + self.assert_order_logs(data["id"]) + def test_trade_to_locked_escrow(self): """ Tests a trade from order creation until escrow locked, before @@ -455,6 +478,8 @@ class TradeTest(BaseAPITestCase): trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) + self.assert_order_logs(data["id"]) + def test_trade_to_submitted_invoice(self): """ Tests a trade from order creation until escrow locked and @@ -509,6 +534,8 @@ class TradeTest(BaseAPITestCase): trade.cancel_order(trade.maker_index) trade.cancel_order(trade.taker_index) + self.assert_order_logs(data["id"]) + def test_trade_to_confirm_fiat_received_LN(self): """ Tests a trade from order creation until fiat received is confirmed by seller/taker @@ -534,6 +561,8 @@ class TradeTest(BaseAPITestCase): self.assertFalse(data["taker_locked"]) self.assertFalse(data["escrow_locked"]) + self.assert_order_logs(data["id"]) + def test_successful_LN(self): """ Tests a trade from order creation until Sats sent to buyer @@ -561,6 +590,8 @@ class TradeTest(BaseAPITestCase): self.assertIsHash(data["maker_summary"]["preimage"]) self.assertIsHash(data["maker_summary"]["payment_hash"]) + self.assert_order_logs(data["id"]) + def test_successful_onchain(self): """ Tests a trade from order creation until Sats sent to buyer @@ -588,6 +619,8 @@ class TradeTest(BaseAPITestCase): self.assertIsInstance(data["maker_summary"]["address"], str) self.assertIsHash(data["maker_summary"]["txid"]) + self.assert_order_logs(data["id"]) + def test_cancel_public_order(self): """ Tests the cancellation of a public order @@ -667,6 +700,8 @@ class TradeTest(BaseAPITestCase): ) self.assertEqual(data["expiry_reason"], Order.ExpiryReasons.NMBOND) + self.assert_order_logs(data["id"]) + def test_public_order_expires(self): """ Tests the expiration of a public order @@ -698,6 +733,8 @@ class TradeTest(BaseAPITestCase): ) self.assertEqual(data["expiry_reason"], Order.ExpiryReasons.NTAKEN) + self.assert_order_logs(data["id"]) + def test_taken_order_expires(self): """ Tests the expiration of a public order @@ -731,6 +768,8 @@ class TradeTest(BaseAPITestCase): ) self.assertEqual(data["expiry_reason"], Order.ExpiryReasons.NESINV) + self.assert_order_logs(data["id"]) + def test_escrow_locked_expires(self): """ Tests the expiration of a public order @@ -765,6 +804,8 @@ class TradeTest(BaseAPITestCase): ) self.assertEqual(data["expiry_reason"], Order.ExpiryReasons.NINVOI) + self.assert_order_logs(data["id"]) + def test_chat(self): """ Tests the chatting REST functionality @@ -871,6 +912,8 @@ class TradeTest(BaseAPITestCase): Order.Status.MLD, ) + self.assert_order_logs(data["id"]) + def test_order_expires_after_only_maker_messaged(self): """ Tests the expiration of an order in chat where taker never messaged @@ -911,6 +954,8 @@ class TradeTest(BaseAPITestCase): Order.Status.TLD, ) + self.assert_order_logs(data["id"]) + def test_withdraw_reward_after_unilateral_cancel(self): """ Tests withdraw rewards as taker after maker cancels order unilaterally @@ -979,6 +1024,8 @@ class TradeTest(BaseAPITestCase): Order.Status.DIS, ) + self.assert_order_logs(data["id"]) + def test_ticks(self): """ Tests the historical ticks serving endpoint after creating a contract