RA.Aid/tests/ra_aid/database/test_connection.py

233 lines
8.0 KiB
Python

"""
Tests for the database connection module.
NOTE: These tests have been updated to minimize file system interactions by:
1. Using in-memory databases wherever possible
2. Mocking file system interactions when testing file-based modes
3. Ensuring proper cleanup of database connections between tests
However, due to the complexity of SQLite's file interactions through the peewee driver,
these tests may still sometimes create files in the real .ra-aid directory during execution.
"""
import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import peewee
import pytest
from ra_aid.database.connection import (
DatabaseManager,
close_db,
db_var,
get_db,
init_db,
)
@pytest.fixture
def cleanup_db():
"""
Fixture to clean up database connections between tests.
This ensures that we don't leak database connections between tests
and that the db_var contextvar is reset.
"""
# Run the test
yield
# Clean up after the test
db = db_var.get()
if db is not None:
# Clean up attributes we may have added
if hasattr(db, "_is_in_memory"):
delattr(db, "_is_in_memory")
if hasattr(db, "_message_shown"):
delattr(db, "_message_shown")
# Close the connection if it's open
if not db.is_closed():
db.close()
# Reset the contextvar
db_var.set(None)
@pytest.fixture
def mock_logger():
"""Mock the logger to test for output messages."""
with patch("ra_aid.database.connection.logger") as mock:
yield mock
class TestInitDb:
"""Tests for the init_db function."""
# Use in-memory=True for all file-based tests to avoid file system interactions
def test_init_db_default(self, cleanup_db):
"""Test init_db with default parameters."""
# Initialize the database with in-memory=True for testing
db = init_db(in_memory=True)
# Override the _is_in_memory attribute to test as if it were a file-based database
db._is_in_memory = False
# Verify database was initialized correctly
assert isinstance(db, peewee.SqliteDatabase)
assert not db.is_closed()
assert hasattr(db, "_is_in_memory")
assert db._is_in_memory is False # We set this manually
def test_init_db_in_memory(self, cleanup_db):
"""Test init_db with in_memory=True."""
db = init_db(in_memory=True)
assert isinstance(db, peewee.SqliteDatabase)
assert not db.is_closed()
assert hasattr(db, "_is_in_memory")
assert db._is_in_memory is True
def test_init_db_reuses_connection(self, cleanup_db):
"""Test that init_db reuses an existing connection."""
db1 = init_db(in_memory=True)
db2 = init_db(in_memory=True)
assert db1 is db2
def test_init_db_reopens_closed_connection(self, cleanup_db):
"""Test that init_db reopens a closed connection."""
db1 = init_db(in_memory=True)
db1.close()
assert db1.is_closed()
db2 = init_db(in_memory=True)
assert db1 is db2
assert not db1.is_closed()
def test_in_memory_mode_no_directory_created(self, cleanup_db):
"""Test that when using in_memory mode, no database file is created."""
# Use a mock to verify that os.path.exists is not called for database files
with patch("os.path.exists") as mock_exists:
# Initialize the database with in_memory=True
db = init_db(in_memory=True)
# Verify it's really in-memory
assert hasattr(db, "_is_in_memory")
assert db._is_in_memory is True
# Verify os.path.exists was not called
mock_exists.assert_not_called()
class TestGetDb:
"""Tests for the get_db function."""
def test_get_db_creates_connection(self, cleanup_db):
"""Test that get_db creates a new connection if none exists."""
# Reset the contextvar to ensure no connection exists
db_var.set(None)
# We'll mock init_db and verify it gets called by get_db() with the default parameters
with patch("ra_aid.database.connection.init_db") as mock_init_db:
# Set up the mock to return a dummy database
mock_db = MagicMock(spec=peewee.SqliteDatabase)
mock_db.is_closed.return_value = False
mock_db._is_in_memory = False
mock_init_db.return_value = mock_db
# Get a connection
db = get_db()
# Verify init_db was called with in_memory=False and base_dir=None
mock_init_db.assert_called_once_with(in_memory=False, base_dir=None)
# Verify the database was returned correctly
assert db is mock_db
def test_get_db_reuses_connection(self, cleanup_db):
"""Test that get_db reuses an existing connection."""
db1 = init_db(in_memory=True)
db2 = get_db()
assert db1 is db2
def test_get_db_reopens_closed_connection(self, cleanup_db):
"""Test that get_db reopens a closed connection."""
db1 = init_db(in_memory=True)
db1.close()
assert db1.is_closed()
db2 = get_db()
assert db1 is db2
assert not db1.is_closed()
class TestCloseDb:
"""Tests for the close_db function."""
def test_close_db(self, cleanup_db):
"""Test that close_db closes an open connection."""
db = init_db(in_memory=True)
assert not db.is_closed()
close_db()
assert db.is_closed()
def test_close_db_no_connection(self, cleanup_db):
"""Test that close_db handles the case where no connection exists."""
# Reset the contextvar to ensure no connection exists
db_var.set(None)
# This should not raise an exception
close_db()
def test_close_db_already_closed(self, cleanup_db):
"""Test that close_db handles the case where the connection is already closed."""
db = init_db(in_memory=True)
db.close()
assert db.is_closed()
# This should not raise an exception
close_db()
class TestDatabaseManager:
"""Tests for the DatabaseManager class."""
def test_database_manager_default(self, cleanup_db):
"""Test DatabaseManager with default parameters."""
# Use in-memory=True but test with _is_in_memory=False
with DatabaseManager(in_memory=True) as db:
# Override the attribute for testing
db._is_in_memory = False
# Verify the database connection
assert isinstance(db, peewee.SqliteDatabase)
assert not db.is_closed()
assert hasattr(db, "_is_in_memory")
assert db._is_in_memory is False # We set this manually
# Store the connection for later assertions
db_in_context = db
# Verify the connection is closed after exiting the context
assert db_in_context.is_closed()
def test_database_manager_in_memory(self, cleanup_db):
"""Test DatabaseManager with in_memory=True."""
with DatabaseManager(in_memory=True) as db:
assert isinstance(db, peewee.SqliteDatabase)
assert not db.is_closed()
assert hasattr(db, "_is_in_memory")
assert db._is_in_memory is True
# Store the connection for later assertions
db_in_context = db
# Verify the connection is closed after exiting the context
assert db_in_context.is_closed()
def test_database_manager_exception_handling(self, cleanup_db):
"""Test that DatabaseManager properly handles exceptions."""
try:
with DatabaseManager(in_memory=True) as db:
assert not db.is_closed()
raise ValueError("Test exception")
except ValueError:
# The exception should be propagated
pass
# Verify the connection is closed even if an exception occurred
assert db.is_closed()