emmental/tests/db/test_table.py

396 lines
15 KiB
Python

# Copyright 2022 (c) Anna Schumaker
"""Tests our database object base classes."""
import unittest
import unittest.mock
import emmental.db.table
import emmental.store
import tests.util.table
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk
class TestRow(unittest.TestCase):
"""Tests our common database Row object."""
def setUp(self):
"""Set up common variables."""
self.table = Gio.ListStore()
self.table.delete = unittest.mock.Mock(return_value=True)
self.table.update = unittest.mock.Mock(return_value=True)
self.row = tests.util.table.MockRow(table=self.table)
def test_init(self):
"""Test that the database Row is configured correctly."""
self.assertIsInstance(self.row, emmental.db.table.Row)
self.assertIsInstance(self.row, GObject.GObject)
self.assertEqual(self.row.table, self.table)
def test_primary_key(self):
"""Test the primary_key property."""
with self.assertRaises(NotImplementedError):
emmental.db.table.Row(self.table).primary_key
self.row.number = 2
self.assertEqual(self.row.primary_key, 2)
def test_do_update(self):
"""Test updating a Row attribute."""
self.row.number = 1
self.table.update.assert_called_with(self.row, "number", 1)
self.table.update.reset_mock()
self.row.table = None
self.table.update.assert_not_called()
def test_delete(self):
"""Test deleting a Row."""
self.assertTrue(self.row.delete())
self.table.delete.assert_called_with(self.row)
@unittest.mock.patch("gi.repository.Gtk.Filter.changed")
class TestKeySet(unittest.TestCase):
"""Tests our KeySet for holding database Rows."""
def setUp(self):
"""Set up common variables."""
self.keyset = emmental.db.table.KeySet()
self.table = Gio.ListStore()
self.row1 = tests.util.table.MockRow(number=1, table=self.table)
self.row2 = tests.util.table.MockRow(number=2, table=self.table)
def test_init(self, mock_changed: unittest.mock.Mock):
"""Test that the KeySet is created correctly."""
self.assertIsInstance(self.keyset, Gtk.Filter)
self.assertIsNone(self.keyset._keys, None)
self.assertEqual(self.keyset.n_keys, -1)
keyset2 = emmental.db.table.KeySet(keys={1, 2, 3})
self.assertSetEqual(keyset2._keys, {1, 2, 3})
self.assertEqual(keyset2.n_keys, 3)
def test_subtract(self, mock_changed: unittest.mock.Mock):
"""Test subtracting two KeySets."""
keyset2 = emmental.db.table.KeySet(keys={2, 3})
self.assertIsNone(self.keyset - self.keyset)
self.assertIsNone(self.keyset - keyset2)
self.assertSetEqual(keyset2 - self.keyset, {2, 3})
self.keyset.keys = {1, 2, 3, 4, 5}
self.assertSetEqual(self.keyset - keyset2, {1, 4, 5})
self.assertSetEqual(keyset2 - self.keyset, set())
def test_strictness(self, mock_changed: unittest.mock.Mock):
"""Test checking strictness."""
self.assertEqual(self.keyset.get_strictness(), Gtk.FilterMatch.ALL)
self.keyset._keys = set()
self.assertEqual(self.keyset.get_strictness(), Gtk.FilterMatch.NONE)
self.keyset._keys = {1, 2, 3}
self.assertEqual(self.keyset.get_strictness(), Gtk.FilterMatch.SOME)
def test_add_row(self, mock_changed: unittest.mock.Mock):
"""Test adding Rows to the KeySet."""
self.keyset.add_row(self.row1)
self.assertIsNone(self.keyset.keys)
self.keyset.keys = set()
self.keyset.add_row(self.row1)
self.assertSetEqual(self.keyset.keys, {1})
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
self.assertEqual(self.keyset.n_keys, 1)
self.keyset.add_row(self.row2)
self.assertSetEqual(self.keyset.keys, {1, 2})
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
self.assertEqual(self.keyset.n_keys, 2)
def test_remove_row(self, mock_changed: unittest.mock.Mock):
"""Test removing Rows from the KeySet."""
self.keyset.remove_row(self.row1)
mock_changed.assert_not_called()
self.keyset.keys = {1, 2}
self.keyset.remove_row(self.row1)
self.assertSetEqual(self.keyset._keys, {2})
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
self.assertEqual(self.keyset.n_keys, 1)
mock_changed.reset_mock()
self.keyset.remove_row(self.row2)
self.assertSetEqual(self.keyset._keys, set())
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
self.assertEqual(self.keyset.n_keys, 0)
def test_keys(self, mock_changed: unittest.mock.Mock):
"""Test setting and getting the KeySet keys property."""
self.assertIsNone(self.keyset.keys)
self.keyset.keys = {1, 2, 3}
self.assertSetEqual(self.keyset._keys, {1, 2, 3})
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
self.assertEqual(self.keyset.n_keys, 3)
mock_changed.reset_mock()
self.keyset.keys = {1, 2}
self.assertSetEqual(self.keyset.keys, {1, 2})
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
self.assertEqual(self.keyset.n_keys, 2)
mock_changed.reset_mock()
self.keyset.keys = {1, 2}
mock_changed.assert_not_called()
self.keyset.keys = {1, 2, 3}
self.assertSetEqual(self.keyset.keys, {1, 2, 3})
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
self.keyset.keys = {4, 5, 6}
self.assertSetEqual(self.keyset._keys, {4, 5, 6})
mock_changed.assert_called_with(Gtk.FilterChange.DIFFERENT)
self.keyset.keys = None
self.assertIsNone(self.keyset._keys)
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
self.assertEqual(self.keyset.n_keys, -1)
def test_match(self, mock_changed: unittest.mock.Mock):
"""Test matching Rows."""
self.assertTrue(self.keyset.match(self.row1))
self.keyset.keys = {1, 2, 3}
self.assertTrue(self.keyset.match(self.row1))
self.keyset.keys = {4, 5, 6}
self.assertFalse(self.keyset.match(self.row1))
self.keyset.keys = set()
self.assertFalse(self.keyset.match(self.row1))
class TestTable(tests.util.TestCase):
"""Tests the base Table object."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.table = emmental.db.table.Table(self.sql)
def test_init(self):
"""Test that the table is set up properly."""
self.assertIsInstance(self.table, Gtk.FilterListModel)
self.assertIsInstance(self.table.queue, emmental.db.idle.Queue)
self.assertIsInstance(self.table.get_filter(),
emmental.db.table.KeySet)
self.assertIsInstance(self.table.store, emmental.store.SortedList)
self.assertIsInstance(self.table.rows, dict)
self.assertEqual(self.table.sql, self.sql)
self.assertEqual(self.table.get_model(), self.table.store)
self.assertEqual(self.table.store.key_func, self.table.get_sort_key)
self.assertDictEqual(self.table.rows, {})
self.assertTrue(self.table.get_incremental())
filter2 = emmental.db.table.KeySet()
queue2 = emmental.db.idle.Queue()
table2 = emmental.db.table.Table(self.sql, filter=filter2,
queue=queue2)
self.assertEqual(table2.get_filter(), filter2)
self.assertEqual(table2.queue, queue2)
def test_clear(self):
"""Test clearing a table."""
row = tests.util.table.MockRow(number=1, table=self.table)
self.table.store.append(row)
self.table.loaded = True
self.table.queue.running = True
self.table.clear()
self.assertEqual(self.table.store.n_items, 0)
self.assertDictEqual(self.table.rows, dict())
self.assertFalse(self.table.queue.running)
self.assertFalse(self.table.loaded)
def test_contains(self):
"""Test checking if a Row is already in this Table."""
row1 = tests.util.table.MockRow(number=1, table=self.table)
row2 = tests.util.table.MockRow(number=2, table=self.table)
self.table.insert(row1)
self.assertTrue(row1 in self.table)
self.assertFalse(row2 in self.table)
def test_get_sort_key(self):
"""Test getting a sort key for a row."""
row = tests.util.table.MockRow(number=1, table=self.table)
self.table.insert(row)
self.assertEqual(self.table.get_sort_key(row), 1)
def test_index(self):
"""Test finding the index of rows in the table."""
row1 = tests.util.table.MockRow(number=1, table=self.table)
row2 = tests.util.table.MockRow(number=2, table=self.table)
row3 = tests.util.table.MockRow(number=3, table=self.table)
self.table.insert(row1)
self.table.rows[row3.primary_key] = row3
self.assertEqual(self.table.index(row1), 0)
self.assertIsNone(self.table.index(row2))
self.assertIsNone(self.table.index(row3))
def test_insert(self):
"""Test inserting rows into the table in sorted position."""
row1 = tests.util.table.MockRow(number=1, table=self.table)
row2 = tests.util.table.MockRow(number=2, table=self.table)
row3 = tests.util.table.MockRow(number=3, table=self.table)
self.assertEqual(self.table.insert(row1), row1)
self.assertEqual(self.table.store.get_item(0), row1)
self.assertDictEqual(self.table.rows, {1: row1})
self.assertEqual(self.table.insert(row3), row3)
self.assertEqual(self.table.store.get_item(0), row1)
self.assertEqual(self.table.store.get_item(1), row3)
self.assertDictEqual(self.table.rows, {1: row1, 3: row3})
self.assertEqual(self.table.insert(row2), row2)
self.assertEqual(self.table.store.get_item(0), row1)
self.assertEqual(self.table.store.get_item(1), row2)
self.assertEqual(self.table.store.get_item(2), row3)
self.assertDictEqual(self.table.rows, {1: row1, 2: row2, 3: row3})
row1_again = tests.util.table.MockRow(number=1, table=self.table)
self.assertIsNone(self.table.insert(row1_again))
self.assertIsNone(self.table.insert(row1))
self.assertIsNone(self.table.insert(None))
def test_interface(self):
"""Test that calling interface functions raises an exception."""
with self.assertRaises(NotImplementedError):
self.table.construct(rowid=1)
with self.assertRaises(NotImplementedError):
self.table.create(rowid=1)
with self.assertRaises(NotImplementedError):
self.table.do_sql_delete(None)
with self.assertRaises(NotImplementedError):
self.table.filter("*text*")
self.table.queue.complete()
with self.assertRaises(NotImplementedError):
self.table.load()
self.table.queue.complete()
with self.assertRaises(NotImplementedError):
self.table.lookup(1)
with self.assertRaises(NotImplementedError):
self.table.update(None, "column", 12345)
class TestTableFunctions(tests.util.TestCase):
"""Tests Table functions with a Mock implementation."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.table = tests.util.table.MockTable(self.sql)
def test_construct(self):
"""Test constructing a new Row object."""
row = self.table.construct(number=1)
self.assertIsInstance(row, tests.util.table.MockRow)
self.assertIsInstance(row, emmental.db.table.Row)
self.assertEqual(row.table, self.table)
self.assertEqual(row.number, 1)
def test_create(self):
"""Test creating new rows."""
row = self.table.create(number=1)
self.assertIsInstance(row, tests.util.table.MockRow)
self.assertEqual(self.table.index(row), 0)
self.assertEqual(row.number, 1)
self.assertDictEqual(self.table.rows, {1: row})
self.assertIsNone(self.table.create(number=1))
def test_delete(self):
"""Test deleting rows."""
row = self.table.create(number=1)
with unittest.mock.patch.object(self.sql, "commit") as mock_commit:
self.assertTrue(row.delete())
self.assertEqual(len(self.table), 0)
self.assertDictEqual(self.table.rows, dict())
mock_commit.assert_called()
self.assertFalse(row.delete())
def test_filter(self):
"""Test filtering Rows in the table."""
for n in [1, 121, 212, 333]:
self.table.create(number=n)
self.table.filter("*2*")
self.assertIsNone(self.table.get_filter().keys)
self.assertEqual(self.table.queue[0], (self.table._filter_idle, "*2*"))
self.table.queue.complete()
self.assertSetEqual(self.table.get_filter().keys, {121, 212})
self.table.filter("*1*", now=True)
self.assertSetEqual(self.table.get_filter().keys, {1, 121, 212})
self.table.filter(None)
self.assertIsNone(self.table.queue[0])
self.assertIsNone(self.table.get_filter().keys)
def test_get_sort_key(self):
"""Test getting a sort key for a row."""
row = self.table.create(number=42)
self.assertTupleEqual(self.table.get_sort_key(row), (42, 42))
def test_load(self):
"""Test loading rows from the database."""
self.assertFalse(self.table.loaded)
table_loaded = unittest.mock.Mock()
self.sql.connect("table-loaded", table_loaded)
self.sql("INSERT INTO mock_table (number) VALUES (?)", 1)
self.sql("INSERT INTO mock_table (number) VALUES (?)", 2)
self.table.load()
self.assertFalse(self.table.loaded)
self.assertEqual(len(self.table), 0)
self.assertEqual(self.table.queue[0], (self.table._load_idle,))
self.table.queue.complete()
self.assertTrue(self.table.loaded)
self.assertEqual(len(self.table), 2)
table_loaded.assert_called_with(self.sql, self.table)
row1 = self.table[0]
row2 = self.table[1]
for row, n in [(row1, 1), (row2, 2)]:
with self.subTest(n=n):
self.assertEqual(row.number, n)
self.assertEqual(self.table.rows, {1: row1, 2: row2})
self.table.load(now=True)
self.assertNotEqual(self.table[0], row1)
self.assertNotEqual(self.table[1], row2)
def test_lookup(self):
"""Test looking up rows in the table."""
row = self.table.create(number=1)
self.assertEqual(self.table.lookup(1), row)
self.assertIsNone(self.table.lookup(2))
def test_stop(self):
"""Test the table.stop() function."""
with unittest.mock.patch.object(self.table.queue, "cancel") as cancel:
self.table.stop()
cancel.assert_called()
def test_update(self):
"""Test updating a Row."""
row = self.table.create(number=1)
self.assertTrue(self.table.update(row, "number", 2))
row.number = 2
self.table.create(number=3)
self.assertFalse(self.table.update(row, "number", 3))