+
+
+
+# testing – run with: python3 -m unittest todo.py
+
+class TestWithDB(TestCase):
+
+ def setUp(self):
+ self.db_file = TodoDBFile(f'test_db:{datetime.now().timestamp()}', force_creation=True)
+ self.db_conn = TodoDBConnection(self.db_file)
+ self.bak_0_path = f'{self.db_file.path}.bak.0'
+
+ def tearDown(self):
+ from os import remove
+ remove(self.db_file.path)
+ for i in range(0, 9):
+ bak_path = f'{self.db_file.path}.bak.{i}'
+ if isfile(f'{self.db_file.path}.bak.{i}'):
+ remove(bak_path)
+
+ def test_backup_bak_0_file_content(self):
+ day = Day('2024-01-01')
+ day.save(self.db_conn)
+ self.db_conn.commit() # backups before writing, so expect different file contents
+ with open(self.db_file.path, 'rb') as f1:
+ original_content = f1.read()
+ with open(self.bak_0_path, 'rb') as f2:
+ backup_content = f2.read()
+ self.assertNotEqual(original_content, backup_content)
+ self.db_conn.commit() # this time commit without changes, so expect equal file contents
+ with open(self.db_file.path, 'rb') as f1:
+ original_content = f1.read()
+ with open(self.bak_0_path, 'rb') as f2:
+ backup_content = f2.read()
+ self.assertEqual(original_content, backup_content)
+
+ def test_backup_bak_0_file_attributes(self):
+ from os import stat
+ sleep(0.1) # so mtime would change if not copied
+ self.db_conn.commit()
+ original_stat = stat(self.db_file.path)
+ backup_stat = stat(self.bak_0_path)
+ for name in {'st_mode', 'st_dev', 'st_nlink', 'st_uid', 'st_gid', 'st_size', 'st_atime', 'st_mtime'}:
+ self.assertEqual(getattr(original_stat, name), getattr(backup_stat, name))
+
+ def test_Day_by_date(self):
+ test_date_1 = '2024-02-28'
+ test_date_2 = '2024-02-29'
+ test_comment = 'foo'
+ day1 = Day(test_date_1, test_comment)
+ day1.save(self.db_conn)
+ retrieved_day = Day.by_date(self.db_conn, test_date_1)
+ self.assertEqual(day1, retrieved_day)
+ self.assertEqual(day1.comment, retrieved_day.comment)
+ self.assertEqual(None, Day.by_date(self.db_conn, test_date_2))
+ self.assertEqual(Day(test_date_2), Day.by_date(self.db_conn, test_date_2, make_if_none=True))
+
+ def test_Day_all(self):
+ with self.assertRaises(HandledException):
+ Day.all(self.db_conn, date_range=(None, None))
+ Day.all(self.db_conn, date_range=('foo', ''))
+ Day.all(self.db_conn, date_range=('', '2024-02-30'))
+ test_date_1 = str(datetime.now() - timedelta(days=2))[:10]
+ test_date_2 = Day.todays_date()
+ test_date_3 = str(datetime.now() + timedelta(days=2))[:10]
+ day1 = Day(test_date_1)
+ day2 = Day(test_date_2)
+ day3 = Day(test_date_3)
+ day1.save(self.db_conn)
+ day2.save(self.db_conn)
+ day3.save(self.db_conn)
+ self.assertEqual([day1, day2, day3], Day.all(self.db_conn))
+ self.assertEqual([day2, day3], Day.all(self.db_conn, date_range=('yesterday', '')))
+ self.assertEqual([day2, day3], Day.all(self.db_conn, date_range=(Day.yesterdays_date(), '')))
+ self.assertEqual([day3], Day.all(self.db_conn, date_range=('tomorrow', '')))
+ self.assertEqual([day3], Day.all(self.db_conn, date_range=(Day.tomorrows_date(), '')))
+ self.assertEqual([day2], Day.all(self.db_conn, date_range=('today', Day.todays_date())))
+ self.assertEqual([day1], Day.all(self.db_conn, date_range=('', 'yesterday')))
+ self.assertEqual([Day(Day.yesterdays_date()), day2],
+ Day.all(self.db_conn, ensure_betweens=True, date_range=('yesterday', 'today')))
+ self.assertEqual([day2, Day(Day.tomorrows_date())],
+ Day.all(self.db_conn, ensure_betweens=True, date_range=('today', 'tomorrow')))
+
+ def test_TodoTemplate_by_id(self):
+ tmpl = TodoTemplate(self.db_conn)
+ tmpl.save(self.db_conn)
+ retrieved = TodoTemplate.by_id(self.db_conn, tmpl.id_)
+ self.assertEqual(tmpl.id_, retrieved.id_)
+ self.assertEqual(None, TodoTemplate.by_id(self.db_conn, tmpl.id_ + 1))
+ self.assertIsInstance(TodoTemplate.by_id(self.db_conn, tmpl.id_ + 1, make_if_none=True), TodoTemplate)
+
+ def test_TodoTemplate_all(self):
+ tmpl_1 = TodoTemplate(self.db_conn)
+ tmpl_1.save(self.db_conn)
+ tmpl_2 = TodoTemplate(self.db_conn)
+ tmpl_2.save(self.db_conn)
+ self.assertEqual({tmpl_1.id_, tmpl_2.id_}, set(t.id_ for t in TodoTemplate.all(self.db_conn)))
+
+ def test_versioned_attributes(self):
+ def test(name, default, values):
+ def wait_till_next_timestamp(timestamp):
+ # if we .set() to early, the timestamp key will not have changed,
+ # i.e. we'd simply over-write the previous value
+ while Day.todays_date(with_time=True) == timestamp:
+ sleep(0.0001)
+ return Day.todays_date(with_time=True)
+ tmpl = TodoTemplate(self.db_conn)
+ tmpl.save(self.db_conn)
+ tmpl_attr = getattr(tmpl, name)
+ # check we get default value on empty history
+ self.assertEqual(tmpl_attr.newest, default)
+ self.assertEqual({}, tmpl_attr.history)
+ # check we get new value when set
+ timestamp_1 = Day.todays_date(with_time=True)
+ tmpl_attr.set(values[0])
+ self.assertEqual(tmpl_attr.newest, values[0])
+ # check history remains unchanged if setting same value as .newest
+ timestamp_2 = wait_till_next_timestamp(timestamp_1)
+ tmpl_attr.set(values[0])
+ self.assertEqual(len(tmpl_attr.history), 1)
+ # check we can access different values with .newest and .at
+ tmpl_attr.set(values[1])
+ self.assertEqual(tmpl_attr.newest, values[1])
+ self.assertEqual(tmpl_attr.at(timestamp_1), values[0])
+ # check attribute history stored in DB with parent
+ tmpl.save(self.db_conn)
+ retrieved = TodoTemplate.by_id(self.db_conn, tmpl.id_)
+ self.assertEqual(getattr(retrieved, name).at(timestamp_1), values[0]) # i.e. attribute.save() works
+ # check forks can use original's history, but only up to their fork moment
+ fork = tmpl.fork(self.db_conn)
+ wait_till_next_timestamp(timestamp_2)
+ tmpl_attr.set(values[2])
+ forked_attr = getattr(fork, name)
+ self.assertEqual(forked_attr.newest, values[1])
+ self.assertEqual(forked_attr.at(timestamp_1), values[0])
+ # check original and fork bifurcate their history
+ forked_attr.set(values[0])
+ self.assertEqual(forked_attr.newest, values[0])
+ self.assertEqual(tmpl_attr.newest, values[2])
+ test('title', 'UNNAMED', ['foo', 'bar', 'baz'])
+ test('default_effort', 1.0, [0.5, 3, 9])
+ test('description', '', ['foo', 'bar', 'baz'])
+
+
+
+class TestSansDB(TestCase):
+
+ def test_Day_date_validate(self):
+ self.assertEqual(None, Day.date_valid('foo'))
+ self.assertEqual(None, Day.date_valid('2024-02-30'))
+ self.assertEqual(None, Day.date_valid('2024-01-01 23:59:59'))
+ self.assertEqual(datetime(2024,1,1), Day.date_valid('2024-01-01'))
+
+ def test_Day_date_classmethods(self):
+ self.assertEqual(str(datetime.now())[:10], Day.todays_date())
+ self.assertEqual(str(datetime.now())[:DATETIME_KEY_RESOLUTION], Day.todays_date(with_time=True))
+ self.assertEqual(str(datetime.now() - timedelta(days=1))[:10], Day.yesterdays_date())
+ self.assertEqual(str(datetime.now() + timedelta(days=1))[:10], Day.tomorrows_date())
+
+ def test_Day_init(self):
+ test_date = '2024-02-29'
+ test_comment = 'foo'
+ day = Day(test_date)
+ self.assertEqual(day.date, test_date)
+ self.assertEqual(day.comment, '')
+ day = Day(test_date, test_comment)
+ self.assertEqual(day.comment, test_comment)
+ with self.assertRaises(HandledException):
+ Day('foo')
+
+ def test_Day_date_neighbors(self):
+ day = Day('2024-02-29')
+ self.assertEqual(day.prev_date, '2024-02-28')
+ self.assertEqual(day.next_date, '2024-03-01')
+
+ def test_Day_date_weekday(self):
+ day = Day('2024-02-29')
+ self.assertEqual(day.weekday, 'Thursday')
+
+ def test_Day_cmp(self):
+ day1 = Day('2024-01-01')
+ day2 = Day('2024-01-02')
+ day3 = Day('2024-01-03')
+ days = [day3, day1, day2]
+ self.assertEqual(sorted(days), [day1, day2, day3])