#!/usr/bin/env python3 # MIT License # # Copyright (c) 2025 Eugenio Parodi # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys import sqlite3 import tempfile from unittest.mock import Mock import pytest sys.path.append(os.path.join(sys.path[0],'../../../libs/pyTermTk')) import TermTk as ttk class TestTTkTableModelSQLite3: """Test cases for TTkTableModelSQLite3 class""" def setup_method(self): ttk.TTkLog.use_default_stdout_logging() """Set up test database for each test""" # Create temporary database file self.temp_db = tempfile.NamedTemporaryFile(suffix='.db', delete=False) self.temp_db_path = self.temp_db.name self.temp_db.close() # Create test database and table self.conn = sqlite3.connect(self.temp_db_path) self.cur = self.conn.cursor() # Create users table with primary key self.cur.execute(''' CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, role TEXT ) ''') # Insert test data test_data = [ ('Alice', 25, 'Engineer'), ('Bob', 30, 'Designer'), ('Charlie', 35, 'Manager'), ('Diana', 28, 'Developer') ] self.cur.executemany('INSERT INTO users (name, age, role) VALUES (?, ?, ?)', test_data) self.conn.commit() self.conn.close() def teardown_method(self): """Clean up after each test""" if os.path.exists(self.temp_db_path): os.unlink(self.temp_db_path) def test_init_with_valid_database(self): """Test initialization with valid database and table""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') assert model.rowCount() == 4 assert model.columnCount() == 3 # name, age, role (id is primary key, not in columns) def test_init_with_invalid_table(self): """Test initialization with invalid table name""" with pytest.raises(sqlite3.OperationalError): ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='nonexistent_table') def test_init_with_invalid_database(self): """Test initialization with invalid database file""" with pytest.raises(sqlite3.OperationalError): ttk.TTkTableModelSQLite3(fileName='/nonexistent/path/db.sqlite', table='users') def test_column_detection(self): """Test that columns are correctly detected from database schema""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Check header data for columns assert model.headerData(0, ttk.TTkK.HORIZONTAL) == 'name' assert model.headerData(1, ttk.TTkK.HORIZONTAL) == 'age' assert model.headerData(2, ttk.TTkK.HORIZONTAL) == 'role' def test_data_access(self): """Test data retrieval from database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Test data access assert model.data(0, 0) == 'Alice' # First row, name column assert model.data(0, 1) == 25 # First row, age column assert model.data(0, 2) == 'Engineer' # First row, role column assert model.data(1, 0) == 'Bob' assert model.data(2, 0) == 'Charlie' assert model.data(3, 0) == 'Diana' def test_set_data(self): """Test data modification in database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Change Alice's name to 'Alicia' result = model.setData(0, 0, 'Alicia') assert result == True # Verify the change assert model.data(0, 0) == 'Alicia' # Verify change persisted in database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT name FROM users WHERE id = 1") assert res.fetchone()[0] == 'Alicia' conn.close() def test_index_method(self): """Test index method returns correct TTkModelIndex""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Get index for first row, first column index = model.index(0, 0) assert isinstance(index, ttk.TTkModelIndex) assert index.row() == 0 assert index.col() == 0 assert index.data() == 'Alice' def test_model_index_set_data(self): """Test setting data through model index""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Get index and modify data through it index = model.index(1, 0) # Bob's name index.setData('Robert') # Verify change assert model.data(1, 0) == 'Robert' assert index.data() == 'Robert' def test_flags(self): """Test item flags""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') flags = model.flags(0, 0) # Should have enabled, editable, and selectable flags assert flags & ttk.TTkK.ItemFlag.ItemIsEnabled assert flags & ttk.TTkK.ItemFlag.ItemIsEditable assert flags & ttk.TTkK.ItemFlag.ItemIsSelectable def test_sort_ascending(self): """Test sorting in ascending order""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name (column 0) ascending model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) # Check order: Alice, Bob, Charlie, Diana assert model.data(0, 0) == 'Alice' assert model.data(1, 0) == 'Bob' assert model.data(2, 0) == 'Charlie' assert model.data(3, 0) == 'Diana' def test_sort_descending(self): """Test sorting in descending order""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name (column 0) descending model.sort(0, ttk.TTkK.SortOrder.DescendingOrder) # Check order: Diana, Charlie, Bob, Alice assert model.data(0, 0) == 'Diana' assert model.data(1, 0) == 'Charlie' assert model.data(2, 0) == 'Bob' assert model.data(3, 0) == 'Alice' def test_sort_by_age_column(self): """Test sorting by age column""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by age (column 1) ascending model.sort(1, ttk.TTkK.SortOrder.AscendingOrder) # Check order by age: Alice(25), Diana(28), Bob(30), Charlie(35) assert model.data(0, 0) == 'Alice' # Age 25 assert model.data(1, 0) == 'Diana' # Age 28 assert model.data(2, 0) == 'Bob' # Age 30 assert model.data(3, 0) == 'Charlie' # Age 35 def test_sort_reset(self): """Test resetting sort order""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # First sort by name descending model.sort(0, ttk.TTkK.SortOrder.DescendingOrder) assert model.data(0, 0) == 'Diana' # Reset sort model.sort(-1, ttk.TTkK.SortOrder.AscendingOrder) # Should return to original order (by id) assert model.data(0, 0) == 'Alice' assert model.data(1, 0) == 'Bob' assert model.data(2, 0) == 'Charlie' assert model.data(3, 0) == 'Diana' def test_header_data_horizontal(self): """Test horizontal header data""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') assert model.headerData(0, ttk.TTkK.HORIZONTAL) == 'name' assert model.headerData(1, ttk.TTkK.HORIZONTAL) == 'age' assert model.headerData(2, ttk.TTkK.HORIZONTAL) == 'role' def test_header_data_vertical(self): """Test vertical header data (should use parent implementation)""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Should return default from parent class (likely row numbers) header = model.headerData(0, ttk.TTkK.VERTICAL) assert isinstance(header, (ttk.TTkString, str, int, type(None))) def test_concurrent_access(self): """Test thread safety with mutex""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # This test ensures no exceptions are raised with concurrent access # In a real scenario, you'd use threading to test this properly for i in range(10): data = model.data(i % 4, 0) # Access data multiple times assert data in ['Alice', 'Bob', 'Charlie', 'Diana'] def test_setdata_with_sort_column_refresh(self): """Test that changing data in sorted column refreshes id map""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) # Change Alice to Zoe (should move to end when sorted) model.setData(0, 0, 'Zoe') # The model should automatically refresh the sort order # Note: This tests the _refreshIdMap functionality when sortColumn matches def test_database_with_different_primary_key(self): """Test with a table that has a different primary key setup""" # Create another test table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE products ( product_id INTEGER PRIMARY KEY, name TEXT, price REAL ) ''') cur.executemany('INSERT INTO products (name, price) VALUES (?, ?)', [ ('Laptop', 999.99), ('Mouse', 25.50) ]) conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='products') assert model.rowCount() == 2 assert model.columnCount() == 2 # name, price assert model.data(0, 0) == 'Laptop' assert model.data(0, 1) == 999.99 def test_insert_rows_single(self): """Test inserting a single row to the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() assert initial_count == 4 # Insert one row at the end result = model.insertRows(4, 1) assert result == True assert model.rowCount() == 5 # New row should have NULL values assert model.data(4, 0) == '' # name assert model.data(4, 1) == 0 # age assert model.data(4, 2) == '' # role def test_insert_rows_multiple(self): """Test inserting multiple rows to the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Insert 3 rows at the end result = model.insertRows(4, 3) assert result == True assert model.rowCount() == initial_count + 3 def test_insert_rows_at_middle(self): """Test inserting rows at middle position""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Insert row at position 2 result = model.insertRows(2, 1) assert result == True assert model.rowCount() == 5 def test_insert_rows_at_beginning(self): """Test inserting rows at the beginning""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Insert row at position 0 result = model.insertRows(0, 1) assert result == True assert model.rowCount() == 5 def test_insert_rows_invalid_position(self): """Test inserting rows at invalid positions""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try to insert at negative position result = model.insertRows(-1, 1) assert result == False # Try to insert at position beyond row count + 1 result = model.insertRows(10, 1) assert result == False def test_insert_rows_invalid_count(self): """Test inserting invalid number of rows""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try to insert negative count result = model.insertRows(0, -1) assert result == False # Try to insert zero rows result = model.insertRows(0, 0) assert result == False def test_remove_rows_single(self): """Test removing a single row from the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Remove first row (Alice) result = model.removeRows(0, 1) assert result == True assert model.rowCount() == initial_count - 1 def test_remove_rows_multiple(self): """Test removing multiple rows from the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Remove 2 rows starting from position 1 result = model.removeRows(1, 2) assert result == True assert model.rowCount() == initial_count - 2 def test_remove_rows_all(self): """Test removing all rows from the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Remove all rows result = model.removeRows(0, initial_count) assert result == True assert model.rowCount() == 0 def test_remove_rows_invalid_position(self): """Test removing rows from invalid positions""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try to remove from negative position result = model.removeRows(-1, 1) assert result == False # Try to remove from position beyond row count result = model.removeRows(10, 1) assert result == False def test_remove_rows_invalid_count(self): """Test removing invalid number of rows""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try to remove negative count result = model.removeRows(0, -1) assert result == False # Try to remove zero rows result = model.removeRows(0, 0) assert result == False # Try to remove more rows than available result = model.removeRows(2, 10) assert result == False def test_insert_remove_rows_with_sorting(self): """Test inserting/removing rows when table is sorted""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name first model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) initial_count = model.rowCount() # Insert a row result = model.insertRows(2, 1) assert result == True assert model.rowCount() == initial_count + 1 # Remove a row result = model.removeRows(1, 1) assert result == True assert model.rowCount() == initial_count def test_insert_rows_persistence(self): """Test that inserted rows persist in the database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Insert a row and set some data model.insertRows(4, 1) model.setData(4, 0, 'Eve') model.setData(4, 1, 32) model.setData(4, 2, 'Tester') # Create new model instance to test persistence model2 = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') assert model2.rowCount() == 5 # Verify Eve exists in the database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT name FROM users WHERE name = 'Eve'") assert res.fetchone()[0] == 'Eve' conn.close() def test_remove_rows_persistence(self): """Test that removed rows are actually deleted from database""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Remember Alice's data before removal alice_name = model.data(0, 0) assert alice_name == 'Alice' # Remove Alice (first row) model.removeRows(0, 1) # Create new model instance to test persistence model2 = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') assert model2.rowCount() == 3 # Verify Alice is deleted from database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT COUNT(*) FROM users WHERE name = 'Alice'") assert res.fetchone()[0] == 0 conn.close() def test_insert_rows_id_map_refresh(self): """Test that inserting rows refreshes the ID map correctly""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name to make ID mapping more complex model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) # Insert rows model.insertRows(2, 2) # Verify we can still access all data without errors for i in range(model.rowCount()): for j in range(model.columnCount()): data = model.data(i, j) # Should not raise exception def test_remove_rows_id_map_refresh(self): """Test that removing rows refreshes the ID map correctly""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name to make ID mapping more complex model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) # Remove rows model.removeRows(1, 1) # Verify we can still access all remaining data without errors for i in range(model.rowCount()): for j in range(model.columnCount()): data = model.data(i, j) # Should not raise exception def test_insert_rows_boundary_conditions(self): """Test boundary conditions for insertRows""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Insert at exact row count (valid) result = model.insertRows(model.rowCount(), 1) assert result == True # Insert at row count + 1 (invalid) result = model.insertRows(model.rowCount() + 1, 1) assert result == False def test_remove_rows_boundary_conditions(self): """Test boundary conditions for removeRows""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') row_count = model.rowCount() # Remove exactly at boundary (valid) result = model.removeRows(row_count - 1, 1) assert result == True # Try to remove from empty table (invalid) if model.rowCount() == 0: result = model.removeRows(0, 1) assert result == False def test_insert_remove_rows_combined(self): """Test combined insert and remove operations""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Insert 2 rows model.insertRows(2, 2) assert model.rowCount() == initial_count + 2 # Remove 1 row model.removeRows(3, 1) assert model.rowCount() == initial_count + 1 # Insert 1 more row model.insertRows(0, 1) assert model.rowCount() == initial_count + 2 # Remove 3 rows model.removeRows(0, 3) assert model.rowCount() == initial_count - 1 def test_insert_rows_with_data_modification(self): """Test inserting rows and then modifying their data""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Insert a row model.insertRows(4, 1) # Set data for the new row result1 = model.setData(4, 0, 'NewUser') result2 = model.setData(4, 1, 40) result3 = model.setData(4, 2, 'Analyst') assert result1 == True assert result2 == True assert result3 == True # Verify the data was set correctly assert model.data(4, 0) == 'NewUser' assert model.data(4, 1) == 40 assert model.data(4, 2) == 'Analyst' def test_remove_rows_with_sorting_persistence(self): """Test that removeRows works correctly with sorting and persists changes""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by age ascending: Alice(25), Diana(28), Bob(30), Charlie(35) model.sort(1, ttk.TTkK.SortOrder.AscendingOrder) # Verify sorted order assert model.data(0, 0) == 'Alice' # Age 25 assert model.data(1, 0) == 'Diana' # Age 28 # Remove Diana (position 1 in sorted order) result = model.removeRows(1, 1) assert result == True assert model.rowCount() == 3 # Bob should now be at position 1 assert model.data(1, 0) == 'Bob' # Age 30 # Verify Diana is actually deleted from database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT COUNT(*) FROM users WHERE name = 'Diana'") assert res.fetchone()[0] == 0 conn.close() def test_insert_columns_not_supported(self): """Test that insertColumns is not supported and returns False""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_column_count = model.columnCount() # Try to insert columns (should not be supported) result = model.insertColumns(1, 1) assert result == False # Column count should remain unchanged assert model.columnCount() == initial_column_count # Try inserting multiple columns result = model.insertColumns(0, 2) assert result == False assert model.columnCount() == initial_column_count def test_insert_columns_invalid_parameters(self): """Test insertColumns with invalid parameters""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try with negative column position result = model.insertColumns(-1, 1) assert result == False # Try with negative count result = model.insertColumns(0, -1) assert result == False # Try with zero count result = model.insertColumns(0, 0) assert result == False def test_remove_columns_not_supported(self): """Test that removeColumns is not supported and returns False""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_column_count = model.columnCount() # Try to remove columns (should not be supported) result = model.removeColumns(1, 1) assert result == False # Column count should remain unchanged assert model.columnCount() == initial_column_count # Try removing multiple columns result = model.removeColumns(0, 2) assert result == False assert model.columnCount() == initial_column_count def test_remove_columns_invalid_parameters(self): """Test removeColumns with invalid parameters""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Try with negative column position result = model.removeColumns(-1, 1) assert result == False # Try with negative count result = model.removeColumns(0, -1) assert result == False # Try with zero count result = model.removeColumns(0, 0) assert result == False # Try to remove more columns than available result = model.removeColumns(0, 10) assert result == False def test_remove_columns_boundary_conditions(self): """Test removeColumns boundary conditions""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') column_count = model.columnCount() # Try to remove from position equal to column count result = model.removeColumns(column_count, 1) assert result == False # Try to remove from position beyond column count result = model.removeColumns(column_count + 1, 1) assert result == False def test_insert_columns_boundary_conditions(self): """Test insertColumns boundary conditions""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') column_count = model.columnCount() # Try to insert at position equal to column count result = model.insertColumns(column_count, 1) assert result == False # Try to insert at position beyond column count result = model.insertColumns(column_count + 1, 1) assert result == False def test_column_operations_with_sorting(self): """Test that column operations don't affect sorting""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name first model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) # Verify sorting is working assert model.data(0, 0) == 'Alice' # Try column operations (should fail but not affect sorting) model.insertColumns(1, 1) model.removeColumns(1, 1) # Sorting should still be intact assert model.data(0, 0) == 'Alice' assert model.data(1, 0) == 'Bob' def test_column_operations_data_integrity(self): """Test that failed column operations don't affect data integrity""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Store original data original_data = [] for i in range(model.rowCount()): row_data = [] for j in range(model.columnCount()): row_data.append(model.data(i, j)) original_data.append(row_data) # Try various column operations (all should fail) model.insertColumns(0, 1) model.insertColumns(1, 2) model.removeColumns(0, 1) model.removeColumns(1, 1) # Verify data is unchanged for i in range(model.rowCount()): for j in range(model.columnCount()): assert model.data(i, j) == original_data[i][j] def test_empty_table_initialization(self): """Test initialization with an empty table""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_users') assert model.rowCount() == 0 assert model.columnCount() == 2 # name, age (id is primary key) # Test data access on empty table assert model.data(0, 0) is None or model.data(0, 0) is None # Test header data still works assert model.headerData(0, ttk.TTkK.HORIZONTAL) == 'name' assert model.headerData(1, ttk.TTkK.HORIZONTAL) == 'age' def test_empty_table_operations(self): """Test operations on empty table""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_test ( id INTEGER PRIMARY KEY, value TEXT ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_test') # Test insert on empty table result = model.insertRows(0, 1) assert result == True assert model.rowCount() == 1 # Test data setting on newly inserted row result = model.setData(0, 0, 'test_value') assert result == True assert model.data(0, 0) == 'test_value' # Test remove from single-row table result = model.removeRows(0, 1) assert result == True assert model.rowCount() == 0 def test_empty_table_invalid_operations(self): """Test invalid operations on empty table""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_invalid ( id INTEGER PRIMARY KEY, data TEXT ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_invalid') # Test invalid remove operations on empty table result = model.removeRows(0, 1) assert result == False result = model.removeRows(-1, 1) assert result == False result = model.removeRows(1, 1) assert result == False # Test invalid insert operations result = model.insertRows(-1, 1) assert result == False result = model.insertRows(1, 1) # Beyond row count assert result == False def test_empty_table_sorting(self): """Test sorting operations on empty table""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_sort ( id INTEGER PRIMARY KEY, name TEXT, value INTEGER ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_sort') # Test sorting on empty table (should not crash) model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) assert model.rowCount() == 0 model.sort(1, ttk.TTkK.SortOrder.DescendingOrder) assert model.rowCount() == 0 # Reset sort model.sort(-1, ttk.TTkK.SortOrder.AscendingOrder) assert model.rowCount() == 0 def test_remove_all_rows_from_populated_table(self): """Test removing all rows from a populated table""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() assert initial_count == 4 # Remove all rows at once result = model.removeRows(0, initial_count) assert result == True assert model.rowCount() == 0 # Verify table is now empty in database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT COUNT(*) FROM users") assert res.fetchone()[0] == 0 conn.close() # Test operations on now-empty table result = model.removeRows(0, 1) # Should fail assert result == False # Test insert into now-empty table result = model.insertRows(0, 1) assert result == True assert model.rowCount() == 1 def test_remove_all_rows_one_by_one(self): """Test removing all rows one by one""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() # Remove rows one by one from the beginning for i in range(initial_count): remaining_count = model.rowCount() result = model.removeRows(0, 1) assert result == True assert model.rowCount() == remaining_count - 1 # Should now be empty assert model.rowCount() == 0 # Verify in database conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() res = cur.execute("SELECT COUNT(*) FROM users") assert res.fetchone()[0] == 0 conn.close() def test_remove_all_rows_with_sorting(self): """Test removing all rows when table is sorted""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Sort by name first model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) initial_count = model.rowCount() # Remove all rows result = model.removeRows(0, initial_count) assert result == True assert model.rowCount() == 0 # Sorting should still work on empty table model.sort(1, ttk.TTkK.SortOrder.DescendingOrder) assert model.rowCount() == 0 def test_remove_rows_boundary_edge_cases(self): """Test boundary edge cases for removeRows""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') row_count = model.rowCount() # Test removing exactly the remaining count from position 0 result = model.removeRows(0, row_count) assert result == True assert model.rowCount() == 0 # Reset table for next test conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() test_data = [ ('Alice', 25, 'Engineer'), ('Bob', 30, 'Designer'), ('Charlie', 35, 'Manager') ] cur.executemany('INSERT INTO users (name, age, role) VALUES (?, ?, ?)', test_data) conn.commit() conn.close() # Refresh model count model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Test removing from last position with count that exceeds available result = model.removeRows(2, 5) # Only 1 row at position 2, trying to remove 5 assert result == False assert model.rowCount() == 3 # Should be unchanged def test_insert_rows_into_empty_table(self): """Test inserting rows into various empty table configurations""" # Create empty table with different column types conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_mixed ( id INTEGER PRIMARY KEY, name TEXT, age INTEGER, salary REAL, active BOOLEAN ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_mixed') # Insert multiple rows into empty table result = model.insertRows(0, 3) assert result == True assert model.rowCount() == 3 # Check default values based on column types assert model.data(0, 0) == '' # TEXT -> empty string assert model.data(0, 1) == 0 # INTEGER -> 0 assert model.data(0, 2) == 0.0 # REAL -> 0.0 assert model.data(0, 3) == 0 # BOOLEAN -> 0 def test_empty_table_index_operations(self): """Test index operations on empty table""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_index ( id INTEGER PRIMARY KEY, value TEXT ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_index') # Test index creation on empty table (should handle gracefully) try: index = model.index(0, 0) # May raise exception or return invalid index # If it doesn't raise an exception, the implementation handles it gracefully except (sqlite3.Error, IndexError): pass # Expected behavior for empty table def test_table_emptied_then_repopulated(self): """Test table that is emptied and then repopulated""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') # Verify initial state assert model.rowCount() == 4 # Empty the table model.removeRows(0, model.rowCount()) assert model.rowCount() == 0 # Repopulate with new data model.insertRows(0, 2) assert model.rowCount() == 2 # Set data for new rows model.setData(0, 0, 'NewUser1') model.setData(0, 1, 40) model.setData(0, 2, 'Admin') model.setData(1, 0, 'NewUser2') model.setData(1, 1, 45) model.setData(1, 2, 'Manager') # Verify data integrity assert model.data(0, 0) == 'NewUser1' assert model.data(1, 0) == 'NewUser2' # Test sorting on repopulated table model.sort(0, ttk.TTkK.SortOrder.AscendingOrder) assert model.data(0, 0) == 'NewUser1' # Should be first alphabetically def test_concurrent_operations_on_empty_table(self): """Test concurrent operations on empty table (basic thread safety)""" # Create empty table conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() cur.execute(''' CREATE TABLE empty_concurrent ( id INTEGER PRIMARY KEY, value INTEGER ) ''') conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='empty_concurrent') # Simulate concurrent operations (basic test) operations = [ lambda: model.insertRows(0, 1), lambda: model.sort(0, ttk.TTkK.SortOrder.AscendingOrder), lambda: model.rowCount(), lambda: model.columnCount() ] # Execute multiple operations - should not crash for op in operations * 3: # Repeat operations try: result = op() except Exception as e: pytest.fail(f"Concurrent operation failed: {e}") def test_large_batch_remove_operations(self): """Test removing large batches of rows""" # First populate with more data conn = sqlite3.connect(self.temp_db_path) cur = conn.cursor() # Add more test data large_data = [(f'User{i}', 20+i, f'Role{i%3}') for i in range(50)] cur.executemany('INSERT INTO users (name, age, role) VALUES (?, ?, ?)', large_data) conn.commit() conn.close() model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') initial_count = model.rowCount() assert initial_count > 50 # Should have original 4 + 50 new = 54 # Remove large batch from middle result = model.removeRows(10, 30) assert result == True assert model.rowCount() == initial_count - 30 # Remove another large batch from beginning result = model.removeRows(0, 15) assert result == True assert model.rowCount() == initial_count - 45 # Remove remaining rows remaining = model.rowCount() result = model.removeRows(0, remaining) assert result == True assert model.rowCount() == 0 def test_alternating_empty_and_populate_operations(self): """Test alternating between emptying and populating table""" model = ttk.TTkTableModelSQLite3(fileName=self.temp_db_path, table='users') for cycle in range(3): # Repeat cycle 3 times # Start with some data if model.rowCount() == 0: model.insertRows(0, 2) model.setData(0, 0, f'User{cycle}A') model.setData(1, 0, f'User{cycle}B') assert model.rowCount() >= 2 # Empty the table model.removeRows(0, model.rowCount()) assert model.rowCount() == 0 # Insert different amount insert_count = (cycle + 1) * 2 model.insertRows(0, insert_count) assert model.rowCount() == insert_count # Set some data for i in range(insert_count): model.setData(i, 0, f'Cycle{cycle}_User{i}') # Final cleanup model.removeRows(0, model.rowCount()) assert model.rowCount() == 0