mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
997 lines
38 KiB
Python
997 lines
38 KiB
Python
"""Tests for database modules.
|
|
|
|
This module contains comprehensive unit tests for the database layer,
|
|
including DatabaseConnection, FileRepository, and related functionality.
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import os
|
|
from typing import List, Dict, Any
|
|
from unittest.mock import Mock
|
|
import sqlite3
|
|
|
|
from nodupe.tools.databases.connection import DatabaseConnection
|
|
from nodupe.tools.databases.files import FileRepository, get_file_repository
|
|
from nodupe.tools.databases.repository_interface import DatabaseRepository
|
|
from nodupe.tools.databases.schema import DatabaseSchema
|
|
from nodupe.tools.databases.query import (DatabaseQuery, DatabaseBatch, DatabasePerformance,
|
|
DatabaseIntegrity, DatabaseBackup, DatabaseMigration,
|
|
DatabaseRecovery, DatabaseOptimization)
|
|
from nodupe.tools.databases.database import Database
|
|
|
|
|
|
def _init_full_schema(db: DatabaseConnection) -> None:
|
|
"""Helper to initialize the full 14-column schema for FileRepository tests."""
|
|
schema = DatabaseSchema(db.get_connection())
|
|
schema.create_schema()
|
|
|
|
|
|
class TestDatabaseConnection:
|
|
"""Test DatabaseConnection class functionality."""
|
|
|
|
def test_singleton_instance_creation(self):
|
|
"""Test that DatabaseConnection follows singleton pattern."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp1, \
|
|
tempfile.NamedTemporaryFile(suffix='.db') as tmp2:
|
|
|
|
db1 = DatabaseConnection.get_instance(tmp1.name)
|
|
db2 = DatabaseConnection.get_instance(tmp1.name) # Same path
|
|
db3 = DatabaseConnection.get_instance(tmp2.name) # Different path
|
|
|
|
assert db1 is db2 # Same path should return same instance
|
|
assert db1 is not db3 # Different paths should return different instances
|
|
assert isinstance(db1, DatabaseConnection)
|
|
|
|
# Clean up connections
|
|
db1.close()
|
|
db3.close()
|
|
|
|
def test_get_connection_creates_directory(self):
|
|
"""Test that get_connection creates database directory if it doesn't exist."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
db_path = os.path.join(temp_dir, "subdir", "test.db")
|
|
db = DatabaseConnection(db_path)
|
|
|
|
connection = db.get_connection()
|
|
assert isinstance(connection, sqlite3.Connection)
|
|
|
|
# Check that directory was created
|
|
assert os.path.exists(os.path.dirname(db_path))
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_execute_query_success(self):
|
|
"""Test successful query execution."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
|
|
# Initialize schema
|
|
_init_full_schema(db)
|
|
|
|
# Execute a simple query
|
|
cursor = db.execute(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("test.txt", 100, 12345, 12345, 12345, 12345)
|
|
)
|
|
assert cursor is not None
|
|
|
|
cursor = db.execute("SELECT * FROM files WHERE path = ?", ("test.txt",))
|
|
result = cursor.fetchone()
|
|
assert result is not None
|
|
assert result[1] == "test.txt" # path
|
|
assert result[2] == 100 # size
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_execute_query_without_params(self):
|
|
"""Test query execution without parameters."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
result = cursor.fetchone()
|
|
assert result[0] == 0
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_executemany_batch_operations(self):
|
|
"""Test batch operations with executemany."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
data = [
|
|
("file1.txt", 100, 12345, 12345, 12345, 12345),
|
|
("file2.txt", 200, 12346, 12345, 12345, 12345),
|
|
("file3.txt", 300, 12347, 12345, 12345, 12345)
|
|
]
|
|
|
|
cursor = db.executemany(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
[tuple(item) for item in data]
|
|
)
|
|
assert cursor is not None
|
|
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
count = cursor.fetchone()[0]
|
|
assert count == 3
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_commit_and_rollback(self):
|
|
"""Test transaction commit and rollback functionality."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
db.execute(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("test.txt", 100, 12345, 12345, 12345, 12345)
|
|
)
|
|
|
|
# Verify it's there
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
assert cursor.fetchone()[0] == 1
|
|
|
|
# Rollback should remove it
|
|
db.rollback()
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
assert cursor.fetchone()[0] == 0
|
|
|
|
# Insert again and commit
|
|
db.execute(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("test.txt", 100, 12345, 12345, 12345, 12345)
|
|
)
|
|
db.commit()
|
|
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
assert cursor.fetchone()[0] == 1
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_close_connection(self):
|
|
"""Test connection closing functionality."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
|
|
# Close should clear the connection
|
|
db.close()
|
|
|
|
# Getting connection after close should work
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
|
|
# Close should have cleaned up properly
|
|
db.close()
|
|
|
|
def test_initialize_database_schema(self):
|
|
"""Test database schema initialization."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_init_full_schema(db)
|
|
|
|
# Test that tables exist
|
|
cursor = db.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
tables = [row[0] for row in cursor.fetchall()]
|
|
|
|
assert "files" in tables
|
|
assert "embeddings" in tables
|
|
|
|
# Test that indexes exist
|
|
cursor = db.execute("SELECT name FROM sqlite_master WHERE type='index'")
|
|
indexes = [row[0] for row in cursor.fetchall()]
|
|
|
|
assert "idx_files_path" in indexes
|
|
assert "idx_files_hash" in indexes
|
|
assert "idx_files_size" in indexes
|
|
assert "idx_files_is_duplicate" in indexes
|
|
assert "idx_embeddings_file_id" in indexes
|
|
assert "idx_embeddings_model_version" in indexes
|
|
|
|
def test_get_connection_thread_local(self):
|
|
"""Test that connections are thread-local."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
|
|
def get_conn():
|
|
"""Helper to get database connection."""
|
|
return db.get_connection()
|
|
|
|
# Same thread should get same connection
|
|
conn1 = get_conn()
|
|
conn2 = get_conn()
|
|
assert conn1 is conn2
|
|
|
|
|
|
class TestFileRepository:
|
|
"""Test FileRepository class functionality."""
|
|
|
|
def test_initialization(self):
|
|
"""Test FileRepository initialization."""
|
|
mock_db = Mock()
|
|
repo = FileRepository(mock_db)
|
|
|
|
assert repo.db is mock_db
|
|
|
|
def test_add_file_success(self):
|
|
"""Test adding a file successfully."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None
|
|
|
|
# Verify file was added
|
|
cursor = db.execute("SELECT * FROM files WHERE id = ?", (file_id,))
|
|
result = cursor.fetchone()
|
|
assert result[1] == "test.txt" # path
|
|
assert result[2] == 100 # size
|
|
assert result[3] == 12345 # modified_time
|
|
assert result[8] == "abc123" # hash (full schema index)
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_get_file_by_id(self):
|
|
"""Test getting a file by ID."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add a file first
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None # Ensure file was added successfully
|
|
|
|
# Get the file
|
|
file_data = repo.get_file(file_id)
|
|
assert file_data is not None
|
|
assert file_data['path'] == "test.txt"
|
|
assert file_data['size'] == 100
|
|
assert file_data['hash'] == "abc123"
|
|
assert file_data['is_duplicate'] is False
|
|
|
|
# Clean up connection
|
|
db.close()
|
|
|
|
def test_get_file_by_id_not_found(self):
|
|
"""Test getting a non-existent file."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
db.get_connection()
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
file_data = repo.get_file(999)
|
|
assert file_data is None
|
|
|
|
# Close database connection to prevent resource warnings
|
|
db.close()
|
|
|
|
def test_get_file_by_path(self):
|
|
"""Test getting a file by path."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add a file first
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None # Ensure file was added successfully
|
|
|
|
# Get the file by path
|
|
file_data = repo.get_file_by_path("test.txt")
|
|
assert file_data is not None
|
|
assert file_data['id'] == file_id
|
|
assert file_data['size'] == 100
|
|
|
|
def test_update_file(self):
|
|
"""Test updating file data."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add a file first
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None # Ensure file was added successfully
|
|
|
|
# Update the file
|
|
success = repo.update_file(file_id, size=200, hash="def456")
|
|
assert success is True
|
|
|
|
# Verify update
|
|
file_data = repo.get_file(file_id)
|
|
assert file_data is not None
|
|
assert file_data['size'] == 200
|
|
assert file_data['hash'] == "def456"
|
|
|
|
def test_update_file_invalid_fields(self):
|
|
"""Test updating with invalid fields."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None # Ensure file was added successfully
|
|
|
|
# Update with no valid fields should return False
|
|
success = repo.update_file(file_id, invalid_field="test")
|
|
assert success is False
|
|
|
|
def test_mark_as_duplicate(self):
|
|
"""Test marking a file as duplicate."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add two files
|
|
original_id = repo.add_file("original.txt", 100, 12345, "abc123")
|
|
duplicate_id = repo.add_file("duplicate.txt", 10, 12346, "abc123")
|
|
assert original_id is not None # Ensure files were added successfully
|
|
assert duplicate_id is not None
|
|
|
|
# Mark one as duplicate of the other
|
|
success = repo.mark_as_duplicate(duplicate_id, original_id)
|
|
assert success is True
|
|
|
|
# Verify the duplicate was marked
|
|
dup_data = repo.get_file(duplicate_id)
|
|
assert dup_data is not None
|
|
assert dup_data['is_duplicate'] is True
|
|
assert dup_data['duplicate_of'] == original_id
|
|
|
|
def test_find_duplicates_by_hash(self):
|
|
"""Test finding files with same hash."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add files with same hash
|
|
file1_id = repo.add_file("file1.txt", 100, 12345, "samehash")
|
|
file2_id = repo.add_file("file2.txt", 100, 12346, "samehash")
|
|
file3_id = repo.add_file("file3.txt", 200, 12347, "differenthash")
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
assert file3_id is not None
|
|
|
|
duplicates = repo.find_duplicates_by_hash("samehash")
|
|
assert len(duplicates) == 2
|
|
assert all(f['hash'] == "samehash" for f in duplicates)
|
|
|
|
# Close database connection to prevent resource warnings
|
|
db.close()
|
|
|
|
def test_find_duplicates_by_size(self):
|
|
"""Test finding files with same size."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add files with same size
|
|
file1_id = repo.add_file("file1.txt", 100, 12345, "hash1")
|
|
file2_id = repo.add_file("file2.txt", 100, 12346, "hash2")
|
|
file3_id = repo.add_file("file3.txt", 200, 12347, "hash3")
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
assert file3_id is not None
|
|
|
|
same_size_files = repo.find_duplicates_by_size(100)
|
|
assert len(same_size_files) == 2
|
|
assert all(f['size'] == 100 for f in same_size_files)
|
|
|
|
def test_get_all_files(self):
|
|
"""Test getting all files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add some files
|
|
file1_id = repo.add_file("file1.txt", 100, 12345, "hash1")
|
|
file2_id = repo.add_file("file2.txt", 200, 12346, "hash2")
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
|
|
all_files = repo.get_all_files()
|
|
assert len(all_files) == 2
|
|
assert {f['path'] for f in all_files} == {"file1.txt", "file2.txt"}
|
|
|
|
def test_delete_file(self):
|
|
"""Test deleting a file."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add a file
|
|
file_id = repo.add_file("test.txt", 100, 12345, "abc123")
|
|
assert file_id is not None # Ensure file was added successfully
|
|
|
|
# Delete the file
|
|
success = repo.delete_file(file_id)
|
|
assert success is True
|
|
|
|
# Verify it's gone
|
|
file_data = repo.get_file(file_id)
|
|
assert file_data is None
|
|
|
|
def test_get_duplicate_files(self):
|
|
"""Test getting duplicate files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add files and mark some as duplicates
|
|
original_id = repo.add_file("original.txt", 10, 12345, "hash1")
|
|
dup1_id = repo.add_file("dup1.txt", 100, 12346, "hash1")
|
|
dup2_id = repo.add_file("dup2.txt", 100, 12347, "hash1")
|
|
normal_id = repo.add_file("normal.txt", 200, 12348, "hash2")
|
|
assert original_id is not None # Ensure files were added successfully
|
|
assert dup1_id is not None
|
|
assert dup2_id is not None
|
|
assert normal_id is not None
|
|
|
|
repo.mark_as_duplicate(dup1_id, original_id)
|
|
repo.mark_as_duplicate(dup2_id, original_id)
|
|
|
|
duplicates = repo.get_duplicate_files()
|
|
assert len(duplicates) == 2
|
|
assert all(f['is_duplicate'] is True for f in duplicates)
|
|
|
|
def test_get_original_files(self):
|
|
"""Test getting original (non-duplicate) files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add files and mark some as duplicates
|
|
original_id = repo.add_file("original.txt", 10, 12345, "hash1")
|
|
dup_id = repo.add_file("dup.txt", 10, 12346, "hash1")
|
|
assert original_id is not None # Ensure files were added successfully
|
|
assert dup_id is not None
|
|
|
|
repo.mark_as_duplicate(dup_id, original_id)
|
|
|
|
originals = repo.get_original_files()
|
|
assert len(originals) == 1
|
|
assert originals[0]['id'] == original_id
|
|
assert originals[0]['is_duplicate'] is False
|
|
|
|
def test_count_files(self):
|
|
"""Test counting total files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
assert repo.count_files() == 0
|
|
file1_id = repo.add_file("file1.txt", 100, 12345, "hash1")
|
|
file2_id = repo.add_file("file2.txt", 200, 12346, "hash2")
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
assert repo.count_files() == 2
|
|
|
|
def test_count_duplicates(self):
|
|
"""Test counting duplicate files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add files and mark one as duplicate
|
|
original_id = repo.add_file("original.txt", 10, 12345, "hash1")
|
|
dup_id = repo.add_file("dup.txt", 10, 12346, "hash1")
|
|
assert original_id is not None # Ensure files were added successfully
|
|
assert dup_id is not None
|
|
|
|
assert repo.count_duplicates() == 0
|
|
repo.mark_as_duplicate(dup_id, original_id)
|
|
assert repo.count_duplicates() == 1
|
|
|
|
def test_batch_add_files(self):
|
|
"""Test batch adding multiple files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
files_data: List[Dict[str, Any]] = [
|
|
{"path": "file1.txt", "size": 100, "modified_time": 12345},
|
|
{"path": "file2.txt", "size": 200, "modified_time": 12346},
|
|
{"path": "file3.txt", "size": 300, "modified_time": 12347}
|
|
]
|
|
|
|
count = repo.batch_add_files(files_data)
|
|
assert count == 3
|
|
|
|
all_files = repo.get_all_files()
|
|
assert len(all_files) == 3
|
|
assert {f['path'] for f in all_files} == {"file1.txt", "file2.txt", "file3.txt"}
|
|
|
|
def test_clear_all_files(self):
|
|
"""Test clearing all files."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_ = db.get_connection() # Use underscore to indicate unused variable
|
|
_init_full_schema(db)
|
|
|
|
repo = FileRepository(db)
|
|
|
|
# Add some files
|
|
file1_id = repo.add_file("file1.txt", 100, 12345, "hash1")
|
|
file2_id = repo.add_file("file2.txt", 200, 12346, "hash2")
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
|
|
assert repo.count_files() == 2
|
|
|
|
# Clear all files
|
|
repo.clear_all_files()
|
|
|
|
assert repo.count_files() == 0
|
|
|
|
def test_get_file_repository_factory(self):
|
|
"""Test the factory function for getting file repository."""
|
|
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
repo = get_file_repository(tmp.name)
|
|
|
|
assert isinstance(repo, FileRepository)
|
|
assert isinstance(repo.db, DatabaseConnection)
|
|
|
|
os.unlink(tmp.name)
|
|
|
|
|
|
class TestDatabaseRepository:
|
|
"""Test DatabaseRepository class functionality."""
|
|
|
|
def test_initialization(self):
|
|
"""Test DatabaseRepository initialization."""
|
|
mock_connection = Mock()
|
|
repo = DatabaseRepository(mock_connection)
|
|
|
|
assert repo.connection is mock_connection
|
|
|
|
def test_create_method_raises_not_implemented(self):
|
|
"""Test that create method raises NotImplementedError."""
|
|
repo = DatabaseRepository(Mock())
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
repo.create("table", {"data": "value"})
|
|
|
|
def test_read_method_raises_not_implemented(self):
|
|
"""Test that read method raises NotImplementedError."""
|
|
repo = DatabaseRepository(Mock())
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
repo.read("table", 1)
|
|
|
|
def test_update_method_raises_not_implemented(self):
|
|
"""Test that update method raises NotImplementedError."""
|
|
repo = DatabaseRepository(Mock())
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
repo.update("table", 1, {"data": "value"})
|
|
|
|
def test_delete_method_raises_not_implemented(self):
|
|
"""Test that delete method raises NotImplementedError."""
|
|
repo = DatabaseRepository(Mock())
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
repo.delete("table", 1)
|
|
|
|
|
|
def test_database_integration():
|
|
"""Test full database integration with all components working together."""
|
|
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
try:
|
|
# Test with real database file
|
|
db = DatabaseConnection(tmp.name)
|
|
repo = FileRepository(db)
|
|
|
|
# Initialize database
|
|
_init_full_schema(db)
|
|
|
|
# Add some test files
|
|
file1_id = repo.add_file("/path/to/file1.txt", 1024, 1234567890, "hash123")
|
|
file2_id = repo.add_file("/path/to/file2.txt", 2048, 1234567891, "hash123") # Same hash
|
|
assert file1_id is not None # Ensure files were added successfully
|
|
assert file2_id is not None
|
|
|
|
# Mark second as duplicate
|
|
repo.mark_as_duplicate(file2_id, file1_id)
|
|
|
|
# Verify operations worked
|
|
assert repo.count_files() == 2
|
|
assert repo.count_duplicates() == 1
|
|
|
|
# Find duplicates by hash
|
|
duplicates = repo.find_duplicates_by_hash("hash123")
|
|
assert len(duplicates) == 2
|
|
|
|
# Get original files
|
|
originals = repo.get_original_files()
|
|
assert len(originals) == 1
|
|
|
|
# Update file
|
|
success = repo.update_file(file1_id, size=3072)
|
|
assert success is True
|
|
|
|
updated_file = repo.get_file(file1_id)
|
|
assert updated_file is not None
|
|
assert updated_file['size'] == 3072
|
|
|
|
finally:
|
|
os.unlink(tmp.name)
|
|
|
|
class TestDatabaseQueryComponents:
|
|
"""Test the new database query components."""
|
|
|
|
def test_database_query_execute(self):
|
|
"""Test DatabaseQuery execute method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_init_full_schema(db)
|
|
|
|
# Create DatabaseQuery instance
|
|
query = DatabaseQuery(db)
|
|
|
|
# Insert test data
|
|
db.execute(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("test.txt", 100, 12345, 12345, 12345, 12345)
|
|
)
|
|
|
|
# Test query execution
|
|
results = query.execute("SELECT * FROM files WHERE path = ?", ("test.txt",))
|
|
assert len(results) == 1
|
|
assert results[0]['path'] == "test.txt"
|
|
assert results[0]['size'] == 100
|
|
|
|
def test_database_batch_operations(self):
|
|
"""Test DatabaseBatch execute_batch method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_init_full_schema(db)
|
|
|
|
# Create DatabaseBatch instance
|
|
batch = DatabaseBatch(db)
|
|
|
|
# Test batch operations
|
|
operations = [
|
|
(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file1.txt", 100, 12345, 12345, 12345, 12345)
|
|
),
|
|
(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file2.txt", 200, 12346, 12345, 12345, 12345)
|
|
)
|
|
]
|
|
|
|
batch.execute_batch(operations)
|
|
|
|
# Verify batch execution
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
count = cursor.fetchone()[0]
|
|
assert count == 2
|
|
|
|
def test_database_batch_transaction(self):
|
|
"""Test DatabaseBatch execute_transaction_batch method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_init_full_schema(db)
|
|
|
|
# Create DatabaseBatch instance
|
|
batch = DatabaseBatch(db)
|
|
|
|
# Test transaction batch operations
|
|
operations = [
|
|
(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file1.txt", 100, 12345, 12345, 12345, 12345)
|
|
),
|
|
(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file2.txt", 200, 12346, 12345, 12345, 12345)
|
|
)
|
|
]
|
|
|
|
batch.execute_transaction_batch(operations)
|
|
|
|
# Verify transaction batch execution
|
|
cursor = db.execute("SELECT COUNT(*) FROM files")
|
|
count = cursor.fetchone()[0]
|
|
assert count == 2
|
|
|
|
def test_database_performance_monitoring(self):
|
|
"""Test DatabasePerformance monitoring methods."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Create DatabasePerformance instance
|
|
performance = DatabasePerformance(db)
|
|
|
|
# Test monitor_performance method
|
|
monitor = performance.monitor_performance()
|
|
assert monitor is not None
|
|
|
|
# Test get_results method
|
|
results = performance.get_results()
|
|
assert isinstance(results, dict)
|
|
assert 'metrics' in results or 'error' in results
|
|
|
|
def test_database_integrity_checking(self):
|
|
"""Test DatabaseIntegrity check_integrity method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Create DatabaseIntegrity instance
|
|
integrity = DatabaseIntegrity(db)
|
|
|
|
# Test check_integrity method
|
|
results = integrity.check_integrity()
|
|
assert isinstance(results, dict)
|
|
assert 'tables' in results
|
|
assert 'indexes' in results
|
|
assert 'valid' in results
|
|
assert results['valid'] is True
|
|
|
|
def test_database_backup_functionality(self):
|
|
"""Test DatabaseBackup create_backup and restore_backup methods."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp:
|
|
with tempfile.NamedTemporaryFile(suffix='_backup.db', delete=False) as backup:
|
|
try:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Add test data
|
|
db.connection.execute(
|
|
"INSERT INTO files (path, size, modified_time, "
|
|
"created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("test.txt", 100, 12345, 12345, 12345, 12345)
|
|
)
|
|
|
|
# Create DatabaseBackup instance
|
|
backup_db = DatabaseBackup(db)
|
|
|
|
# Test create_backup method
|
|
backup_db.create_backup(backup.name)
|
|
|
|
# Verify backup was created
|
|
assert os.path.exists(backup.name)
|
|
|
|
# Test restore_backup method
|
|
restore_path = tmp.name + "_restored"
|
|
backup_db.restore_backup(backup.name, restore_path)
|
|
|
|
# Verify restore was created
|
|
assert os.path.exists(restore_path)
|
|
|
|
finally:
|
|
os.unlink(tmp.name)
|
|
if os.path.exists(backup.name):
|
|
os.unlink(backup.name)
|
|
if os.path.exists(tmp.name + "_restored"):
|
|
os.unlink(tmp.name + "_restored")
|
|
|
|
def test_database_migration_functionality(self):
|
|
"""Test DatabaseMigration migrate_schema method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Create DatabaseMigration instance
|
|
migration = DatabaseMigration(db)
|
|
|
|
# Test migrate_schema method
|
|
migrations = {
|
|
"test_table": {
|
|
"add_columns": ["new_column TEXT"],
|
|
"add_indexes": ["CREATE INDEX idx_test_table_new ON test_table(new_column)"]
|
|
}
|
|
}
|
|
|
|
# This should not raise an error even if the table doesn't exist
|
|
migration.migrate_schema(migrations)
|
|
|
|
def test_database_recovery_functionality(self):
|
|
"""Test DatabaseRecovery handle_errors method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Create DatabaseRecovery instance
|
|
recovery = DatabaseRecovery(db)
|
|
|
|
# Test handle_errors method with no errors
|
|
result = recovery.handle_errors(raise_on_error=False)
|
|
assert result is True
|
|
|
|
# Test handle_errors method with raise_on_error=True
|
|
result = recovery.handle_errors(raise_on_error=True)
|
|
assert result is True
|
|
|
|
def test_database_optimization_functionality(self):
|
|
"""Test DatabaseOptimization optimize_query method."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = DatabaseConnection(tmp.name)
|
|
_init_full_schema(db)
|
|
|
|
# Create DatabaseOptimization instance
|
|
optimization = DatabaseOptimization(db)
|
|
|
|
# Test optimize_query method
|
|
query = " SELECT * FROM files WHERE size > 100; "
|
|
optimized = optimization.optimize_query(query)
|
|
assert optimized == "SELECT * FROM files WHERE size > 100"
|
|
|
|
# Test with query that doesn't end with semicolon
|
|
query2 = "SELECT * FROM files"
|
|
optimized2 = optimization.optimize_query(query2)
|
|
assert optimized2 == "SELECT * FROM files"
|
|
|
|
class TestDatabaseIntegration:
|
|
"""Test full Database class integration."""
|
|
|
|
def test_database_initialization(self):
|
|
"""Test Database class initialization."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
# Test Database initialization
|
|
db = Database(tmp.name)
|
|
assert db.path == tmp.name
|
|
assert db.timeout == 30.0
|
|
assert hasattr(db, 'connection')
|
|
assert hasattr(db, 'schema')
|
|
assert hasattr(db, 'indexing')
|
|
assert hasattr(db, 'query')
|
|
assert hasattr(db, 'batch')
|
|
assert hasattr(db, 'transaction')
|
|
assert hasattr(db, 'performance')
|
|
assert hasattr(db, 'integrity')
|
|
assert hasattr(db, 'backup')
|
|
assert hasattr(db, 'migration')
|
|
assert hasattr(db, 'recovery')
|
|
assert hasattr(db, 'security')
|
|
assert hasattr(db, 'optimization')
|
|
|
|
# Test connection
|
|
conn = db.connect()
|
|
assert isinstance(conn, sqlite3.Connection)
|
|
|
|
# Test close
|
|
db.close()
|
|
|
|
def test_database_query_operations(self):
|
|
"""Test Database query operations."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Test create_table
|
|
db.create_table("test_table", "id INTEGER PRIMARY KEY, name TEXT")
|
|
|
|
# Test create
|
|
data_id = db.create("test_table", {"name": "test"})
|
|
assert data_id is not None
|
|
|
|
# Test read
|
|
results = db.read("SELECT * FROM test_table WHERE id = ?", (data_id,))
|
|
assert len(results) == 1
|
|
assert results[0]['name'] == "test"
|
|
|
|
# Test update
|
|
updated_count = db.update("UPDATE test_table SET name = ? WHERE id = ?", ("updated", data_id))
|
|
assert updated_count == 1
|
|
|
|
# Test delete
|
|
deleted_count = db.delete("DELETE FROM test_table WHERE id = ?", (data_id,))
|
|
assert deleted_count == 1
|
|
|
|
# Test close
|
|
db.close()
|
|
|
|
def test_database_batch_operations(self):
|
|
"""Test Database batch operations."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
db = Database(tmp.name)
|
|
_init_full_schema(db.connection)
|
|
|
|
# Test execute_batch
|
|
operations = [
|
|
("INSERT INTO files (path, size, modified_time, created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file1.txt", 100, 12345, 12345, 12345, 12345)),
|
|
("INSERT INTO files (path, size, modified_time, created_time, scanned_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
("file2.txt", 200, 12346, 12345, 12345, 12345))
|
|
]
|
|
|
|
db.execute_batch(operations)
|
|
|
|
# Verify batch execution
|
|
results = db.read("SELECT COUNT(*) FROM files")
|
|
assert results[0]['COUNT(*)'] == 2
|
|
|
|
# Test close
|
|
db.close()
|
|
|
|
def test_database_context_manager(self):
|
|
"""Test Database context manager functionality."""
|
|
with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
|
|
# Test context manager
|
|
with Database(tmp.name) as db:
|
|
_init_full_schema(db.connection)
|
|
assert isinstance(db, Database)
|
|
|
|
# Test operations within context
|
|
db.create_table("context_table", "id INTEGER PRIMARY KEY, value TEXT")
|
|
db.create("context_table", {"value": "test"})
|
|
|
|
# Context manager should have closed the connection
|
|
|
|
# Verify we can create a new database instance
|
|
db2 = Database(tmp.name)
|
|
results = db2.read("SELECT * FROM context_table")
|
|
assert len(results) == 1
|
|
db2.close()
|