diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 8691fface49804e256e2a003930cb9035c155da7..3cb8204f300cc894b3d40ff1d80aed4b0d0e2d0b 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -94,6 +94,13 @@ class StoreOpenError(Exception): self.reason = reason +class NotEnoughTokens(Exception): + """ + An attempt to extract tokens failed because the store does not contain as + many tokens as were requested. + """ + + CONFIG_DB_NAME = u"privatestorageio-zkapauthz-v1.sqlite3" def open_and_initialize(path, connect=None): @@ -396,6 +403,16 @@ class VoucherStore(object): :return list[UnblindedTokens]: The removed unblinded tokens. """ + cursor.execute( + """ + SELECT COUNT(token) + FROM [unblinded-tokens] + """, + ) + [(existing_tokens,)] = cursor.fetchall() + if existing_tokens < count: + raise NotEnoughTokens() + cursor.execute( """ CREATE TEMPORARY TABLE [extracting] diff --git a/src/_zkapauthorizer/tests/_exception.py b/src/_zkapauthorizer/tests/_exception.py index 504e383cb5a664cee0a0c11449721072e3bb80c4..5e8bc8f5b21c29ebba4e93528a417761ff3fcf1d 100644 --- a/src/_zkapauthorizer/tests/_exception.py +++ b/src/_zkapauthorizer/tests/_exception.py @@ -60,7 +60,7 @@ class MatchesExceptionType(Matcher): etype, evalue, etb = other if not issubclass(etype, expected_class): return Mismatch( - "{} is an instance of {}, expected an instance of {}.".format( + "{!r} is an instance of {}, expected an instance of {}.".format( evalue, etype, expected_class, diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index c4885ed96367e4227a46dc080e2f66df1895d780..8929957edf209af5949778fa12d58af6b3807922 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -45,7 +45,6 @@ from testtools.matchers import ( Equals, Raises, IsInstance, - raises, ) from fixtures import ( @@ -75,6 +74,7 @@ from ..storage_common import ( from ..model import ( StoreOpenError, + NotEnoughTokens, VoucherStore, Voucher, Pending, @@ -96,6 +96,9 @@ from .strategies import ( from .fixtures import ( TemporaryVoucherStore, ) +from .matchers import ( + raises, +) class VoucherStoreTests(TestCase): @@ -367,14 +370,14 @@ class UnblindedTokenStoreTests(TestCase): Unblinded tokens for a voucher which has not been added to the store cannot be inserted. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - try: - result = store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded_tokens) - except ValueError: - pass - except Exception as e: - self.fail("insert_unblinded_tokens_for_voucher raised the wrong exception: {}".format(e)) - else: - self.fail("insert_unblinded_tokens_for_voucher didn't raise, returned: {}".format(result)) + self.assertThat( + lambda: store.insert_unblinded_tokens_for_voucher( + voucher_value, + public_key, + unblinded_tokens, + ), + raises(ValueError), + ) @given( tahoe_configs(), @@ -395,8 +398,10 @@ class UnblindedTokenStoreTests(TestCase): self.expectThat(unblinded_tokens, AfterPreprocessing(sorted, Equals(retrieved_tokens))) # After extraction, the unblinded tokens are no longer available. - more_unblinded_tokens = store.extract_unblinded_tokens(1) - self.expectThat([], Equals(more_unblinded_tokens)) + self.assertThat( + lambda: store.extract_unblinded_tokens(1), + raises(NotEnoughTokens), + ) @given( tahoe_configs(), @@ -498,14 +503,10 @@ class UnblindedTokenStoreTests(TestCase): store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store store.add(voucher_value, lambda: random) store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded) - try: - result = store.mark_voucher_double_spent(voucher_value) - except ValueError: - pass - except Exception as e: - self.fail("mark_voucher_double_spent raised the wrong exception: {}".format(e)) - else: - self.fail("mark_voucher_double_spent didn't raise, returned: {}".format(result)) + self.assertThat( + lambda: store.mark_voucher_double_spent(voucher_value), + raises(ValueError), + ) @given( tahoe_configs(), @@ -517,14 +518,49 @@ class UnblindedTokenStoreTests(TestCase): A voucher which is not known cannot be marked as double-spent. """ store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store - try: - result = store.mark_voucher_double_spent(voucher_value) - except ValueError: - pass - except Exception as e: - self.fail("mark_voucher_double_spent raised the wrong exception: {}".format(e)) - else: - self.fail("mark_voucher_double_spent didn't raise, returned: {}".format(result)) + self.assertThat( + lambda: store.mark_voucher_double_spent(voucher_value), + raises(ValueError), + ) + + @given( + tahoe_configs(), + datetimes(), + vouchers(), + dummy_ristretto_keys(), + integers(min_value=1, max_value=100), + integers(min_value=1), + data(), + ) + def test_not_enough_unblinded_tokens(self, get_config, now, voucher_value, public_key, num_tokens, extra, data): + """ + ``extract_unblinded_tokens`` raises ``NotEnoughTokens`` if ``count`` is + greater than the number of unblinded tokens in the store. + """ + 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, + ), + ) + store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store + store.add(voucher_value, lambda: random) + store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded) + + self.assertThat( + lambda: store.extract_unblinded_tokens(num_tokens + extra), + raises(NotEnoughTokens), + ) # TODO: Other error states and transient states diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 6fb6a13963c7f4ca92ae36f4e0d91266da5f91ab..48f8cfcee9ceed36b72dc9df7dd42953c1eee11f 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -42,7 +42,6 @@ from testtools.matchers import ( Always, Contains, AfterPreprocessing, - Equals, ) from testtools.twistedsupport import ( succeeded, @@ -100,6 +99,7 @@ from ..foolscap import ( RIPrivacyPassAuthorizedStorageServer, ) from ..model import ( + NotEnoughTokens, VoucherStore, ) from ..controller import ( @@ -107,6 +107,10 @@ from ..controller import ( PaymentController, DummyRedeemer, ) +from ..storage_common import ( + BYTES_PER_PASS, + required_passes, +) from .._storage_client import ( IncorrectStorageServerReference, ) @@ -318,14 +322,14 @@ class ClientPluginTests(TestCase): node_config.config.write(config_text) self.addDetail(u"config", text_content(config_text.getvalue())) self.addDetail(u"announcement", text_content(unicode(announcement))) - try: - result = storage_server.get_storage_client(node_config, announcement, get_rref) - except IssuerConfigurationMismatch: - pass - except Exception as e: - self.fail("get_storage_client raised the wrong exception: {}".format(e)) - else: - self.fail("get_storage_client didn't raise, returned: {}".format(result)) + self.assertThat( + lambda: storage_server.get_storage_client( + node_config, + announcement, + get_rref, + ), + raises(IssuerConfigurationMismatch), + ) @given( @@ -418,7 +422,8 @@ class ClientPluginTests(TestCase): controller = PaymentController( store, DummyRedeemer(), - 1, + # Give it enough for the allocate_buckets call below. + required_passes(BYTES_PER_PASS, [size] * len(sharenums)), ) # Get a token inserted into the store. redeeming = controller.redeem(voucher) @@ -445,10 +450,9 @@ class ClientPluginTests(TestCase): ) # There should be no unblinded tokens left to extract. - remaining = store.extract_unblinded_tokens(1) self.assertThat( - remaining, - Equals([]), + lambda: store.extract_unblinded_tokens(1), + raises(NotEnoughTokens), )