You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1157 lines
40 KiB
1157 lines
40 KiB
#!/usr/bin/env python3 |
|
# MIT License |
|
# |
|
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# 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 |