From d759e08a84d89f3646acd50187a3447dc0bc3075 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Fri, 15 Nov 2019 09:39:54 -0500
Subject: [PATCH] Replace ``redeemed`` with ``state``

---
 src/_zkapauthorizer/model.py                  | 23 +++++++++++--------
 src/_zkapauthorizer/tests/strategies.py       | 14 +++++++++--
 .../tests/test_client_resource.py             |  4 ++--
 src/_zkapauthorizer/tests/test_controller.py  |  8 +++----
 src/_zkapauthorizer/tests/test_model.py       |  6 ++---
 5 files changed, 34 insertions(+), 21 deletions(-)

diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 308947d..ab0a19a 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -119,13 +119,16 @@ def open_and_initialize(path, required_schema_version, connect=None):
             )
 
         cursor.execute(
+            # A denormalized schema because, for now, it's simpler. :/
             """
             CREATE TABLE IF NOT EXISTS [vouchers] (
                 [number] text,
                 [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.
+                [state] text DEFAULT "pending",     -- pending, double-spend, redeemed
+
+                [token-count] num DEFAULT NULL,     -- Set in the redeemed state to  the number
+                                                    -- of tokens received on this voucher's
+                                                    -- redemption.
 
                 PRIMARY KEY([number])
             )
@@ -221,7 +224,7 @@ class VoucherStore(object):
         cursor.execute(
             """
             SELECT
-                [number], [created], [redeemed], [token-count]
+                [number], [created], [state], [token-count]
             FROM
                 [vouchers]
             WHERE
@@ -279,7 +282,7 @@ class VoucherStore(object):
         """
         cursor.execute(
             """
-            SELECT [number], [created], [redeemed], [token-count] FROM [vouchers]
+            SELECT [number], [created], [state], [token-count] FROM [vouchers]
             """,
         )
         refs = cursor.fetchall()
@@ -315,7 +318,7 @@ class VoucherStore(object):
         cursor.execute(
             """
             UPDATE [vouchers]
-            SET [redeemed] = 1
+            SET [state] = "redeemed"
               , [token-count] = ?
             WHERE [number] = ?
             """,
@@ -434,7 +437,7 @@ class Voucher(object):
     """
     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))
+    state = attr.ib(default=u"pending", validator=attr.validators.in_((u"pending", u"double-spend", u"redeemed")))
     token_count = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of((int, long))))
 
     @classmethod
@@ -447,7 +450,7 @@ class Voucher(object):
             # Python to generate the data in the first place, it should never
             # represent a leap second... I hope.
             parse_datetime(row[1], delimiter=u" "),
-            bool(row[2]),
+            row[2],
             row[3],
         )
 
@@ -463,7 +466,7 @@ class Voucher(object):
         return cls(
             number=values[u"number"],
             created=None if values[u"created"] is None else parse_datetime(values[u"created"]),
-            redeemed=values[u"redeemed"],
+            state=values[u"state"],
             token_count=values[u"token-count"],
         )
 
@@ -480,7 +483,7 @@ class Voucher(object):
         return {
             u"number": self.number,
             u"created": None if self.created is None else self.created.isoformat(),
-            u"redeemed": self.redeemed,
+            u"state": self.state,
             u"token-count": self.token_count,
             u"version": 1,
         }
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index 9f9e254..07e8ce7 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -26,7 +26,6 @@ from hypothesis.strategies import (
     one_of,
     just,
     none,
-    booleans,
     binary,
     characters,
     text,
@@ -220,6 +219,17 @@ def vouchers():
     )
 
 
+def voucher_states():
+    """
+    Build unicode strings giving states a Voucher can be in.
+    """
+    return one_of(
+        just(u"pending"),
+        just(u"double-spend"),
+        just(u"redeemed"),
+    )
+
+
 def voucher_objects():
     """
     Build ``Voucher`` instances.
@@ -228,7 +238,7 @@ def voucher_objects():
         Voucher,
         number=vouchers(),
         created=one_of(none(), datetimes()),
-        redeemed=booleans(),
+        state=voucher_states(),
         token_count=one_of(none(), integers(min_value=1)),
     )
 
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 1df9393..397e8c0 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -669,7 +669,7 @@ class VoucherTests(TestCase):
                                 MatchesStructure(
                                     number=Equals(voucher),
                                     created=Equals(now),
-                                    redeemed=Equals(redeemed),
+                                    state=Equals(u"redeemed" if redeemed else u"pending"),
                                     token_count=token_count_comparison,
                                 ),
                             ),
@@ -731,7 +731,7 @@ class VoucherTests(TestCase):
                                     Voucher(
                                         voucher,
                                         created=now,
-                                        redeemed=True,
+                                        state=u"redeemed",
                                         # Value duplicated from
                                         # PaymentController.redeem default.
                                         # Should do this better.
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index 032ed09..bb72c66 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -140,8 +140,8 @@ class PaymentControllerTests(TestCase):
 
         persisted_voucher = store.get(voucher)
         self.assertThat(
-            persisted_voucher.redeemed,
-            Equals(False),
+            persisted_voucher.state,
+            Equals(u"pending"),
         )
 
     @given(tahoe_configs(), datetimes(), vouchers())
@@ -163,8 +163,8 @@ class PaymentControllerTests(TestCase):
 
         persisted_voucher = store.get(voucher)
         self.assertThat(
-            persisted_voucher.redeemed,
-            Equals(True),
+            persisted_voucher.state,
+            Equals(u"redeemed"),
         )
 
 
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index 925f464..615d202 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -134,7 +134,7 @@ class VoucherStoreTests(TestCase):
             store.get(voucher),
             MatchesStructure(
                 number=Equals(voucher),
-                redeemed=Equals(False),
+                state=Equals(u"pending"),
                 created=Equals(now),
             ),
         )
@@ -159,7 +159,7 @@ class VoucherStoreTests(TestCase):
             MatchesStructure(
                 number=Equals(voucher),
                 created=Equals(now),
-                redeemed=Equals(False),
+                state=Equals(u"pending"),
                 token_count=Equals(None),
             ),
         )
@@ -339,7 +339,7 @@ class UnblindedTokenStoreTests(TestCase):
         self.assertThat(
             loaded_voucher,
             MatchesStructure(
-                redeemed=Equals(True),
+                state=Equals(u"redeemed"),
                 token_count=Equals(num_tokens),
             ),
         )
-- 
GitLab