From 7a95bd179b058f5667a587a862da35e67d2f202e Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Wed, 13 Nov 2019 11:43:11 -0500
Subject: [PATCH] Add a redeemed `token-count` to the voucher model

---
 src/_zkapauthorizer/model.py            | 24 ++++++++++----
 src/_zkapauthorizer/tests/test_model.py | 44 ++++++++++++++++++++++---
 2 files changed, 56 insertions(+), 12 deletions(-)

diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 8fd2e8f..c8d2a32 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -122,8 +122,10 @@ def open_and_initialize(path, required_schema_version, connect=None):
             """
             CREATE TABLE IF NOT EXISTS [vouchers] (
                 [number] text,
-                [created] text,            -- An ISO8601 date+time string.
-                [redeemed] num DEFAULT 0,
+                [created] text,                     -- An ISO8601 date+time string.
+                [redeemed] num DEFAULT 0,           -- 0 if the voucher has not been redeemed, 1 otherwise.
+                [token-count] num DEFAULT NULL,     -- NULL if the voucher has not been redeemed,
+                                                    -- a number of tokens received on its redemption otherwise.
 
                 PRIMARY KEY([number])
             )
@@ -219,7 +221,7 @@ class VoucherStore(object):
         cursor.execute(
             """
             SELECT
-                [number], [created], [redeemed]
+                [number], [created], [redeemed], [token-count]
             FROM
                 [vouchers]
             WHERE
@@ -277,7 +279,7 @@ class VoucherStore(object):
         """
         cursor.execute(
             """
-            SELECT [number], [created], [redeemed] FROM [vouchers]
+            SELECT [number], [created], [redeemed], [token-count] FROM [vouchers]
             """,
         )
         refs = cursor.fetchall()
@@ -312,9 +314,12 @@ class VoucherStore(object):
         )
         cursor.execute(
             """
-            UPDATE [vouchers] SET [redeemed] = 1 WHERE [number] = ?
+            UPDATE [vouchers]
+            SET [redeemed] = 1
+              , [token-count] = ?
+            WHERE [number] = ?
             """,
-            (voucher,),
+            (len(unblinded_tokens), voucher),
         )
 
     @with_cursor
@@ -422,14 +427,18 @@ class Voucher(object):
 
     :ivar bool redeemed: ``True`` if this voucher has successfully been
         redeemed with a payment server, ``False`` otherwise.
+
+    :ivar int token_count: A number of tokens received from the redemption of
+        this voucher if it has been redeemed, ``None`` if it has not been
+        redeemed.
     """
     number = attr.ib()
     created = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(datetime)))
     redeemed = attr.ib(default=False, validator=attr.validators.instance_of(bool))
+    token_count = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(int)))
 
     @classmethod
     def from_row(cls, row):
-        print(row[1])
         return cls(
             row[0],
             # All Python datetime-based date/time libraries fail to handle
@@ -439,6 +448,7 @@ class Voucher(object):
             # represent a leap second... I hope.
             parse_datetime(row[1], delimiter=u" "),
             bool(row[2]),
+            row[3],
         )
 
     @classmethod
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index 319b363..f53bd5b 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -48,9 +48,12 @@ from fixtures import (
 from hypothesis import (
     given,
 )
+
 from hypothesis.strategies import (
+    data,
     lists,
     datetimes,
+    integers,
 )
 
 from twisted.python.filepath import (
@@ -155,6 +158,8 @@ class VoucherStoreTests(TestCase):
             MatchesStructure(
                 number=Equals(voucher),
                 created=Equals(now),
+                redeemed=Equals(False),
+                token_count=Equals(None),
             ),
         )
 
@@ -292,12 +297,35 @@ class UnblindedTokenStoreTests(TestCase):
         more_unblinded_tokens = store.extract_unblinded_tokens(1)
         self.expectThat([], Equals(more_unblinded_tokens))
 
-    @given(tahoe_configs(), datetimes(), vouchers(), random_tokens(), unblinded_tokens())
-    def test_mark_vouchers_redeemed(self, get_config, now, voucher_value, token, one_token):
+    @given(
+        tahoe_configs(),
+        datetimes(),
+        vouchers(),
+        integers(min_value=1, max_value=100),
+        data(),
+    )
+    def test_mark_vouchers_redeemed(self, get_config, now, voucher_value, num_tokens, data):
         """
         The voucher for unblinded tokens that are added to the store is marked as
         redeemed.
         """
+        random = data.draw(
+            lists(
+                random_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+        unblinded = data.draw(
+            lists(
+                unblinded_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+
         tempdir = self.useFixture(TempDir())
         config = get_config(tempdir.join(b"node"), b"tub.port")
         store = VoucherStore.from_node_config(
@@ -305,7 +333,13 @@ class UnblindedTokenStoreTests(TestCase):
             lambda: now,
             memory_connect,
         )
-        store.add(voucher_value, [token])
-        store.insert_unblinded_tokens_for_voucher(voucher_value, [one_token])
+        store.add(voucher_value, random)
+        store.insert_unblinded_tokens_for_voucher(voucher_value, unblinded)
         loaded_voucher = store.get(voucher_value)
-        self.assertThat(loaded_voucher.redeemed, Equals(True))
+        self.assertThat(
+            loaded_voucher,
+            MatchesStructure(
+                redeemed=Equals(True),
+                token_count=Equals(num_tokens),
+            ),
+        )
-- 
GitLab