From 5fe4b42643b52ea5e9eb3da8ea1821d06d69b0d2 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Thu, 21 May 2020 10:10:48 -0400 Subject: [PATCH] Very basic test for SpendingController --- src/_zkapauthorizer/spending.py | 1 + src/_zkapauthorizer/tests/matchers.py | 2 +- src/_zkapauthorizer/tests/strategies.py | 9 ++ src/_zkapauthorizer/tests/test_spending.py | 112 ++++++++++++++++++ .../tests/test_storage_client.py | 11 +- 5 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 src/_zkapauthorizer/tests/test_spending.py diff --git a/src/_zkapauthorizer/spending.py b/src/_zkapauthorizer/spending.py index 2e44de9..a2836e1 100644 --- a/src/_zkapauthorizer/spending.py +++ b/src/_zkapauthorizer/spending.py @@ -145,6 +145,7 @@ class PassGroup(object): self._factory._reset(self.passes) +@implementer(IPassFactory) @attr.s class SpendingController(object): """ diff --git a/src/_zkapauthorizer/tests/matchers.py b/src/_zkapauthorizer/tests/matchers.py index 79b4feb..5ea2613 100644 --- a/src/_zkapauthorizer/tests/matchers.py +++ b/src/_zkapauthorizer/tests/matchers.py @@ -54,7 +54,7 @@ class Provides(object): """ Match objects that provide all of a list of Zope Interface interfaces. """ - interfaces = attr.ib() + interfaces = attr.ib(validator=attr.validators.instance_of(list)) def match(self, obj): missing = set() diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 28028fd..0c448cd 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -813,3 +813,12 @@ def node_hierarchies(): ).filter( storage_indexes_are_distinct, ) + + +def pass_counts(): + """ + Build integers usable as a number of passes to work on. There is always + at least one pass in a group and there are never "too many", whatever that + means. + """ + return integers(min_value=1, max_value=2 ** 8) diff --git a/src/_zkapauthorizer/tests/test_spending.py b/src/_zkapauthorizer/tests/test_spending.py new file mode 100644 index 0000000..62473bb --- /dev/null +++ b/src/_zkapauthorizer/tests/test_spending.py @@ -0,0 +1,112 @@ +# Copyright 2019 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Tests for ``_zkapauthorizer.spending``. +""" + +from testtools import ( + TestCase, +) +from testtools.matchers import ( + Always, + MatchesAll, + MatchesStructure, + HasLength, +) +from testtools.twistedsupport import ( + succeeded, +) + +from hypothesis import ( + given, +) + +from twisted.python.filepath import ( + FilePath, +) + +from .strategies import ( + vouchers, + pass_counts, + posix_safe_datetimes, +) +from .matchers import ( + Provides, +) +from ..model import ( + VoucherStore, + open_and_initialize, + memory_connect, +) +from ..controller import ( + DummyRedeemer, + PaymentController, +) +from ..spending import ( + IPassGroup, + SpendingController, +) + +class PassGroupTests(TestCase): + """ + Tests for ``IPassGroup`` and the factories that create them. + """ + @given(vouchers(), pass_counts(), posix_safe_datetimes()) + def test_get(self, voucher, num_passes, now): + """ + ``IPassFactory.get`` returns an ``IPassGroup`` provider containing the + requested number of passes. + """ + redeemer = DummyRedeemer() + here = FilePath(u".") + store = VoucherStore( + pass_value=2 ** 15, + database_path=here, + now=lambda: now, + connection=open_and_initialize(here, memory_connect), + ) + # Make sure there are enough tokens for us to extract! + self.assertThat( + PaymentController( + store, + redeemer, + # Have to pass it here or to redeem, doesn't matter which. + default_token_count=num_passes, + # No value in splitting it into smaller groups in this case. + # Doing so only complicates the test by imposing a different + # minimum token count requirement (can't have fewer tokens + # than groups). + num_redemption_groups=1, + ).redeem( + voucher, + ), + succeeded(Always()), + ) + + pass_factory = SpendingController( + extract_unblinded_tokens=store.extract_unblinded_tokens, + tokens_to_passes=redeemer.tokens_to_passes, + ) + + group = pass_factory.get(u"message", num_passes) + self.assertThat( + group, + MatchesAll( + Provides([IPassGroup]), + MatchesStructure( + passes=HasLength(num_passes), + ), + ), + ) diff --git a/src/_zkapauthorizer/tests/test_storage_client.py b/src/_zkapauthorizer/tests/test_storage_client.py index ee5d1bc..5fd6b6d 100644 --- a/src/_zkapauthorizer/tests/test_storage_client.py +++ b/src/_zkapauthorizer/tests/test_storage_client.py @@ -41,9 +41,6 @@ from testtools.twistedsupport import ( from hypothesis import ( given, ) -from hypothesis.strategies import ( - integers, -) from twisted.internet.defer import ( succeed, @@ -55,6 +52,10 @@ from .matchers import ( odd, ) +from .strategies import ( + pass_counts, +) + from ..api import ( MorePassesRequired, ) @@ -71,10 +72,6 @@ from .storage_common import ( ) -def pass_counts(): - return integers(min_value=1, max_value=2 ** 8) - - class CallWithPassesTests(TestCase): """ Tests for ``call_with_passes``. -- GitLab