# 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 TestFilter(unittest.TestCase): """Tests our database row Filter.""" def setUp(self): """Set up common variables.""" self.filter = emmental.db.table.Filter() 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 filter is created correctly.""" self.assertIsInstance(self.filter, Gtk.Filter) self.assertIsNone(self.filter._keys, None) self.assertEqual(self.filter.n_keys, -1) filter2 = emmental.db.table.Filter(keys={1, 2, 3}) self.assertSetEqual(filter2._keys, {1, 2, 3}) self.assertEqual(filter2.n_keys, 3) def test_subtract(self, mock_changed: unittest.mock.Mock): """Test subtracting two filters.""" filter2 = emmental.db.table.Filter(keys={2, 3}) self.assertIsNone(self.filter - self.filter) self.assertIsNone(self.filter - filter2) self.assertSetEqual(filter2 - self.filter, {2, 3}) self.filter.keys = {1, 2, 3, 4, 5} self.assertSetEqual(self.filter - filter2, {1, 4, 5}) self.assertSetEqual(filter2 - self.filter, set()) def test_strictness(self, mock_changed: unittest.mock.Mock): """Test checking strictness.""" self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.ALL) self.filter._keys = set() self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.NONE) self.filter._keys = {1, 2, 3} self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME) def test_add_row(self, mock_changed: unittest.mock.Mock): """Test adding Rows to the filter.""" self.filter.add_row(self.row1) self.assertIsNone(self.filter.keys) self.filter.keys = set() self.filter.add_row(self.row1) self.assertSetEqual(self.filter.keys, {1}) mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT) self.assertEqual(self.filter.n_keys, 1) self.filter.add_row(self.row2) self.assertSetEqual(self.filter.keys, {1, 2}) mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT) self.assertEqual(self.filter.n_keys, 2) def test_remove_row(self, mock_changed: unittest.mock.Mock): """Test removing Rows from the filter.""" self.filter.remove_row(self.row1) mock_changed.assert_not_called() self.filter.keys = {1, 2} self.filter.remove_row(self.row1) self.assertSetEqual(self.filter._keys, {2}) mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT) self.assertEqual(self.filter.n_keys, 1) mock_changed.reset_mock() self.filter.remove_row(self.row2) self.assertSetEqual(self.filter._keys, set()) mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT) self.assertEqual(self.filter.n_keys, 0) def test_keys(self, mock_changed: unittest.mock.Mock): """Test setting and getting the filter keys property.""" self.assertIsNone(self.filter.keys) self.filter.keys = {1, 2, 3} self.assertSetEqual(self.filter._keys, {1, 2, 3}) mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT) self.assertEqual(self.filter.n_keys, 3) mock_changed.reset_mock() self.filter.keys = {1, 2} self.assertSetEqual(self.filter.keys, {1, 2}) mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT) self.assertEqual(self.filter.n_keys, 2) mock_changed.reset_mock() self.filter.keys = {1, 2} mock_changed.assert_not_called() self.filter.keys = {1, 2, 3} self.assertSetEqual(self.filter.keys, {1, 2, 3}) mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT) self.filter.keys = {4, 5, 6} self.assertSetEqual(self.filter._keys, {4, 5, 6}) mock_changed.assert_called_with(Gtk.FilterChange.DIFFERENT) self.filter.keys = None self.assertIsNone(self.filter._keys) mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT) self.assertEqual(self.filter.n_keys, -1) def test_match(self, mock_changed: unittest.mock.Mock): """Test matching playlists.""" self.assertTrue(self.filter.match(self.row1)) self.filter.keys = {1, 2, 3} self.assertTrue(self.filter.match(self.row1)) self.filter.keys = {4, 5, 6} self.assertFalse(self.filter.match(self.row1)) self.filter.keys = set() self.assertFalse(self.filter.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.Filter) 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.Filter() 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) self.assertTrue(row.delete()) self.assertEqual(len(self.table), 0) self.assertDictEqual(self.table.rows, dict()) 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))