owenrusk.dev

casebench

test cases straight onto the board.

git clone https://owenrusk.dev/casebench.git

casebench / tests/test_lock.py -rw-r--r-- · 5213 bytes

  1 import os
  2 import unittest
  3 from datetime import UTC, datetime, timedelta
  4 from unittest import mock
  5 
  6 from casebench import lock
  7 from casebench.memstore import MemStore
  8 
  9 from .helpers import UMBRELLA, run
 10 
 11 T0 = datetime(2025, 7, 17, 14, 0, tzinfo=UTC)
 12 
 13 
 14 class AttemptTest(unittest.TestCase):
 15     def setUp(self):
 16         self.lock = lock.make(["Tuesday", "114"], lock.Rest(3, 60))
 17 
 18     def test_opens_on_both_lines(self):
 19         result, after = lock.attempt(self.lock, ["tuesday", " 114 "], T0)
 20         self.assertEqual(result, "open")
 21         self.assertEqual(after.opened_at, T0)
 22 
 23     def test_one_line_right_is_still_wrong(self):
 24         result, after = lock.attempt(self.lock, ["tuesday", "115"], T0)
 25         self.assertEqual(result, "wrong")
 26         self.assertEqual(after.wrong, 1)
 27 
 28     def test_wrong_number_of_lines(self):
 29         result, _ = lock.attempt(self.lock, ["tuesday"], T0)
 30         self.assertEqual(result, "wrong")
 31 
 32     def test_rests_after_three_wrong(self):
 33         state = self.lock
 34         for _ in range(3):
 35             result, state = lock.attempt(state, ["no", "no"], T0)
 36         self.assertEqual(result, "wrong")
 37         self.assertEqual(state.resting_until, T0 + timedelta(minutes=60))
 38 
 39     def test_takes_nothing_while_resting(self):
 40         state = self.lock
 41         for _ in range(3):
 42             _, state = lock.attempt(state, ["no", "no"], T0)
 43         result, held = lock.attempt(state, ["tuesday", "114"], T0 + timedelta(minutes=59))
 44         self.assertEqual(result, "resting")
 45         self.assertIs(held, state)
 46         result, _ = lock.attempt(state, ["tuesday", "114"], T0 + timedelta(minutes=60))
 47         self.assertEqual(result, "open")
 48 
 49     def test_count_starts_over_after_a_rest(self):
 50         state = self.lock
 51         for _ in range(3):
 52             _, state = lock.attempt(state, ["no", "no"], T0)
 53         result, state = lock.attempt(state, ["no", "no"], T0 + timedelta(minutes=61))
 54         self.assertEqual(result, "wrong")
 55         self.assertEqual(state.wrong, 1)
 56 
 57     def test_right_answer_clears_the_count(self):
 58         _, state = lock.attempt(self.lock, ["no", "no"], T0)
 59         _, state = lock.attempt(state, ["tuesday", "114"], T0)
 60         self.assertEqual(state.wrong, 0)
 61 
 62     def test_answers_are_not_stored(self):
 63         self.assertNotIn("tuesday", repr(self.lock).casefold())
 64 
 65     def test_alternates(self):
 66         either = lock.make(["grey|gray", "glove|mitten"], lock.Rest(3, 60))
 67         self.assertEqual(len(either.hashes), 4)
 68         for typed in (["Gray", "MITTEN"], ["grey", "glove"]):
 69             self.assertEqual(lock.attempt(either, typed, T0)[0], "open")
 70         self.assertEqual(lock.attempt(either, ["grey", "hat"], T0)[0], "wrong")
 71 
 72     def test_too_many_combinations(self):
 73         with self.assertRaisesRegex(ValueError, "combinations"):
 74             lock.make(["a|b|c|d"] * 4, lock.Rest(3, 60))
 75 
 76     def test_empty_line(self):
 77         with self.assertRaises(ValueError):
 78             lock.make(["a", " | "], lock.Rest(3, 60))
 79 
 80     def test_parse_rest(self):
 81         self.assertEqual(lock.parse_rest("3/60"), lock.Rest(3, 60))
 82         for bad in ("3", "0/60", "a/b", "3/-1"):
 83             with self.assertRaises(ValueError):
 84                 lock.parse_rest(bad)
 85 
 86 
 87 class LockCommandTest(unittest.TestCase):
 88     def setUp(self):
 89         self.store = MemStore(("owen",))
 90         self.owen = self.store.player("owen")
 91         run(self.store, "put", str(UMBRELLA), "--player", "owen")
 92 
 93     def test_lock_and_unlock(self):
 94         code, _, _ = run(
 95             self.store, "lock", "test-lost-umbrella", "--answer", "tuesday", "--rest", "5/10", "--player", "owen"
 96         )
 97         self.assertEqual(code, 0)
 98         held = self.store.get_lock(self.owen, "test-lost-umbrella")
 99         self.assertEqual((held.text, held.lines, held.rest), ("locked.", 1, lock.Rest(5, 10)))
100         code, out, _ = run(self.store, "unlock", "test-lost-umbrella", "--player", "owen")
101         self.assertEqual((code, out), (0, "owen: unlocked test-lost-umbrella\n"))
102         self.assertIsNone(self.store.get_lock(self.owen, "test-lost-umbrella"))
103 
104     def test_unlock_when_not_locked(self):
105         code, _, err = run(self.store, "unlock", "test-lost-umbrella", "--player", "owen")
106         self.assertEqual(code, 1)
107         self.assertIn("wasn't locked anywhere", err)
108 
109     def test_locks_a_runtime_case(self):
110         self.store.runtime_case(self.owen, "case-x", "A real case")
111         code, _, _ = run(self.store, "lock", "case-x", "--answer", "a", "--player", "owen")
112         self.assertEqual(code, 0)
113         self.assertIsNotNone(self.store.get_lock(self.owen, "case-x"))
114 
115     def test_rest_from_the_environment(self):
116         with mock.patch.dict(os.environ, {"CASEBENCH_REST": "2/15"}):
117             run(self.store, "lock", "test-lost-umbrella", "--answer", "a", "--player", "owen")
118         self.assertEqual(self.store.get_lock(self.owen, "test-lost-umbrella").rest, lock.Rest(2, 15))
119 
120     def test_bad_rest(self):
121         code, _, err = run(self.store, "lock", "test-lost-umbrella", "--answer", "a", "--rest", "three", "--player", "owen")
122         self.assertEqual(code, 1)
123         self.assertIn("tries/minutes", err)
124 
125 
126 if __name__ == "__main__":
127     unittest.main()