import pathlib import sys import unittest sys.path.insert(0, str(pathlib.Path(__file__).parents[1])) from password_setup import PasswordSetupGrants class PasswordSetupGrantTests(unittest.TestCase): def setUp(self): self.now = 100.0 self.calls = [] self.grants = PasswordSetupGrants( public_url="https://kc.example/setup/password", setter=lambda subject, password: self.calls.append((subject, password)), ttl_seconds=60, clock=lambda: self.now, ) def token(self, url): return url.partition("token=")[2] def test_grant_is_single_use(self): token = self.token(self.grants.issue("binky-admin")) self.assertTrue(self.grants.valid(token)) self.grants.consume(token, "a-secure-password") self.assertEqual([("binky-admin", "a-secure-password")], self.calls) self.assertFalse(self.grants.valid(token)) with self.assertRaisesRegex(ValueError, "invalid or expired"): self.grants.consume(token, "a-secure-password") def test_new_grant_revokes_previous_subject_grant(self): first = self.token(self.grants.issue("binky-admin")) second = self.token(self.grants.issue("binky-admin")) self.assertFalse(self.grants.valid(first)) self.assertTrue(self.grants.valid(second)) def test_expired_grant_fails_closed(self): token = self.token(self.grants.issue("binky-admin")) self.now = 161.0 self.assertFalse(self.grants.valid(token)) with self.assertRaisesRegex(ValueError, "invalid or expired"): self.grants.consume(token, "a-secure-password") def test_password_policy_precedes_consumption(self): token = self.token(self.grants.issue("binky-admin")) with self.assertRaisesRegex(ValueError, "12 characters"): self.grants.consume(token, "too-short") self.assertTrue(self.grants.valid(token))