diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py index 98eb8da61c3718281cbdf020637d0b6c151ff09f..1b8874e4a7a8353c7112906b2459a85f710efa60 100644 --- a/src/_zkapauthorizer/_plugin.py +++ b/src/_zkapauthorizer/_plugin.py @@ -185,7 +185,7 @@ class ZKAPAuthorizer(object): ) - def get_client_resource(self, node_config, default_token_count=None): + def get_client_resource(self, node_config, default_token_count=None, reactor=None): """ Get an ``IZKAPRoot`` for the given node configuration. @@ -197,12 +197,14 @@ class ZKAPAuthorizer(object): This is only used if a number of tokens isn't specified at the point of redemption. """ - from twisted.internet import reactor + if reactor is None: + from twisted.internet import reactor return resource_from_configuration( node_config, store=self._get_store(node_config), redeemer=self._get_redeemer(node_config, None, reactor), default_token_count=default_token_count, + clock=reactor, ) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 96d70747cc45f31cebd2c67cc3bcd821c3977e54..15d8395080c10777a57cd227fa05e70ffdc301a5 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -712,6 +712,9 @@ class PaymentController(object): TODO: Retrieve this value from the PaymentServer or from the ZKAPAuthorizer configuration instead of just hard-coding a duplicate value in this implementation. + + :ivar IReactorTime _clock: The reactor to use for scheduling redemption + retries. """ _log = Logger() @@ -721,9 +724,7 @@ class PaymentController(object): num_redemption_groups = attr.ib(default=16) - _clock = attr.ib( - default=attr.Factory(partial(namedAny, "twisted.internet.reactor")), - ) + _clock = attr.ib(default=None) _error = attr.ib(default=attr.Factory(dict)) _unpaid = attr.ib(default=attr.Factory(dict)) @@ -735,6 +736,9 @@ class PaymentController(object): This is an initialization-time hook called by attrs. """ + if self._clock is None: + self._clock = namedAny("twisted.internet.reactor") + self._check_pending_vouchers() # Also start a time-based polling loop to retry redemption of vouchers # in retryable error states. diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py index 96dc29dbaeeda4ed9ee2f04150d5651c85380af4..f8fa7a9dab92f86bba84ece5a21a0e1cfeb603e4 100644 --- a/src/_zkapauthorizer/resource.py +++ b/src/_zkapauthorizer/resource.py @@ -91,7 +91,13 @@ class IZKAPRoot(IResource): controller = Attribute("The ``PaymentController`` used by this resource tree.") -def from_configuration(node_config, store, redeemer=None, default_token_count=None): +def from_configuration( + node_config, + store, + redeemer=None, + default_token_count=None, + clock=None, +): """ Instantiate the plugin root resource using data from its configuration section, **storageclient.plugins.privatestorageio-zkapauthz-v1**, in the @@ -108,6 +114,10 @@ def from_configuration(node_config, store, redeemer=None, default_token_count=No :param IRedeemer redeemer: The voucher redeemer to use. If ``None`` a sensible one is constructed. + :param default_token_count: See ``PaymentController.default_token_count``. + + :param clock: See ``PaymentController._clock``. + :return IZKAPRoot: The root of the resource hierarchy presented by the client side of the plugin. """ @@ -120,7 +130,12 @@ def from_configuration(node_config, store, redeemer=None, default_token_count=No ) if default_token_count is None: default_token_count = NUM_TOKENS - controller = PaymentController(store, redeemer, default_token_count) + controller = PaymentController( + store, + redeemer, + default_token_count, + clock=clock, + ) calculator = PriceCalculator( get_configured_shares_needed(node_config), diff --git a/src/_zkapauthorizer/tests/fixtures.py b/src/_zkapauthorizer/tests/fixtures.py index 00be5b25283194c4a9454d6fa5314a6695b60650..35aadaea07c79020433806bdcf6ccea6cb2e4410 100644 --- a/src/_zkapauthorizer/tests/fixtures.py +++ b/src/_zkapauthorizer/tests/fixtures.py @@ -30,7 +30,9 @@ from fixtures import ( from twisted.python.filepath import ( FilePath, ) - +from twisted.internet.task import ( + Clock, +) from allmydata.storage.server import ( StorageServer, ) @@ -125,6 +127,7 @@ class ConfiglessMemoryVoucherStore(Fixture): # minimum token count requirement (can't have fewer tokens # than groups). num_redemption_groups=1, + clock=Clock(), ).redeem( voucher, ) diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index e13d4747e0c2c3246c4ce012b7da911df0970212..6b38da748c5e7ce8f541b72698f79c974cfab177 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -103,6 +103,7 @@ from twisted.internet.defer import ( ) from twisted.internet.task import ( Cooperator, + Clock, ) from twisted.web.http import ( OK, @@ -278,6 +279,7 @@ def root_from_config(config, now): memory_connect, ), default_token_count=NUM_TOKENS, + clock=Clock(), ) diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index bec922bb0863dea1caee2df78cd783e713894d11..25568d09803c21def1bf10a49d7b088e6bf00895 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -73,6 +73,9 @@ from twisted.python.url import ( from twisted.internet.defer import ( fail, ) +from twisted.internet.task import ( + Clock, +) from twisted.web.iweb import ( IAgent, ) @@ -228,6 +231,7 @@ class PaymentControllerTests(TestCase): store, DummyRedeemer(), default_token_count=100, + clock=Clock(), ) self.assertThat( @@ -263,6 +267,7 @@ class PaymentControllerTests(TestCase): store, NonRedeemer(), default_token_count=100, + clock=Clock(), ) self.assertThat( controller.redeem(voucher), @@ -299,6 +304,7 @@ class PaymentControllerTests(TestCase): # Require more success than we're going to get so it doesn't # finish. num_redemption_groups=counter, + clock=Clock(), ) self.assertThat( @@ -353,6 +359,7 @@ class PaymentControllerTests(TestCase): ), default_token_count=num_tokens, num_redemption_groups=num_redemption_groups, + clock=Clock(), ) self.assertThat( controller.redeem(voucher), @@ -378,6 +385,7 @@ class PaymentControllerTests(TestCase): # The number of redemption groups must not change for # redemption of a particular voucher. num_redemption_groups=num_redemption_groups, + clock=Clock(), ) first_try() @@ -412,6 +420,7 @@ class PaymentControllerTests(TestCase): redeemer, default_token_count=num_tokens, num_redemption_groups=num_redemption_groups, + clock=Clock(), ) self.assertThat( controller.redeem(voucher), @@ -435,6 +444,7 @@ class PaymentControllerTests(TestCase): store, DummyRedeemer(public_key), default_token_count=100, + clock=Clock(), ) self.assertThat( controller.redeem(voucher), @@ -462,6 +472,7 @@ class PaymentControllerTests(TestCase): store, DoubleSpendRedeemer(), default_token_count=100, + clock=Clock(), ) self.assertThat( controller.redeem(voucher), @@ -491,6 +502,7 @@ class PaymentControllerTests(TestCase): store, UnpaidRedeemer(), default_token_count=100, + clock=Clock(), ) self.assertThat( unpaid_controller.redeem(voucher), @@ -510,6 +522,7 @@ class PaymentControllerTests(TestCase): store, DummyRedeemer(), default_token_count=100, + clock=Clock(), ) self.assertThat( diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 44c79af85c0a783ec4af01ae06cc44309686a000..e28f06be5ec06360346d5790c06df3293479425a 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -99,6 +99,9 @@ from twisted.plugin import ( from twisted.test.proto_helpers import ( StringTransport, ) +from twisted.internet.task import ( + Clock, +) from twisted.web.resource import ( IResource, ) @@ -483,6 +486,7 @@ class ClientPluginTests(TestCase): DummyRedeemer(), default_token_count=num_passes, num_redemption_groups=1, + clock=Clock(), ) # Get a token inserted into the store. redeeming = controller.redeem(voucher) @@ -543,7 +547,11 @@ class ClientResourceTests(TestCase): nodedir = tempdir.join(b"node") config = get_config(nodedir, b"tub.port") self.assertThat( - storage_server.get_client_resource(config, default_token_count=10), + storage_server.get_client_resource( + config, + default_token_count=10, + reactor=Clock(), + ), Provides([IResource]), )