diff --git a/src/chains/signals.py b/src/chains/signals.py index 53c55947..f670283f 100644 --- a/src/chains/signals.py +++ b/src/chains/signals.py @@ -1,11 +1,10 @@ import logging from typing import Any -from django.conf import settings from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete from django.dispatch import receiver -from clients.safe_client_gateway import HookEvent, flush, hook_event +from clients.safe_client_gateway import HookEvent, hook_event from .models import Chain, Feature, GasPrice, Wallet @@ -16,22 +15,14 @@ @receiver(post_delete, sender=Chain) def on_chain_update(sender: Chain, instance: Chain, **kwargs: Any) -> None: logger.info("Chain update. Triggering CGW webhook") - if settings.FF_HOOK_EVENTS: - hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=instance.id)) - else: - flush() + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=instance.id)) @receiver(post_save, sender=GasPrice) @receiver(post_delete, sender=GasPrice) def on_gas_price_update(sender: GasPrice, instance: GasPrice, **kwargs: Any) -> None: logger.info("GasPrice update. Triggering CGW webhook") - if settings.FF_HOOK_EVENTS: - hook_event( - HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=instance.chain.id) - ) - else: - flush() + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=instance.chain.id)) # pre_delete is used because on pre_delete the model still has chains @@ -40,12 +31,9 @@ def on_gas_price_update(sender: GasPrice, instance: GasPrice, **kwargs: Any) -> @receiver(pre_delete, sender=Feature) def on_feature_changed(sender: Feature, instance: Feature, **kwargs: Any) -> None: logger.info("Feature update. Triggering CGW webhook") - if settings.FF_HOOK_EVENTS: - # A Feature change affects all the chains that have this feature - for chain in instance.chains.all(): - hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain.id)) - else: - flush() + # A Feature change affects all the chains that have this feature + for chain in instance.chains.all(): + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain.id)) @receiver(m2m_changed, sender=Feature.chains.through) @@ -54,13 +42,8 @@ def on_feature_chains_changed( ) -> None: logger.info("FeatureChains update. Triggering CGW webhook") if action == "post_add" or action == "post_remove": - if settings.FF_HOOK_EVENTS: - for chain_id in pk_set: - hook_event( - HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain_id) - ) - else: - flush() + for chain_id in pk_set: + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain_id)) # pre_delete is used because on pre_delete the model still has chains @@ -69,12 +52,9 @@ def on_feature_chains_changed( @receiver(pre_delete, sender=Wallet) def on_wallet_changed(sender: Wallet, instance: Wallet, **kwargs: Any) -> None: logger.info("Wallet update. Triggering CGW webhook") - if settings.FF_HOOK_EVENTS: - # A Wallet change affects all the chains that have this wallet - for chain in instance.chains.all(): - hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain.id)) - else: - flush() + # A Wallet change affects all the chains that have this wallet + for chain in instance.chains.all(): + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain.id)) @receiver(m2m_changed, sender=Wallet.chains.through) @@ -83,10 +63,5 @@ def on_wallet_chains_changed( ) -> None: logger.info("WalletChains update. Triggering CGW webhook") if action == "post_add" or action == "post_remove": - if settings.FF_HOOK_EVENTS: - for chain_id in pk_set: - hook_event( - HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain_id) - ) - else: - flush() + for chain_id in pk_set: + hook_event(HookEvent(type=HookEvent.Type.CHAIN_UPDATE, chain_id=chain_id)) diff --git a/src/chains/tests/test_signals.py b/src/chains/tests/test_signals.py index 79404035..1b22446b 100644 --- a/src/chains/tests/test_signals.py +++ b/src/chains/tests/test_signals.py @@ -1,88 +1,101 @@ import responses from django.test import TestCase, override_settings +from faker import Faker from ..models import Feature, Wallet -from ..tests.factories import ChainFactory, GasPriceFactory +from .factories import ChainFactory, FeatureFactory, GasPriceFactory, WalletFactory +fake = Faker() +Faker.seed(0) -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) + +class ChainNetworkHookTestCaseSetupCheck(TestCase): + @responses.activate + @override_settings(CGW_URL=None, CGW_FLUSH_TOKEN="example-token") + def test_no_cgw_call_with_no_url(self) -> None: + ChainFactory.create() + + assert len(responses.calls) == 0 + + @responses.activate + @override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN=None) + def test_no_cgw_call_with_no_token(self) -> None: + ChainFactory.create() + + assert len(responses.calls) == 0 + + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") class ChainNetworkHookTestCase(TestCase): @responses.activate - def test_on_chain_update_hook_200(self) -> None: + def test_on_chain_create(self) -> None: + chain_id = fake.pyint() responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "CHAIN_UPDATE", "chainId": str(chain_id)} + ), ], ) - ChainFactory.create() + ChainFactory.create(id=chain_id) assert len(responses.calls) == 1 assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + assert responses.calls[ + 0 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[0].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_chain_update_hook_400(self) -> None: + def test_on_chain_delete(self) -> None: + # Deleting an object sets the primary key to None so we set it in a separate variable + chain_id = fake.pyint() + chain = ChainFactory.create(id=chain_id) responses.add( responses.POST, - "http://127.0.0.1/v2/flush", - status=400, + "http://127.0.0.1/v1/hooks/events", + status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], - ) - - ChainFactory.create() - - assert len(responses.calls) == 1 - - @responses.activate - def test_on_chain_update_hook_500(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=500, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} + responses.matchers.json_params_matcher( + {"type": "CHAIN_UPDATE", "chainId": str(chain.id)} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), ], ) - ChainFactory.create() - - assert len(responses.calls) == 1 - - @responses.activate - def test_on_chain_delete_hook_call(self) -> None: - chain = ChainFactory.create() - chain.delete() # 2 calls: one for creation and one for deletion assert len(responses.calls) == 2 + assert isinstance(responses.calls[1], responses.Call) + assert responses.calls[ + 1 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[1].request.headers.get("Authorization") + == "Basic example-token" + ) @responses.activate - def test_on_chain_update_hook_call(self) -> None: + def test_on_chain_update(self) -> None: chain = ChainFactory.create() # Not updating using queryset because hooks are not triggered that way @@ -91,139 +104,272 @@ def test_on_chain_update_hook_call(self) -> None: # 2 calls: one for creation and one for updating assert len(responses.calls) == 2 + assert isinstance(responses.calls[1], responses.Call) + assert responses.calls[ + 1 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[0].request.headers.get("Authorization") + == "Basic example-token" + ) - @override_settings( - CGW_URL=None, - CGW_FLUSH_TOKEN=None, - ) - @responses.activate - def test_on_chain_update_with_no_cgw_set(self) -> None: - ChainFactory.create() - - assert len(responses.calls) == 0 - @override_settings( - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN=None, - ) +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") +class FeatureHookTestCase(TestCase): @responses.activate - def test_on_chain_update_with_no_flush_token_set(self) -> None: - ChainFactory.create() + def test_on_feature_create_with_no_chain(self) -> None: + Feature(key="Test Feature").save() + # Creating a feature with no chains should not trigger any webhook assert len(responses.calls) == 0 - -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class FeatureHookTestCase(TestCase): @responses.activate - def test_on_feature_create_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], + def test_on_feature_create_with_chain(self) -> None: + chain = ChainFactory.create() + FeatureFactory.create(key="Test Feature", chains=(chain,)) + + # 1 call for Chain creation, 1 call for feature creation, 1 call for M2M update + assert len(responses.calls) == 3 + assert isinstance(responses.calls[2], responses.Call) + assert responses.calls[ + 2 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" ) - - Feature(key="Test Feature").save() - - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" assert ( - responses.calls[0].request.headers.get("Authorization") + responses.calls[2].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_feature_delete_hook_call(self) -> None: + def test_on_feature_delete_with_no_chain(self) -> None: feature = Feature(key="Test Feature") feature.save() # create feature.delete() # delete - # 2 calls: one for creation and one for deletion - assert len(responses.calls) == 2 + # Deleting a feature with no chains should not trigger any webhook + assert len(responses.calls) == 0 @responses.activate - def test_on_feature_update_hook_call(self) -> None: + def test_on_feature_delete_with_chain(self) -> None: + chain = ChainFactory.create() + feature = FeatureFactory.create(key="Test Feature", chains=(chain,)) + + feature.delete() + + # 1 call for Chain creation, 1 call for feature creation, 1 call for M2M update, 1 call for feature deletion + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) + + @responses.activate + def test_on_feature_update_with_no_chain(self) -> None: feature = Feature(key="Test Feature") feature.save() # create feature.key = "New Test Feature" feature.save() # update - # 2 calls: one for creation and one for updating - assert len(responses.calls) == 2 + # Updating a feature with no chains should not trigger any webhook + assert len(responses.calls) == 0 + @responses.activate + def test_on_feature_update_with_chain(self) -> None: + chain = ChainFactory.create() + feature = FeatureFactory.create(key="Test Feature", chains=(chain,)) + + feature.chains.remove(chain) + + # 1 call for Chain creation, 1 call for feature creation, + # 1 call for M2M update, 1 call for removing m2m relationship + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class WalletHookTestCase(TestCase): @responses.activate - def test_on_wallet_create_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], + def test_on_feature_update_with_multiple_chains(self) -> None: + chain_1 = ChainFactory.create() + chain_2 = ChainFactory.create() + + FeatureFactory.create(key="Test Feature", chains=(chain_1, chain_2)) + + # 1 call for Chain 1 creation, 1 call for Chain 2 creation, 1 call for feature creation, + # 1 call for Chain 1 M2M update, 1 call for Chain 2 M2M update, 1 call for Feature update + assert len(responses.calls) == 6 + assert isinstance(responses.calls[3], responses.Call) + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_2.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_1.id}"}}'.encode( + "utf-8" ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) + assert ( + responses.calls[4].request.headers.get("Authorization") + == "Basic example-token" + ) + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") +class WalletHookTestCase(TestCase): + @responses.activate + def test_on_wallet_create_with_no_chain(self) -> None: Wallet(key="Test Wallet").save() - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + # Creating a wallet with no chains should not trigger any webhook + assert len(responses.calls) == 0 + + @responses.activate + def test_on_wallet_create_with_chain(self) -> None: + chain = ChainFactory.create() + WalletFactory.create(key="Test Wallet", chains=(chain,)) + + # 1 call for Chain creation, 1 call for Wallet creation, 1 call for M2M update + assert len(responses.calls) == 3 + assert isinstance(responses.calls[2], responses.Call) + assert responses.calls[ + 2 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" assert ( - responses.calls[0].request.headers.get("Authorization") + responses.calls[2].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_wallet_delete_hook_call(self) -> None: + def test_on_wallet_delete_with_no_chain(self) -> None: wallet = Wallet(key="Test Wallet") wallet.save() # create wallet.delete() # delete - # 2 calls: one for creation and one for deletion - assert len(responses.calls) == 2 + # deleting a wallet with no chains should not trigger any webhook + assert len(responses.calls) == 0 @responses.activate - def test_on_wallet_update_hook_call(self) -> None: + def test_on_wallet_delete_with_chain(self) -> None: + chain = ChainFactory.create() + wallet = WalletFactory.create(key="Test Wallet", chains=(chain,)) + + wallet.delete() + + # 1 call for Chain creation, 1 call for Wallet creation, 1 call for M2M update, 1 call for Wallet deletion + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) + + @responses.activate + def test_on_wallet_update_with_no_chain(self) -> None: wallet = Wallet(key="Test Wallet") wallet.save() # create wallet.key = "Test Wallet v2" wallet.save() # update - # 2 calls: one for creation and one for updating - assert len(responses.calls) == 2 + # Updating a wallet with no chains should not trigger any webhook + assert len(responses.calls) == 0 + @responses.activate + def test_on_wallet_update_with_chain(self) -> None: + chain = ChainFactory.create() + wallet = WalletFactory.create(key="Test Wallet", chains=(chain,)) + + wallet.chains.remove(chain) + + # 1 call for Chain creation, 1 call for Wallet creation, + # 1 call for M2M update, 1 call for removing m2m relationship + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) + @responses.activate + def test_on_wallet_update_with_multiple_chains(self) -> None: + chain_1 = ChainFactory.create() + chain_2 = ChainFactory.create() + + WalletFactory.create(key="Test Wallet", chains=(chain_1, chain_2)) + + # 1 call for Chain 1 creation, 1 call for Chain 2 creation, 1 call for Wallet creation, + # 1 call for Chain 1 M2M update, 1 call for Chain 2 M2M update, 1 call for Wallet update + assert len(responses.calls) == 6 + assert isinstance(responses.calls[3], responses.Call) + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_2.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_1.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) + assert ( + responses.calls[4].request.headers.get("Authorization") + == "Basic example-token" + ) + + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") class GasPriceHookTestCase(TestCase): def setUp(self) -> None: self.chain = ( @@ -231,16 +377,18 @@ def setUp(self) -> None: ) # chain creation: a GasPrice requires a chain @responses.activate - def test_on_gas_price_create_hook_call(self) -> None: + def test_on_gas_price_create(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "CHAIN_UPDATE", "chainId": str(self.chain.id)} + ), ], ) @@ -248,23 +396,38 @@ def test_on_gas_price_create_hook_call(self) -> None: assert len(responses.calls) == 1 assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + assert responses.calls[ + 0 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[0].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_gas_price_delete_hook_call(self) -> None: + def test_on_gas_price_delete(self) -> None: gas_price = GasPriceFactory.create(chain=self.chain) # create gas_price.delete() # delete # 2 calls: one for creation and one for deletion assert len(responses.calls) == 2 + assert isinstance(responses.calls[1], responses.Call) + assert responses.calls[ + 1 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[1].request.headers.get("Authorization") + == "Basic example-token" + ) @responses.activate - def test_on_gas_price_update_hook_call(self) -> None: + def test_on_gas_price_update(self) -> None: gas_price = GasPriceFactory.create( chain=self.chain, fixed_wei_value=1000 ) # create @@ -274,3 +437,14 @@ def test_on_gas_price_update_hook_call(self) -> None: # 2 calls: one for creation and one for updating assert len(responses.calls) == 2 + assert isinstance(responses.calls[1], responses.Call) + assert responses.calls[ + 1 + ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( + "utf-8" + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[1].request.headers.get("Authorization") + == "Basic example-token" + ) diff --git a/src/chains/tests/test_signals_ff_hook_events.py b/src/chains/tests/test_signals_ff_hook_events.py deleted file mode 100644 index 38493f26..00000000 --- a/src/chains/tests/test_signals_ff_hook_events.py +++ /dev/null @@ -1,468 +0,0 @@ -import responses -from django.test import TestCase, override_settings -from faker import Faker - -from ..models import Feature, Wallet -from ..tests.factories import ( - ChainFactory, - FeatureFactory, - GasPriceFactory, - WalletFactory, -) - -fake = Faker() -Faker.seed(0) - - -@override_settings(FF_HOOK_EVENTS=True) -class ChainNetworkHookTestCaseSetupCheck(TestCase): - @responses.activate - @override_settings(CGW_URL=None, CGW_FLUSH_TOKEN="example-token") - def test_no_cgw_call_with_no_url(self) -> None: - ChainFactory.create() - - assert len(responses.calls) == 0 - - @responses.activate - @override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN=None) - def test_no_cgw_call_with_no_token(self) -> None: - ChainFactory.create() - - assert len(responses.calls) == 0 - - -@override_settings( - CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token", FF_HOOK_EVENTS=True -) -class ChainNetworkHookWithFFHookEventsTestCase(TestCase): - @responses.activate - def test_on_chain_create(self) -> None: - chain_id = fake.pyint() - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "CHAIN_UPDATE", "chainId": str(chain_id)} - ), - ], - ) - - ChainFactory.create(id=chain_id) - - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[ - 0 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_chain_delete(self) -> None: - # Deleting an object sets the primary key to None so we set it in a separate variable - chain_id = fake.pyint() - chain = ChainFactory.create(id=chain_id) - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "CHAIN_UPDATE", "chainId": str(chain.id)} - ), - ], - ) - - chain.delete() - - # 2 calls: one for creation and one for deletion - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_chain_update(self) -> None: - chain = ChainFactory.create() - - # Not updating using queryset because hooks are not triggered that way - chain.currency_name = "Ether" - chain.save() - - # 2 calls: one for creation and one for updating - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - - -@override_settings( - CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token", FF_HOOK_EVENTS=True -) -class FeatureHookTestCase(TestCase): - @responses.activate - def test_on_feature_create_with_no_chain(self) -> None: - Feature(key="Test Feature").save() - - # Creating a feature with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_create_with_chain(self) -> None: - chain = ChainFactory.create() - FeatureFactory.create(key="Test Feature", chains=(chain,)) - - # 1 call for Chain creation, 1 call for feature creation, 1 call for M2M update - assert len(responses.calls) == 3 - assert isinstance(responses.calls[2], responses.Call) - assert responses.calls[ - 2 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[2].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_feature_delete_with_no_chain(self) -> None: - feature = Feature(key="Test Feature") - - feature.save() # create - feature.delete() # delete - - # Deleting a feature with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_delete_with_chain(self) -> None: - chain = ChainFactory.create() - feature = FeatureFactory.create(key="Test Feature", chains=(chain,)) - - feature.delete() - - # 1 call for Chain creation, 1 call for feature creation, 1 call for M2M update, 1 call for feature deletion - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_feature_update_with_no_chain(self) -> None: - feature = Feature(key="Test Feature") - - feature.save() # create - feature.key = "New Test Feature" - feature.save() # update - - # Updating a feature with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_update_with_chain(self) -> None: - chain = ChainFactory.create() - feature = FeatureFactory.create(key="Test Feature", chains=(chain,)) - - feature.chains.remove(chain) - - # 1 call for Chain creation, 1 call for feature creation, - # 1 call for M2M update, 1 call for removing m2m relationship - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_feature_update_with_multiple_chains(self) -> None: - chain_1 = ChainFactory.create() - chain_2 = ChainFactory.create() - - FeatureFactory.create(key="Test Feature", chains=(chain_1, chain_2)) - - # 1 call for Chain 1 creation, 1 call for Chain 2 creation, 1 call for feature creation, - # 1 call for Chain 1 M2M update, 1 call for Chain 2 M2M update, 1 call for Feature update - assert len(responses.calls) == 6 - assert isinstance(responses.calls[3], responses.Call) - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_2.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_1.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - assert ( - responses.calls[4].request.headers.get("Authorization") - == "Basic example-token" - ) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class WalletHookTestCase(TestCase): - @responses.activate - def test_on_wallet_create_with_no_chain(self) -> None: - Wallet(key="Test Wallet").save() - - # Creating a wallet with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_wallet_create_with_chain(self) -> None: - chain = ChainFactory.create() - WalletFactory.create(key="Test Wallet", chains=(chain,)) - - # 1 call for Chain creation, 1 call for Wallet creation, 1 call for M2M update - assert len(responses.calls) == 3 - assert isinstance(responses.calls[2], responses.Call) - assert responses.calls[ - 2 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[2].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_wallet_delete_with_no_chain(self) -> None: - wallet = Wallet(key="Test Wallet") - - wallet.save() # create - wallet.delete() # delete - - # deleting a wallet with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_wallet_delete_with_chain(self) -> None: - chain = ChainFactory.create() - wallet = WalletFactory.create(key="Test Wallet", chains=(chain,)) - - wallet.delete() - - # 1 call for Chain creation, 1 call for Wallet creation, 1 call for M2M update, 1 call for Wallet deletion - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_wallet_update_with_no_chain(self) -> None: - wallet = Wallet(key="Test Wallet") - - wallet.save() # create - wallet.key = "Test Wallet v2" - wallet.save() # update - - # Updating a wallet with no chains should not trigger any webhook - assert len(responses.calls) == 0 - - @responses.activate - def test_on_wallet_update_with_chain(self) -> None: - chain = ChainFactory.create() - wallet = WalletFactory.create(key="Test Wallet", chains=(chain,)) - - wallet.chains.remove(chain) - - # 1 call for Chain creation, 1 call for Wallet creation, - # 1 call for M2M update, 1 call for removing m2m relationship - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_wallet_update_with_multiple_chains(self) -> None: - chain_1 = ChainFactory.create() - chain_2 = ChainFactory.create() - - WalletFactory.create(key="Test Wallet", chains=(chain_1, chain_2)) - - # 1 call for Chain 1 creation, 1 call for Chain 2 creation, 1 call for Wallet creation, - # 1 call for Chain 1 M2M update, 1 call for Chain 2 M2M update, 1 call for Wallet update - assert len(responses.calls) == 6 - assert isinstance(responses.calls[3], responses.Call) - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_2.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{chain_1.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - assert ( - responses.calls[4].request.headers.get("Authorization") - == "Basic example-token" - ) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class GasPriceHookTestCase(TestCase): - def setUp(self) -> None: - self.chain = ( - ChainFactory.create() - ) # chain creation: a GasPrice requires a chain - - @responses.activate - def test_on_gas_price_create(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "CHAIN_UPDATE", "chainId": str(self.chain.id)} - ), - ], - ) - - GasPriceFactory.create(chain=self.chain) - - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[ - 0 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_gas_price_delete(self) -> None: - gas_price = GasPriceFactory.create(chain=self.chain) # create - gas_price.delete() # delete - - # 2 calls: one for creation and one for deletion - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_gas_price_update(self) -> None: - gas_price = GasPriceFactory.create( - chain=self.chain, fixed_wei_value=1000 - ) # create - - gas_price.fixed_wei_value = 2000 - gas_price.save() # update - - # 2 calls: one for creation and one for updating - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "CHAIN_UPDATE", "chainId": "{self.chain.id}"}}'.encode( - "utf-8" - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) diff --git a/src/clients/safe_client_gateway.py b/src/clients/safe_client_gateway.py index 04f6a68e..3bb69f77 100644 --- a/src/clients/safe_client_gateway.py +++ b/src/clients/safe_client_gateway.py @@ -40,15 +40,6 @@ def cgw_setup() -> tuple[str, str]: return (settings.CGW_URL, settings.CGW_FLUSH_TOKEN) -def flush() -> None: - try: - (url, token) = cgw_setup() - url = urljoin(url, "/v2/flush") - post(url, token, json={"invalidate": "Chains"}) - except Exception as error: - logger.error(error) - - def hook_event(event: HookEvent) -> None: try: (url, token) = cgw_setup() diff --git a/src/config/settings.py b/src/config/settings.py index c3f23980..1d723de1 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -231,5 +231,3 @@ allowed_csrf_origins.strip() for allowed_csrf_origins in allowed_csrf_origins.split(",") ] - -FF_HOOK_EVENTS = os.getenv("FF_HOOK_EVENTS", "false").lower() == "true" diff --git a/src/safe_apps/signals.py b/src/safe_apps/signals.py index 3f5455c1..2bc79df0 100644 --- a/src/safe_apps/signals.py +++ b/src/safe_apps/signals.py @@ -1,7 +1,6 @@ import logging from typing import Any -from django.conf import settings from django.core.cache import caches from django.db.models.signals import ( m2m_changed, @@ -12,7 +11,7 @@ ) from django.dispatch import receiver -from clients.safe_client_gateway import HookEvent, flush, hook_event +from clients.safe_client_gateway import HookEvent, hook_event from .models import Feature, Provider, SafeApp, Tag @@ -23,31 +22,21 @@ def on_safe_app_update(sender: SafeApp, instance: SafeApp, **kwargs: Any) -> None: logger.info("Clearing safe-apps cache") caches["safe-apps"].clear() - if settings.FF_HOOK_EVENTS: - chain_ids = set(instance.chain_ids) - if instance.app_id is not None: # existing SafeApp being updated - previous = SafeApp.objects.filter(app_id=instance.app_id).first() - if previous is not None: - chain_ids.update(previous.chain_ids) - for chain_id in chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + chain_ids = set(instance.chain_ids) + if instance.app_id is not None: # existing SafeApp being updated + previous = SafeApp.objects.filter(app_id=instance.app_id).first() + if previous is not None: + chain_ids.update(previous.chain_ids) + for chain_id in chain_ids: + hook_event(HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id)) @receiver(post_delete, sender=SafeApp) def on_safe_app_delete(sender: SafeApp, instance: SafeApp, **kwargs: Any) -> None: logger.info("Clearing safe-apps cache") caches["safe-apps"].clear() - if settings.FF_HOOK_EVENTS: - for chain_id in instance.chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + for chain_id in instance.chain_ids: + hook_event(HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id)) @receiver(post_save, sender=Provider) @@ -55,14 +44,11 @@ def on_safe_app_delete(sender: SafeApp, instance: SafeApp, **kwargs: Any) -> Non def on_provider_update(sender: Provider, instance: Provider, **kwargs: Any) -> None: logger.info("Clearing safe-apps cache") caches["safe-apps"].clear() - if settings.FF_HOOK_EVENTS: - for safe_app in instance.safeapp_set.all(): - for chain_id in safe_app.chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + for safe_app in instance.safeapp_set.all(): + for chain_id in safe_app.chain_ids: + hook_event( + HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) + ) # pre_delete is used because on pre_delete the model still has safe_apps @@ -72,14 +58,11 @@ def on_provider_update(sender: Provider, instance: Provider, **kwargs: Any) -> N def on_tag_update(sender: Tag, instance: Tag, **kwargs: Any) -> None: logger.info("Clearing safe-apps cache") caches["safe-apps"].clear() - if settings.FF_HOOK_EVENTS: - for safe_app in instance.safe_apps.all(): - for chain_id in safe_app.chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + for safe_app in instance.safe_apps.all(): + for chain_id in safe_app.chain_ids: + hook_event( + HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) + ) @receiver(m2m_changed, sender=Tag.safe_apps.through) @@ -89,17 +72,14 @@ def on_tag_chains_update( logger.info("TagChains update. Triggering CGW webhook") caches["safe-apps"].clear() if action == "post_add" or action == "post_remove": - if settings.FF_HOOK_EVENTS: - chain_ids = set() - for safe_app in SafeApp.objects.filter(app_id__in=pk_set): - for chain_id in safe_app.chain_ids: - chain_ids.add(chain_id) - for chain_id in chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + chain_ids = set() + for safe_app in SafeApp.objects.filter(app_id__in=pk_set): + for chain_id in safe_app.chain_ids: + chain_ids.add(chain_id) + for chain_id in chain_ids: + hook_event( + HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) + ) # pre_delete is used because on pre_delete the model still has safe_apps @@ -109,14 +89,11 @@ def on_tag_chains_update( def on_feature_update(sender: Feature, instance: Feature, **kwargs: Any) -> None: logger.info("Feature update. Triggering CGW webhook") caches["safe-apps"].clear() - if settings.FF_HOOK_EVENTS: - for safe_app in instance.safe_apps.all(): - for chain_id in safe_app.chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + for safe_app in instance.safe_apps.all(): + for chain_id in safe_app.chain_ids: + hook_event( + HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) + ) @receiver(m2m_changed, sender=Feature.safe_apps.through) @@ -126,14 +103,11 @@ def on_feature_safe_apps_update( logger.info("FeatureSafeApps update. Triggering CGW webhook") caches["safe-apps"].clear() if action == "post_add" or action == "post_remove": - if settings.FF_HOOK_EVENTS: - chain_ids = set() - for safe_app in SafeApp.objects.filter(app_id__in=pk_set): - for chain_id in safe_app.chain_ids: - chain_ids.add(chain_id) - for chain_id in chain_ids: - hook_event( - HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) - ) - else: - flush() + chain_ids = set() + for safe_app in SafeApp.objects.filter(app_id__in=pk_set): + for chain_id in safe_app.chain_ids: + chain_ids.add(chain_id) + for chain_id in chain_ids: + hook_event( + HookEvent(type=HookEvent.Type.SAFE_APPS_UPDATE, chain_id=chain_id) + ) diff --git a/src/safe_apps/tests/test_signals.py b/src/safe_apps/tests/test_signals.py index 7f285cc1..94b4e55b 100644 --- a/src/safe_apps/tests/test_signals.py +++ b/src/safe_apps/tests/test_signals.py @@ -1,27 +1,29 @@ import responses from django.test import TestCase, override_settings +from faker import Faker -from ..models import SafeApp, Tag -from ..tests.factories import ProviderFactory +from ..models import SafeApp +from .factories import FeatureFactory, ProviderFactory, SafeAppFactory, TagFactory +fake = Faker() +Faker.seed(0) -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") class SafeAppHookTestCase(TestCase): @responses.activate - def test_on_safe_app_create_hook_call(self) -> None: + def test_on_safe_app_create(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "SAFE_APPS_UPDATE", "chainId": "1"} + ), ], ) @@ -29,24 +31,29 @@ def test_on_safe_app_create_hook_call(self) -> None: assert len(responses.calls) == 1 assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + assert ( + responses.calls[0].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[0].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_safe_app_update_hook_call(self) -> None: + def test_on_safe_app_update(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "SAFE_APPS_UPDATE", "chainId": "1"} + ), ], ) @@ -57,208 +64,495 @@ def test_on_safe_app_update_hook_call(self) -> None: assert len(responses.calls) == 2 assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" + assert ( + responses.calls[1].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[1].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_safe_app_delete_hook_call(self) -> None: + def test_on_safe_app_update_by_adding_chain_ids(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "SAFE_APPS_UPDATE", "chainId": "1"} + ), ], ) safe_app = SafeApp(app_id=1, chain_ids=[1]) safe_app.save() # create - safe_app.delete() # delete + safe_app.chain_ids = [1, 2, 3] + safe_app.save() # update - assert len(responses.calls) == 2 + assert len(responses.calls) == 4 + assert isinstance(responses.calls[0], responses.Call) + assert ( + responses.calls[0].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[0].request.headers.get("Authorization") + == "Basic example-token" + ) assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" + assert ( + responses.calls[1].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[1].request.headers.get("Authorization") == "Basic example-token" ) + assert isinstance(responses.calls[2], responses.Call) + assert ( + responses.calls[2].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' + ) + assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[2].request.headers.get("Authorization") + == "Basic example-token" + ) + assert isinstance(responses.calls[3], responses.Call) + assert ( + responses.calls[3].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) - -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class ProviderHookTestCase(TestCase): @responses.activate - def test_on_provider_create_hook_call(self) -> None: + def test_on_safe_app_update_by_removing_chain_ids(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "SAFE_APPS_UPDATE", "chainId": "1"} + ), ], ) - ProviderFactory.create() + safe_app = SafeApp(app_id=1, chain_ids=[1, 2, 3]) + safe_app.save() # create + safe_app.chain_ids = [1] + safe_app.save() # update - assert len(responses.calls) == 1 + assert len(responses.calls) == 6 assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + assert ( + responses.calls[0].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[0].request.headers.get("Authorization") == "Basic example-token" ) + assert isinstance(responses.calls[1], responses.Call) + assert ( + responses.calls[1].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[1].request.headers.get("Authorization") + == "Basic example-token" + ) + assert isinstance(responses.calls[2], responses.Call) + assert ( + responses.calls[2].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' + ) + assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[2].request.headers.get("Authorization") + == "Basic example-token" + ) + assert isinstance(responses.calls[3], responses.Call) + assert ( + responses.calls[3].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[3].request.headers.get("Authorization") + == "Basic example-token" + ) + assert isinstance(responses.calls[4], responses.Call) + assert ( + responses.calls[4].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' + ) + assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[4].request.headers.get("Authorization") + == "Basic example-token" + ) + assert isinstance(responses.calls[5], responses.Call) + assert ( + responses.calls[5].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' + ) + assert responses.calls[5].request.url == "http://127.0.0.1/v1/hooks/events" + assert ( + responses.calls[5].request.headers.get("Authorization") + == "Basic example-token" + ) @responses.activate - def test_on_provider_update_hook_call(self) -> None: + def test_on_safe_app_delete(self) -> None: responses.add( responses.POST, - "http://127.0.0.1/v2/flush", + "http://127.0.0.1/v1/hooks/events", status=200, match=[ responses.matchers.header_matcher( {"Authorization": "Basic example-token"} ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), + responses.matchers.json_params_matcher( + {"type": "SAFE_APPS_UPDATE", "chainId": "1"} + ), ], ) - provider = ProviderFactory.create() # create - provider.name = "Test Provider" - provider.save() # update + safe_app = SafeApp(app_id=1, chain_ids=[1]) + safe_app.save() # create + safe_app.delete() # delete assert len(responses.calls) == 2 assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" + assert ( + responses.calls[1].request.body + == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[1].request.headers.get("Authorization") == "Basic example-token" ) + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") +class ProviderHookTestCase(TestCase): @responses.activate - def test_on_provider_delete_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], - ) + def test_on_provider_create_with_no_safe_app(self) -> None: + ProviderFactory.create() - provider = ProviderFactory.create() # create - provider.delete() # delete + assert len(responses.calls) == 0 + + @responses.activate + def test_on_provider_create_with_safe_app(self) -> None: + chain_id = fake.pyint() + provider = ProviderFactory.create() + SafeAppFactory.create(chain_ids=[chain_id], provider=provider) + # Safe App Creation, Safe App Update assert len(responses.calls) == 2 assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" + assert responses.calls[ + 1 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[1].request.headers.get("Authorization") == "Basic example-token" ) - -@override_settings( - FF_HOOK_EVENTS=False, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class TagHookTestCase(TestCase): @responses.activate - def test_on_tag_create_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], - ) + def test_on_provider_update_with_safe_app(self) -> None: + chain_id = fake.pyint() + provider = ProviderFactory.create() + SafeAppFactory.create(chain_ids=[chain_id], provider=provider) - Tag().save() # create + provider.name = "New name" + provider.save() - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert responses.calls[0].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[0].request.url == "http://127.0.0.1/v2/flush" + # Safe App Creation, Safe App Update, Provider update + assert len(responses.calls) == 3 + assert isinstance(responses.calls[2], responses.Call) + assert responses.calls[ + 2 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" assert ( - responses.calls[0].request.headers.get("Authorization") + responses.calls[2].request.headers.get("Authorization") == "Basic example-token" ) @responses.activate - def test_on_tag_update_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], - ) + def test_on_provider_delete_with_no_safe_app(self) -> None: + provider = ProviderFactory.create() # create + provider.delete() # delete - tag = Tag() - tag.save() # create - tag.name = "Test Tag" - tag.save() # update + assert len(responses.calls) == 0 + + @responses.activate + def test_on_provider_delete_with_safe_app(self) -> None: + chain_id = fake.pyint() + provider = ProviderFactory.create() + SafeAppFactory.create(chain_ids=[chain_id], provider=provider) + + provider.delete() + # Safe App Creation, Safe App Update, Provider update assert len(responses.calls) == 2 assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" + assert responses.calls[ + 1 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" assert ( responses.calls[1].request.headers.get("Authorization") == "Basic example-token" ) + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") +class TagHookTestCase(TestCase): @responses.activate - def test_on_tag_delete_hook_call(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v2/flush", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher({"invalidate": "Chains"}), - ], + def test_on_tag_create_with_no_safe_app(self) -> None: + TagFactory.create() # create + + assert len(responses.calls) == 0 + + @responses.activate + def test_on_tag_create_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + + TagFactory.create(safe_apps=(safe_app,)) + + # Safe App Creation, Safe App Update, M2M update, Tag create + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" ) - tag = Tag() - tag.save() # create + @responses.activate + def test_on_tag_update_with_no_safe_app(self) -> None: + tag = TagFactory.create() # create + tag.name = "Test Tag" + + tag.save() # update + + assert len(responses.calls) == 0 + + @responses.activate + def test_on_tag_update_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + tag = TagFactory.create(safe_apps=(safe_app,)) + + tag.name = "test" + tag.save() + + # Safe App Creation, Safe App Update, M2M update, Tag create, Tag update + assert len(responses.calls) == 5 + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + + @responses.activate + def test_on_tag_delete_with_no_safe_app(self) -> None: + tag = TagFactory.create() # create + tag.delete() # delete - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[1].request.body == b'{"invalidate": "Chains"}' - assert responses.calls[1].request.url == "http://127.0.0.1/v2/flush" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" + assert len(responses.calls) == 0 + + @responses.activate + def test_on_tag_delete_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + tag = TagFactory.create(safe_apps=(safe_app,)) + + tag.delete() + + # Safe App Creation, Safe App Update, M2M update, Tag create, Tag delete + assert len(responses.calls) == 5 + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + + @responses.activate + def test_on_tag_update_with_multiple_safe_apps(self) -> None: + chain_id_1 = fake.pyint() + chain_id_2 = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id_1, chain_id_2]) + + TagFactory.create(safe_apps=(safe_app,)) + + # Safe App Creation for chain 1, Safe App Creation for chain 2, + # Safe App Update for chain 1, Safe App Update for chain 2, + # Tag update for chain 1, M2M update for chain 1 + # Tag update for chain 2, M2M update for chain 2 + assert len(responses.calls) == 8 + assert isinstance(responses.calls[5], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 5 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 6 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 7 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( + "utf-8" + ) + + +@override_settings(CGW_URL="http://127.0.0.1", CGW_FLUSH_TOKEN="example-token") +class FeatureHookTestCase(TestCase): + @responses.activate + def test_on_feature_create_with_no_safe_app(self) -> None: + FeatureFactory.create() # create + + assert len(responses.calls) == 0 + + @responses.activate + def test_on_feature_create_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + + FeatureFactory.create(safe_apps=(safe_app,)) + + # Safe App Creation, Safe App Update, M2M update, Feature create + assert len(responses.calls) == 4 + assert isinstance(responses.calls[3], responses.Call) + assert responses.calls[ + 3 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + + @responses.activate + def test_on_feature_update_with_no_safe_app(self) -> None: + feature = FeatureFactory.create() # create + feature.name = "Test Feature" + + feature.save() # update + + assert len(responses.calls) == 0 + + @responses.activate + def test_on_feature_update_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + feature = FeatureFactory.create(safe_apps=(safe_app,)) + + feature.name = "test" + feature.save() + + # Safe App Creation, Safe App Update, M2M update, Feature create, Feature update + assert len(responses.calls) == 5 + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + + @responses.activate + def test_on_feature_delete_with_no_safe_app(self) -> None: + feature = FeatureFactory.create() # create + + feature.delete() # delete + + assert len(responses.calls) == 0 + + @responses.activate + def test_on_feature_delete_with_safe_app(self) -> None: + chain_id = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id]) + feature = FeatureFactory.create(safe_apps=(safe_app,)) + + feature.delete() + + # Safe App Creation, Safe App Update, M2M update, Feature create, Feature delete + assert len(responses.calls) == 5 + assert isinstance(responses.calls[4], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( + "utf-8" + ) + + @responses.activate + def test_on_feature_update_with_multiple_safe_apps(self) -> None: + chain_id_1 = fake.pyint() + chain_id_2 = fake.pyint() + safe_app = SafeAppFactory.create(chain_ids=[chain_id_1, chain_id_2]) + + FeatureFactory.create(safe_apps=(safe_app,)) + + # Safe App Creation for chain 1, Safe App Creation for chain 2, + # Safe App Update for chain 1, Safe App Update for chain 2, + # Feature update for chain 1, M2M update for chain 1 + # Feature update for chain 2, M2M update for chain 2 + assert len(responses.calls) == 8 + assert isinstance(responses.calls[5], responses.Call) + assert responses.calls[ + 4 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 5 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 6 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( + "utf-8" + ) + assert responses.calls[ + 7 + ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( + "utf-8" ) diff --git a/src/safe_apps/tests/test_signals_ff_hook_events.py b/src/safe_apps/tests/test_signals_ff_hook_events.py deleted file mode 100644 index bebb2396..00000000 --- a/src/safe_apps/tests/test_signals_ff_hook_events.py +++ /dev/null @@ -1,574 +0,0 @@ -import responses -from django.test import TestCase, override_settings -from faker import Faker - -from ..models import SafeApp -from .factories import FeatureFactory, ProviderFactory, SafeAppFactory, TagFactory - -fake = Faker() -Faker.seed(0) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class SafeAppHookTestCase(TestCase): - @responses.activate - def test_on_safe_app_create(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "SAFE_APPS_UPDATE", "chainId": "1"} - ), - ], - ) - - SafeApp(app_id=1, chain_ids=[1]).save() - - assert len(responses.calls) == 1 - assert isinstance(responses.calls[0], responses.Call) - assert ( - responses.calls[0].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_safe_app_update(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "SAFE_APPS_UPDATE", "chainId": "1"} - ), - ], - ) - - safe_app = SafeApp(app_id=1, chain_ids=[1]) - safe_app.save() # create - safe_app.name = "Test app" - safe_app.save() # update - - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert ( - responses.calls[1].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_safe_app_update_by_adding_chain_ids(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "SAFE_APPS_UPDATE", "chainId": "1"} - ), - ], - ) - - safe_app = SafeApp(app_id=1, chain_ids=[1]) - safe_app.save() # create - safe_app.chain_ids = [1, 2, 3] - safe_app.save() # update - - assert len(responses.calls) == 4 - assert isinstance(responses.calls[0], responses.Call) - assert ( - responses.calls[0].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[1], responses.Call) - assert ( - responses.calls[1].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[2], responses.Call) - assert ( - responses.calls[2].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' - ) - assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[2].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[3], responses.Call) - assert ( - responses.calls[3].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_safe_app_update_by_removing_chain_ids(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "SAFE_APPS_UPDATE", "chainId": "1"} - ), - ], - ) - - safe_app = SafeApp(app_id=1, chain_ids=[1, 2, 3]) - safe_app.save() # create - safe_app.chain_ids = [1] - safe_app.save() # update - - assert len(responses.calls) == 6 - assert isinstance(responses.calls[0], responses.Call) - assert ( - responses.calls[0].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[0].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[0].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[1], responses.Call) - assert ( - responses.calls[1].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[2], responses.Call) - assert ( - responses.calls[2].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' - ) - assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[2].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[3], responses.Call) - assert ( - responses.calls[3].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[3].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[3].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[4], responses.Call) - assert ( - responses.calls[4].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "2"}' - ) - assert responses.calls[4].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[4].request.headers.get("Authorization") - == "Basic example-token" - ) - assert isinstance(responses.calls[5], responses.Call) - assert ( - responses.calls[5].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "3"}' - ) - assert responses.calls[5].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[5].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_safe_app_delete(self) -> None: - responses.add( - responses.POST, - "http://127.0.0.1/v1/hooks/events", - status=200, - match=[ - responses.matchers.header_matcher( - {"Authorization": "Basic example-token"} - ), - responses.matchers.json_params_matcher( - {"type": "SAFE_APPS_UPDATE", "chainId": "1"} - ), - ], - ) - - safe_app = SafeApp(app_id=1, chain_ids=[1]) - safe_app.save() # create - safe_app.delete() # delete - - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert ( - responses.calls[1].request.body - == b'{"type": "SAFE_APPS_UPDATE", "chainId": "1"}' - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class ProviderHookTestCase(TestCase): - @responses.activate - def test_on_provider_create_with_no_safe_app(self) -> None: - ProviderFactory.create() - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_provider_create_with_safe_app(self) -> None: - chain_id = fake.pyint() - provider = ProviderFactory.create() - SafeAppFactory.create(chain_ids=[chain_id], provider=provider) - - # Safe App Creation, Safe App Update - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_provider_update_with_safe_app(self) -> None: - chain_id = fake.pyint() - provider = ProviderFactory.create() - SafeAppFactory.create(chain_ids=[chain_id], provider=provider) - - provider.name = "New name" - provider.save() - - # Safe App Creation, Safe App Update, Provider update - assert len(responses.calls) == 3 - assert isinstance(responses.calls[2], responses.Call) - assert responses.calls[ - 2 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - assert responses.calls[2].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[2].request.headers.get("Authorization") - == "Basic example-token" - ) - - @responses.activate - def test_on_provider_delete_with_no_safe_app(self) -> None: - provider = ProviderFactory.create() # create - provider.delete() # delete - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_provider_delete_with_safe_app(self) -> None: - chain_id = fake.pyint() - provider = ProviderFactory.create() - SafeAppFactory.create(chain_ids=[chain_id], provider=provider) - - provider.delete() - - # Safe App Creation, Safe App Update, Provider update - assert len(responses.calls) == 2 - assert isinstance(responses.calls[1], responses.Call) - assert responses.calls[ - 1 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - assert responses.calls[1].request.url == "http://127.0.0.1/v1/hooks/events" - assert ( - responses.calls[1].request.headers.get("Authorization") - == "Basic example-token" - ) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class TagHookTestCase(TestCase): - @responses.activate - def test_on_tag_create_with_no_safe_app(self) -> None: - TagFactory.create() # create - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_tag_create_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - - TagFactory.create(safe_apps=(safe_app,)) - - # Safe App Creation, Safe App Update, M2M update, Tag create - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_tag_update_with_no_safe_app(self) -> None: - tag = TagFactory.create() # create - tag.name = "Test Tag" - - tag.save() # update - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_tag_update_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - tag = TagFactory.create(safe_apps=(safe_app,)) - - tag.name = "test" - tag.save() - - # Safe App Creation, Safe App Update, M2M update, Tag create, Tag update - assert len(responses.calls) == 5 - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_tag_delete_with_no_safe_app(self) -> None: - tag = TagFactory.create() # create - - tag.delete() # delete - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_tag_delete_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - tag = TagFactory.create(safe_apps=(safe_app,)) - - tag.delete() - - # Safe App Creation, Safe App Update, M2M update, Tag create, Tag delete - assert len(responses.calls) == 5 - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_tag_update_with_multiple_safe_apps(self) -> None: - chain_id_1 = fake.pyint() - chain_id_2 = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id_1, chain_id_2]) - - TagFactory.create(safe_apps=(safe_app,)) - - # Safe App Creation for chain 1, Safe App Creation for chain 2, - # Safe App Update for chain 1, Safe App Update for chain 2, - # Tag update for chain 1, M2M update for chain 1 - # Tag update for chain 2, M2M update for chain 2 - assert len(responses.calls) == 8 - assert isinstance(responses.calls[5], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 5 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 6 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 7 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( - "utf-8" - ) - - -@override_settings( - FF_HOOK_EVENTS=True, - CGW_URL="http://127.0.0.1", - CGW_FLUSH_TOKEN="example-token", -) -class FeatureHookTestCase(TestCase): - @responses.activate - def test_on_feature_create_with_no_safe_app(self) -> None: - FeatureFactory.create() # create - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_create_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - - FeatureFactory.create(safe_apps=(safe_app,)) - - # Safe App Creation, Safe App Update, M2M update, Feature create - assert len(responses.calls) == 4 - assert isinstance(responses.calls[3], responses.Call) - assert responses.calls[ - 3 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_feature_update_with_no_safe_app(self) -> None: - feature = FeatureFactory.create() # create - feature.name = "Test Feature" - - feature.save() # update - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_update_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - feature = FeatureFactory.create(safe_apps=(safe_app,)) - - feature.name = "test" - feature.save() - - # Safe App Creation, Safe App Update, M2M update, Feature create, Feature update - assert len(responses.calls) == 5 - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_feature_delete_with_no_safe_app(self) -> None: - feature = FeatureFactory.create() # create - - feature.delete() # delete - - assert len(responses.calls) == 0 - - @responses.activate - def test_on_feature_delete_with_safe_app(self) -> None: - chain_id = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id]) - feature = FeatureFactory.create(safe_apps=(safe_app,)) - - feature.delete() - - # Safe App Creation, Safe App Update, M2M update, Feature create, Feature delete - assert len(responses.calls) == 5 - assert isinstance(responses.calls[4], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id}"}}'.encode( - "utf-8" - ) - - @responses.activate - def test_on_feature_update_with_multiple_safe_apps(self) -> None: - chain_id_1 = fake.pyint() - chain_id_2 = fake.pyint() - safe_app = SafeAppFactory.create(chain_ids=[chain_id_1, chain_id_2]) - - FeatureFactory.create(safe_apps=(safe_app,)) - - # Safe App Creation for chain 1, Safe App Creation for chain 2, - # Safe App Update for chain 1, Safe App Update for chain 2, - # Feature update for chain 1, M2M update for chain 1 - # Feature update for chain 2, M2M update for chain 2 - assert len(responses.calls) == 8 - assert isinstance(responses.calls[5], responses.Call) - assert responses.calls[ - 4 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 5 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 6 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_1}"}}'.encode( - "utf-8" - ) - assert responses.calls[ - 7 - ].request.body == f'{{"type": "SAFE_APPS_UPDATE", "chainId": "{chain_id_2}"}}'.encode( - "utf-8" - )