From 739d852581d8cbeb04322e363067411a78e6e374 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Fri, 10 Apr 2020 14:12:12 -0400
Subject: [PATCH] Test token backup and spend ordering and make it the same as
 insert order

---
 src/_zkapauthorizer/model.py            |  4 +-
 src/_zkapauthorizer/tests/test_model.py | 79 ++++++++++++++++++++++++-
 2 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 3cb8204..8731977 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -417,7 +417,7 @@ class VoucherStore(object):
             """
             CREATE TEMPORARY TABLE [extracting]
             AS
-            SELECT [token] FROM [unblinded-tokens] ORDER BY [token] LIMIT ?
+            SELECT [token] FROM [unblinded-tokens] LIMIT ?
             """,
             (count,),
         )
@@ -451,7 +451,7 @@ class VoucherStore(object):
         """
         cursor.execute(
             """
-            SELECT [token] FROM [unblinded-tokens] ORDER BY [token]
+            SELECT [token] FROM [unblinded-tokens]
             """,
         )
         tokens = cursor.fetchall()
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index 8929957..648e610 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -246,6 +246,81 @@ class VoucherStoreTests(TestCase):
             raises(StoreOpenError),
         )
 
+    @given(tahoe_configs(), vouchers(), dummy_ristretto_keys(), datetimes(), data())
+    def test_spend_order_equals_backup_order(self, get_config, voucher_value, public_key, now, data):
+        """
+        Unblinded tokens returned by ``VoucherStore.backup`` appear in the same
+        order as they are returned ``VoucherStore.extract_unblinded_tokens``.
+        """
+        backed_up_tokens, spent_tokens, inserted_tokens = self._spend_order_test(
+            get_config,
+            voucher_value,
+            public_key,
+            now,
+            data
+        )
+        self.assertThat(
+            backed_up_tokens,
+            Equals(spent_tokens),
+        )
+
+
+    @given(tahoe_configs(), vouchers(), dummy_ristretto_keys(), datetimes(), data())
+    def test_spend_order_equals_insert_order(self, get_config, voucher_value, public_key, now, data):
+        """
+        Unblinded tokens returned by ``VoucherStore.extract_unblinded_tokens``
+        appear in the same order as they were inserted.
+        """
+        backed_up_tokens, spent_tokens, inserted_tokens = self._spend_order_test(
+            get_config,
+            voucher_value,
+            public_key,
+            now,
+            data
+        )
+        self.assertThat(
+            spent_tokens,
+            Equals(inserted_tokens),
+        )
+
+
+    def _spend_order_test(self, get_config, voucher_value, public_key, now, data):
+        tempdir = self.useFixture(TempDir())
+        nodedir = tempdir.join(b"node")
+
+        config = get_config(nodedir, b"tub.port")
+
+        # Create the underlying database file.
+        store = VoucherStore.from_node_config(config, lambda: now)
+
+        # Put some tokens in it that we can backup and extract
+        random_tokens, unblinded_tokens = paired_tokens(data, integers(min_value=1, max_value=5))
+        store.add(voucher_value, lambda: random_tokens)
+        store.insert_unblinded_tokens_for_voucher(
+            voucher_value,
+            public_key,
+            unblinded_tokens,
+        )
+
+        backed_up_tokens = store.backup()[u"unblinded-tokens"]
+        extracted_tokens = []
+        tokens_remaining = len(unblinded_tokens)
+        while tokens_remaining > 0:
+            to_spend = data.draw(integers(min_value=1, max_value=tokens_remaining))
+            extracted_tokens.extend(
+                token.unblinded_token
+                for token
+                in store.extract_unblinded_tokens(to_spend)
+            )
+            tokens_remaining -= to_spend
+
+        return (
+            backed_up_tokens,
+            extracted_tokens,
+            list(token.unblinded_token for token in unblinded_tokens),
+        )
+
+
 
 class LeaseMaintenanceTests(TestCase):
     """
@@ -331,14 +406,14 @@ class VoucherTests(TestCase):
         )
 
 
-def paired_tokens(data):
+def paired_tokens(data, sizes=integers(min_value=1, max_value=1000)):
     """
     Draw two lists of the same length, one of random tokens and one of
     unblinded tokens.
 
     :rtype: ([RandomTokens], [UnblindedTokens])
     """
-    num_tokens = data.draw(integers(min_value=1, max_value=1000))
+    num_tokens = data.draw(sizes)
     r = data.draw(lists(
         random_tokens(),
         min_size=num_tokens,
-- 
GitLab