diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 88bad2c1d14e94924c000cec0b2483b43e60aec1..b6f9a931e07588d20a92801501e88570d39c5723 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -533,6 +533,17 @@ class VoucherStore(object): ) return list(UnblindedToken(t.encode("ascii")) for (t,) in texts) + @with_cursor + def count_random_tokens(self, cursor) -> int: + """ + :return: The number of random tokens present in the database. This is + usually not interesting but it is exposed so the test suite can check + invariants related to it. + """ + cursor.execute("SELECT count(1) FROM [tokens]") + (count,) = cursor.fetchone() + return count + @with_cursor def count_unblinded_tokens(self, cursor): """ diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py index 494e0af7940f1ecca5ae5fef42044a5069cfe1bf..7bd9aecb33ed096b1cc0bdf9b9707349caada4e6 100644 --- a/src/_zkapauthorizer/tests/test_model.py +++ b/src/_zkapauthorizer/tests/test_model.py @@ -452,6 +452,21 @@ class UnblindedTokenStateMachine(RuleBasedStateMachine): self.available += len(self.using) del self.using[:] + @invariant() + def random_token_count(self): + """ + ``VoucherStore.random_token_count`` returns ``0``. + + The state machine currently jumps over all intermediate states where + this function could legitimately return non-zero. It might be nice to + split up the ``get_passes`` rule so that we could see other values + sometimes. + """ + self.case.assertThat( + self.configless.store.count_random_tokens(), + Equals(0), + ) + @invariant() def unblinded_token_count(self): """