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