diff --git a/squeaknode/node/price_policy.py b/squeaknode/node/price_policy.py new file mode 100644 index 00000000..a06d90c9 --- /dev/null +++ b/squeaknode/node/price_policy.py @@ -0,0 +1,65 @@ +# MIT License +# +# Copyright (c) 2020 Jonathan Zernik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import logging +from typing import Optional + +from squeak.core import CSqueak + +from squeaknode.config.config import SqueaknodeConfig +from squeaknode.core.peer_address import PeerAddress +from squeaknode.core.squeak_peer import SqueakPeer +from squeaknode.core.squeak_profile import SqueakProfile +from squeaknode.db.squeak_db import SqueakDb + + +logger = logging.getLogger(__name__) + + +class PricePolicy: + + def __init__(self, squeak_db: SqueakDb, config: SqueaknodeConfig): + self.squeak_db = squeak_db + self.config = config + + def get_price(self, squeak: CSqueak, peer_address: PeerAddress) -> int: + """Get the price to sell this squeak to this peer. + + """ + # Return zero for price if peer is configured to be share for free. + peer = self.get_peer(peer_address) + if peer is not None and peer.share_for_free: + return 0 + # Return custom price if address is configured with custom price. + squeak_address = str(squeak.GetAddress()) + squeak_profile = self.get_profile(squeak_address) + if squeak_profile is not None and squeak_profile.use_custom_price: + return squeak_profile.custom_price_msat + return self.get_default_price() + + def get_peer(self, peer_address: PeerAddress) -> Optional[SqueakPeer]: + return self.squeak_db.get_peer_by_address(peer_address) + + def get_profile(self, address: str) -> Optional[SqueakProfile]: + return self.squeak_db.get_profile_by_address(address) + + def get_default_price(self) -> int: + return self.config.node.price_msat diff --git a/squeaknode/node/squeak_controller.py b/squeaknode/node/squeak_controller.py index f684c539..09b681a3 100644 --- a/squeaknode/node/squeak_controller.py +++ b/squeaknode/node/squeak_controller.py @@ -65,6 +65,7 @@ from squeaknode.core.update_twitter_stream_event import UpdateTwitterStreamEvent from squeaknode.core.user_config import UserConfig from squeaknode.node.active_download_manager import ActiveDownload from squeaknode.node.listener_subscription_client import EventListener +from squeaknode.node.price_policy import PricePolicy from squeaknode.node.received_payments_subscription_client import ReceivedPaymentsSubscriptionClient @@ -232,16 +233,8 @@ class SqueakController: return sent_offer def get_price_for_squeak(self, squeak: CSqueak, peer_address: PeerAddress) -> int: - # Return zero for price if peer is configured to be share for free. - peer = self.squeak_db.get_peer_by_address(peer_address) - if peer is not None and peer.share_for_free: - return 0 - # Return custom price if address is configured with custom price. - squeak_address = str(squeak.GetAddress()) - squeak_profile = self.get_squeak_profile_by_address(squeak_address) - if squeak_profile is not None and squeak_profile.use_custom_price: - return squeak_profile.custom_price_msat - return self.config.node.price_msat + price_policy = PricePolicy(self.squeak_db, self.config) + return price_policy.get_price(squeak, peer_address) def create_signing_profile(self, profile_name: str) -> int: squeak_profile = create_signing_profile( diff --git a/tests/node/test_price_policy.py b/tests/node/test_price_policy.py new file mode 100644 index 00000000..2542aa37 --- /dev/null +++ b/tests/node/test_price_policy.py @@ -0,0 +1,95 @@ +# MIT License +# +# Copyright (c) 2020 Jonathan Zernik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import mock +import pytest + +from squeaknode.node.price_policy import PricePolicy + + +@pytest.fixture() +def price_policy(): + yield PricePolicy(None, None) + + +def test_get_price(price_policy, squeak, peer_address): + with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ + mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \ + mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock_get_peer.return_value = None + mock_get_profile.return_value = None + mock_get_default_price.return_value = 555 + + assert price_policy.get_price(squeak, peer_address) == 555 + + +def test_get_price_profile_custom_price(price_policy, squeak, peer_address, signing_profile): + with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ + mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \ + mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock_get_peer.return_value = None + mock_get_profile.return_value = signing_profile._replace( + use_custom_price=True, + custom_price_msat=54321, + ) + mock_get_default_price.return_value = 555 + + assert price_policy.get_price(squeak, peer_address) == 54321 + + +def test_get_price_profile_no_custom_price(price_policy, squeak, peer_address, signing_profile): + with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ + mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \ + mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock_get_peer.return_value = None + mock_get_profile.return_value = signing_profile._replace( + use_custom_price=False, + custom_price_msat=54321, + ) + mock_get_default_price.return_value = 555 + + assert price_policy.get_price(squeak, peer_address) == 555 + + +def test_get_price_profile_share_free_peer(price_policy, squeak, peer_address, peer): + with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ + mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \ + mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock_get_peer.return_value = peer._replace( + share_for_free=True, + ) + mock_get_profile.return_value = None + mock_get_default_price.return_value = 555 + + assert price_policy.get_price(squeak, peer_address) == 0 + + +def test_get_price_profile_no_share_free_peer(price_policy, squeak, peer_address, peer): + with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ + mock.patch.object(price_policy, 'get_profile', autospec=True) as mock_get_profile, \ + mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock_get_peer.return_value = peer._replace( + share_for_free=False, + ) + mock_get_profile.return_value = None + mock_get_default_price.return_value = 555 + + assert price_policy.get_price(squeak, peer_address) == 555