""" Edge case tests for Tablet Management System Tests critical scenarios: already loaned devices, non-existent loans, duplicates, etc. """ import pytest import sqlite3 from datetime import datetime import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) class TestLoanEdgeCases: """Critical edge cases for loan operations""" def test_loan_device_already_loaned(self, db_conn): """ CRITICAL: Test that a device already loaned cannot be loaned again This prevents the same physical device from being loaned to multiple users """ cursor = db_conn.cursor() # Add a tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'ALREADY_LOANED')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'ALREADY_LOANED'") tablet = cursor.fetchone() # Add a user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User1', 'USER1')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'USER1'") user1 = cursor.fetchone() # Add another user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User2', 'USER2')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'USER2'") user2 = cursor.fetchone() # Loan to first user 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'], user1['id'], loan_date)) db_conn.commit() # Verify tablet is loaned cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet['id'],)) status = cursor.fetchone()['status'] assert status == 'loaned' # Try to loan to second user - should check status first # In the actual application, this would be prevented by checking status cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet['id'],)) current_status = cursor.fetchone()['status'] # The application logic should prevent this assert current_status == 'loaned' # If we tried to loan it anyway (without checking), we'd get a constraint error # because the tablet status is already 'loaned' # The proper app logic checks status before allowing loan def test_return_nonexistent_loan_id(self, db_conn): """ CRITICAL: Test returning a loan that doesn't exist Should handle gracefully without crashing """ cursor = db_conn.cursor() # Try to return a non-existent loan loan_id = 99999 cursor.execute("SELECT tablet_id FROM loans WHERE id = ? AND status = 'active'", (loan_id,)) loan = cursor.fetchone() # Should return None (no such loan) assert loan is None # The application should handle this by showing an error message # rather than crashing def test_return_already_returned_loan(self, db_conn): """ CRITICAL: Test returning a loan that's already been returned Should handle gracefully """ cursor = db_conn.cursor() # Add tablet and user cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'ALREADY_RETURNED')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'ALREADY_RETURNED'") tablet = cursor.fetchone() cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User', 'RETURN_USER')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'RETURN_USER'") user = cursor.fetchone() # Create and return a loan loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') return_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, return_date, status) VALUES (?, ?, ?, ?, 'returned') ''', (tablet['id'], user['id'], loan_date, return_date)) cursor.execute("UPDATE tablets SET status = 'available' WHERE id = ?", (tablet['id'],)) db_conn.commit() # Get the loan ID cursor.execute("SELECT id FROM loans WHERE tablet_id = ?", (tablet['id'],)) loan = cursor.fetchone() loan_id = loan['id'] # Try to return it again cursor.execute("SELECT tablet_id FROM loans WHERE id = ? AND status = 'active'", (loan_id,)) active_loan = cursor.fetchone() # Should be None because status is 'returned', not 'active' assert active_loan is None def test_loan_with_invalid_tablet_id(self, db_conn): """ CRITICAL: Test loaning with an invalid/non-existent tablet ID Should fail gracefully """ cursor = db_conn.cursor() # Add a user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User', 'INVALID_USER')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'INVALID_USER'") user = cursor.fetchone() # Try to loan with invalid tablet ID invalid_tablet_id = 99999 loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 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 = ?", (invalid_tablet_id,)) 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_loan_with_invalid_user_id(self, db_conn): """ CRITICAL: Test loaning with an invalid/non-existent user ID Should fail gracefully """ cursor = db_conn.cursor() # Add a tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'INVALID_LOAN')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'INVALID_LOAN'") tablet = cursor.fetchone() # Try to loan with invalid user ID invalid_user_id = 99999 loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 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 = ?", (invalid_user_id,)) user_check = cursor.fetchone() assert user_check 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 class TestDuplicatePrevention: """Tests for preventing duplicate entries""" def test_duplicate_tablet_serial_number(self, db_conn): """ CRITICAL: Test that duplicate tablet serial numbers are prevented Serial numbers must be unique for tracking """ cursor = db_conn.cursor() # Add first tablet cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand1', 'Model1', 'DUP_SERIAL')) 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', 'DUP_SERIAL')) db_conn.commit() def test_duplicate_user_identification(self, db_conn): """ CRITICAL: Test that duplicate user identifications are prevented User identifications must be unique """ cursor = db_conn.cursor() # Add first user cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User1', 'DUP_ID')) db_conn.commit() # Try to add duplicate with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User2', 'DUP_ID')) db_conn.commit() def test_duplicate_non_loanable_device_serial(self, db_conn): """ CRITICAL: Test that duplicate non-loanable device serials are prevented """ cursor = db_conn.cursor() # Add first device cursor.execute(''' INSERT INTO non_loanable_devices (brand, model, serial_number, device_type, status) VALUES (?, ?, ?, ?, 'available') ''', ('Brand', 'Model', 'DUP_DEVICE_SERIAL', 'projector')) db_conn.commit() # Try to add duplicate with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO non_loanable_devices (brand, model, serial_number, device_type, status) VALUES (?, ?, ?, ?, 'available') ''', ('Brand2', 'Model2', 'DUP_DEVICE_SERIAL', 'monitor')) db_conn.commit() class TestDataIntegrity: """Tests for data integrity constraints""" def test_foreign_key_tablet_deletion(self, db_conn): """ Test that deleting a tablet with active loans is handled SQLite defaults to allowing this, but we should be aware """ cursor = db_conn.cursor() # Add tablet and user cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'FK_TEST')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'FK_TEST'") tablet = cursor.fetchone() cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User', 'FK_USER')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'FK_USER'") user = cursor.fetchone() # Create active loan 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)) db_conn.commit() # SQLite allows this by default (no ON DELETE RESTRICT) # In production, we might want to add CASCADE or RESTRICT # For now, just verify the loan exists cursor.execute("SELECT COUNT(*) FROM loans WHERE tablet_id = ?", (tablet['id'],)) loan_count = cursor.fetchone()[0] assert loan_count == 1 def test_null_serial_number_prevention(self, db_conn): """ Test that NULL serial numbers are prevented Serial numbers are required (NOT NULL constraint) """ cursor = db_conn.cursor() # Try to add tablet with NULL serial number with pytest.raises(sqlite3.IntegrityError): cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', None)) db_conn.commit() def test_null_identification_prevention(self, db_conn): """ Test that NULL user identifications are prevented Identifications are required (NOT NULL constraint) """ cursor = db_conn.cursor() # Try to add user with NULL identification # Note: identification is NOT marked as NOT NULL in the schema # This test verifies the current behavior cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User', None)) db_conn.commit() # This should work because identification is not NOT NULL # But in practice, we should have this constraint cursor.execute("SELECT COUNT(*) FROM users WHERE identification IS NULL") count = cursor.fetchone()[0] # This will be 1, showing that NULL is currently allowed # In production, we should add NOT NULL constraint def test_empty_string_serial_number(self, db_conn): """ Test handling of empty string serial numbers Empty strings are different from NULL """ cursor = db_conn.cursor() # Add tablet with empty string serial number cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', '')) db_conn.commit() # This should work (empty string is allowed unless we add CHECK constraint) cursor.execute("SELECT COUNT(*) FROM tablets WHERE serial_number = ''") count = cursor.fetchone()[0] assert count == 1 # In production, we might want to prevent empty strings # with a CHECK constraint: CHECK(serial_number <> '') class TestConcurrentScenarioSimulations: """Simulate scenarios that could cause issues in concurrent environments""" def test_loan_return_loan_sequence(self, db_conn): """ Test the sequence: loan -> return -> loan again This simulates a device being loaned multiple times over its lifetime """ cursor = db_conn.cursor() # Add tablet and user cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', ('Brand', 'Model', 'SEQUENCE_TEST')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = 'SEQUENCE_TEST'") tablet = cursor.fetchone() cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', ('User', 'SEQUENCE_USER')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = 'SEQUENCE_USER'") user = cursor.fetchone() # First loan loan_date1 = 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_date1)) db_conn.commit() # Return return_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') cursor.execute("SELECT id FROM loans WHERE tablet_id = ?", (tablet['id'],)) loan1 = cursor.fetchone() cursor.execute(''' UPDATE loans SET status = 'returned', return_date = ? WHERE id = ? ''', (return_date, loan1['id'])) cursor.execute("UPDATE tablets SET status = 'available' WHERE id = ?", (tablet['id'],)) db_conn.commit() # Second loan loan_date2 = 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_date2)) db_conn.commit() # Verify we have 2 loans for this tablet cursor.execute("SELECT COUNT(*) FROM loans WHERE tablet_id = ?", (tablet['id'],)) loan_count = cursor.fetchone()[0] assert loan_count == 2 # Verify 1 active, 1 returned cursor.execute("SELECT COUNT(*) FROM loans WHERE tablet_id = ? AND status = 'active'", (tablet['id'],)) active_count = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM loans WHERE tablet_id = ? AND status = 'returned'", (tablet['id'],)) returned_count = cursor.fetchone()[0] assert active_count == 1 assert returned_count == 1 def test_multiple_users_multiple_tablets(self, db_conn): """ Test complex scenario with multiple users and tablets Ensures the many-to-many relationship works correctly """ cursor = db_conn.cursor() # Add 3 users users = [] for i in range(3): cursor.execute(''' INSERT INTO users (name, identification) VALUES (?, ?) ''', (f'User{i}', f'MULTI_USER_{i}')) db_conn.commit() cursor.execute("SELECT id FROM users WHERE identification = ?", (f'MULTI_USER_{i}',)) users.append(cursor.fetchone()) # Add 5 tablets tablets = [] for i in range(5): cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', (f'Brand{i}', f'Model{i}', f'MULTI_TABLET_{i}')) db_conn.commit() cursor.execute("SELECT id FROM tablets WHERE serial_number = ?", (f'MULTI_TABLET_{i}',)) tablets.append(cursor.fetchone()) # Loan tablets to users in a pattern # User 0: tablets 0, 1 # User 1: tablets 2, 3 # User 2: tablet 4 loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') loans_map = { users[0]['id']: [tablets[0]['id'], tablets[1]['id']], users[1]['id']: [tablets[2]['id'], tablets[3]['id']], users[2]['id']: [tablets[4]['id']], } for user_id, tablet_ids in loans_map.items(): for tablet_id in tablet_ids: 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 counts cursor.execute("SELECT COUNT(*) FROM loans") total_loans = cursor.fetchone()[0] assert total_loans == 5 # 2 + 2 + 1 # Verify each user has correct number of loans for user_id, expected_tablet_ids in loans_map.items(): cursor.execute("SELECT COUNT(*) FROM loans WHERE user_id = ?", (user_id,)) count = cursor.fetchone()[0] assert count == len(expected_tablet_ids) # Verify all loaned tablets have correct status cursor.execute("SELECT COUNT(*) FROM tablets WHERE status = 'loaned'") loaned_count = cursor.fetchone()[0] assert loaned_count == 5