""" Unit tests for core tablet management functions Tests loan logic, validation, and database operations """ import pytest import sqlite3 from datetime import datetime import sys import os # Import the functions from minimal_app (they work with any db connection) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def get_tablet_count(conn): """Helper to get tablet count""" cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM tablets") return cursor.fetchone()[0] def get_user_count(conn): """Helper to get user count""" cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM users") return cursor.fetchone()[0] def get_loan_count(conn): """Helper to get loan count""" cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM loans") return cursor.fetchone()[0] class TestTabletOperations: """Tests for tablet CRUD operations""" def test_add_tablet_success(self, db_conn): """Test adding a new tablet successfully""" cursor = db_conn.cursor() # Add a tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('TestBrand', 'TestModel', 'TEST001')) db_conn.commit() # Verify it was added cursor.execute("SELECT * FROM tablets WHERE serial_number = 'TEST001'") tablet = cursor.fetchone() assert tablet is not None assert tablet['brand'] == 'TestBrand' assert tablet['model'] == 'TestModel' assert tablet['serial_number'] == 'TEST001' assert tablet['status'] == 'available' def test_add_tablet_duplicate_serial(self, db_conn): """Test that duplicate serial numbers are rejected""" cursor = db_conn.cursor() # Add first tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand1', 'Model1', 'DUP001')) db_conn.commit() # Try to add duplicate with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand2', 'Model2', 'DUP001')) db_conn.commit() def test_tablet_status_update(self, db_conn): """Test updating tablet status""" cursor = db_conn.cursor() # Add a tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'STATUS001')) db_conn.commit() # Update status cursor.execute("UPDATE tablets SET status = 'loaned' WHERE serial_number = 'STATUS001'") db_conn.commit() # Verify update cursor.execute("SELECT status FROM tablets WHERE serial_number = 'STATUS001'") status = cursor.fetchone()['status'] assert status == 'loaned' class TestUserOperations: """Tests for user CRUD operations""" def test_add_user_success(self, db_conn): """Test adding a new user successfully""" cursor = db_conn.cursor() cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('Test User', 'TESTID001')) db_conn.commit() cursor.execute("SELECT * FROM users WHERE identification = 'TESTID001'") user = cursor.fetchone() assert user is not None assert user['name'] == 'Test User' assert user['identification'] == 'TESTID001' def test_add_user_duplicate_identification(self, db_conn): """Test that duplicate user identifications are rejected""" cursor = db_conn.cursor() # Add first user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User1', 'DUPID001')) db_conn.commit() # Try to add duplicate with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User2', 'DUPID001')) db_conn.commit() def test_user_with_contact_info(self, db_conn): """Test adding user with email and phone""" cursor = db_conn.cursor() cursor.execute(''' INSERT INTO users (name, email, phone, identification) VALUES (?, ?, ?, ?) ''', ('Contact User', 'test@email.com', '1234567890', 'CONTACT001')) db_conn.commit() cursor.execute("SELECT * FROM users WHERE identification = 'CONTACT001'") user = cursor.fetchone() assert user['email'] == 'test@email.com' assert user['phone'] == '1234567890' class TestLoanOperations: """Tests for loan operations - the core business logic""" def test_loan_tablet_success(self, populated_db): """Test loaning an available tablet to a user""" cursor = populated_db.cursor() # Get an available tablet and user cursor.execute("SELECT id FROM tablets WHERE status = 'available' LIMIT 1") tablet = cursor.fetchone() cursor.execute("SELECT id FROM users LIMIT 1") user = cursor.fetchone() assert tablet is not None assert user is not None tablet_id = tablet['id'] user_id = user['id'] # Loan the tablet loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') cursor.execute("UPDATE tablets SET status = 'loaned' WHERE id = ?", (tablet_id,)) cursor.execute(''' INSERT INTO loans (tablet_id, user_id, loan_date, status) VALUES (?, ?, ?, 'active') ''', (tablet_id, user_id, loan_date)) populated_db.commit() # Verify tablet status changed cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet_id,)) status = cursor.fetchone()['status'] assert status == 'loaned' # Verify loan was created cursor.execute("SELECT * FROM loans WHERE tablet_id = ? AND user_id = ?", (tablet_id, user_id)) loan = cursor.fetchone() assert loan is not None assert loan['status'] == 'active' assert loan['return_date'] is None def test_loan_already_loaned_tablet(self, populated_db): """Test that loaning an already loaned tablet fails gracefully""" cursor = populated_db.cursor() # Get a loaned tablet (SN004 should be loaned from sample data) cursor.execute("SELECT id FROM tablets WHERE status = 'loaned' LIMIT 1") tablet = cursor.fetchone() cursor.execute("SELECT id FROM users LIMIT 1") user = cursor.fetchone() if tablet and user: tablet_id = tablet['id'] user_id = user['id'] # Try to loan it again (should check status first) cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet_id,)) status = cursor.fetchone()['status'] # This should be 'loaned', so we shouldn't be able to loan it assert status == 'loaned' # The application logic should prevent this # In the actual app, this would be checked before inserting # Here we verify the status is still loaned cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet_id,)) final_status = cursor.fetchone()['status'] assert final_status == 'loaned' def test_return_tablet_success(self, populated_db): """Test returning a loaned tablet""" cursor = populated_db.cursor() # Get an active loan cursor.execute("SELECT * FROM loans WHERE status = 'active' LIMIT 1") loan = cursor.fetchone() if loan: loan_id = loan['id'] tablet_id = loan['tablet_id'] # Return the tablet return_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') cursor.execute(''' UPDATE loans SET status = 'returned', return_date = ? WHERE id = ? ''', (return_date, loan_id)) cursor.execute("UPDATE tablets SET status = 'available' WHERE id = ?", (tablet_id,)) populated_db.commit() # Verify loan status changed cursor.execute("SELECT status, return_date FROM loans WHERE id = ?", (loan_id,)) updated_loan = cursor.fetchone() assert updated_loan['status'] == 'returned' assert updated_loan['return_date'] is not None # Verify tablet status changed cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet_id,)) tablet_status = cursor.fetchone()['status'] assert tablet_status == 'available' def test_return_nonexistent_loan(self, db_conn): """Test returning a loan that doesn't exist""" cursor = db_conn.cursor() # Try to return a non-existent loan cursor.execute("SELECT tablet_id FROM loans WHERE id = ? AND status = 'active'", (9999,)) loan = cursor.fetchone() # Should be None since loan doesn't exist assert loan is None class TestEdgeCases: """Tests for edge cases and error conditions""" def test_loan_to_nonexistent_user(self, db_conn): """Test loaning to a user that doesn't exist""" cursor = db_conn.cursor() # Add a tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'EDGE001')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'EDGE001'") tablet = cursor.fetchone() # Try to loan to non-existent user (ID 9999) # Note: SQLite doesn't enforce foreign keys by default unless we enable it # The application should validate this at the application level # For now, we verify that the user doesn't exist cursor.execute("SELECT id FROM users WHERE id = 9999") user = cursor.fetchone() assert user is None # User doesn't exist # In a real app with FK enforcement, this would raise IntegrityError # For SQLite without FK enforcement, we just verify the user check def test_loan_nonexistent_tablet(self, db_conn): """Test loaning a tablet that doesn't exist""" cursor = db_conn.cursor() # Add a user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('Test User', 'EDGEID001')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'EDGEID001'") user = cursor.fetchone() # Try to loan non-existent tablet (ID 9999) # Note: SQLite doesn't enforce foreign keys by default unless we enable it # The application should validate this at the application level # For now, we verify that the tablet doesn't exist cursor.execute("SELECT id FROM tablets WHERE id = 9999") tablet_check = cursor.fetchone() assert tablet_check is None # Tablet doesn't exist # In a real app with FK enforcement, this would raise IntegrityError # For SQLite without FK enforcement, we just verify the tablet check def test_empty_database_operations(self, db_conn): """Test operations on empty database""" cursor = db_conn.cursor() # Query empty tables cursor.execute("SELECT COUNT(*) FROM tablets") tablet_count = cursor.fetchone()[0] assert tablet_count == 0 cursor.execute("SELECT COUNT(*) FROM users") user_count = cursor.fetchone()[0] assert user_count == 0 cursor.execute("SELECT COUNT(*) FROM loans") loan_count = cursor.fetchone()[0] assert loan_count == 0 def test_multiple_loans_same_user(self, db_conn): """Test that one user can have multiple loans (one-to-many relationship)""" cursor = db_conn.cursor() # Add user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('MultiLoan User', 'MULTI001')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'MULTI001'") user = cursor.fetchone() # Add multiple tablets for i in range(3): cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', (f'Brand{i}', f'Model{i}', f'MULTI{i:03d}')) db_conn.commit() # Loan all tablets to the same user cursor.execute("SELECT id FROM tablets WHERE serial_number LIKE 'MULTI%'") tablets = cursor.fetchall() loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') for tablet in tablets: cursor.execute("UPDATE tablets SET status = 'loaned' WHERE id = ?", (tablet['id'],)) cursor.execute(''' INSERT INTO loans (tablet_id, user_id, loan_date, status) VALUES (?, ?, ?, 'active') ''', (tablet['id'], user['id'], loan_date)) db_conn.commit() # Verify user has multiple loans cursor.execute("SELECT COUNT(*) FROM loans WHERE user_id = ?", (user['id'],)) loan_count = cursor.fetchone()[0] assert loan_count == 3 def test_serial_number_uniqueness_across_tables(self, db_conn): """Test that serial numbers are unique within their respective tables""" cursor = db_conn.cursor() # Add tablet with serial number cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'UNIQUE001')) db_conn.commit() # Add non-loanable device with same serial number (should be allowed - different tables) cursor.execute(''' INSERT INTO non_loanable_devices (brand, model, serial_number, device_type, status) VALUES (?, ?, ?, ?, 'available') ''', ('Brand', 'Model', 'UNIQUE001', 'projector')) db_conn.commit() # Both should exist (different tables) cursor.execute("SELECT COUNT(*) FROM tablets WHERE serial_number = 'UNIQUE001'") tablet_count = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM non_loanable_devices WHERE serial_number = 'UNIQUE001'") device_count = cursor.fetchone()[0] assert tablet_count == 1 assert device_count == 1 # But duplicate within same table should fail with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand2', 'Model2', 'UNIQUE001')) db_conn.commit() class TestQueryOperations: """Tests for query and filtering operations""" def test_query_available_tablets(self, populated_db): """Test querying available tablets""" cursor = populated_db.cursor() cursor.execute("SELECT * FROM tablets WHERE status = 'available'") available = cursor.fetchall() # Should have at least the sample available tablets assert len(available) >= 3 # SN001, SN002, SN003 from sample def test_query_loaned_tablets(self, populated_db): """Test querying loaned tablets""" cursor = populated_db.cursor() cursor.execute("SELECT * FROM tablets WHERE status = 'loaned'") loaned = cursor.fetchall() # Should have at least SN004 from sample data assert len(loaned) >= 1 def test_query_active_loans(self, populated_db): """Test querying active loans""" cursor = populated_db.cursor() cursor.execute("SELECT * FROM loans WHERE status = 'active'") active = cursor.fetchall() # Should have at least 1 active loan from sample assert len(active) >= 1 def test_query_returned_loans(self, populated_db): """Test querying returned loans""" cursor = populated_db.cursor() cursor.execute("SELECT * FROM loans WHERE status = 'returned'") returned = cursor.fetchall() # Should have at least 1 returned loan from sample assert len(returned) >= 1 def test_query_loans_by_user(self, populated_db): """Test querying loans by user""" cursor = populated_db.cursor() cursor.execute("SELECT id FROM users LIMIT 1") user = cursor.fetchone() if user: cursor.execute("SELECT * FROM loans WHERE user_id = ?", (user['id'],)) loans = cursor.fetchall() # User should have at least 0 loans assert isinstance(loans, list) def test_query_tablets_by_brand(self, populated_db): """Test querying tablets by brand""" cursor = populated_db.cursor() cursor.execute("SELECT * FROM tablets WHERE brand = 'Samsung'") samsung = cursor.fetchall() # Should find Samsung tablet from sample data assert len(samsung) >= 1 assert samsung[0]['brand'] == 'Samsung'