import pytest
import tempfile
import shutil
from pathlib import Path
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'apexbase', 'python'))
try:
from apexbase import ApexClient, DurabilityLevel, __version__, FTS_AVAILABLE, ARROW_AVAILABLE, POLARS_AVAILABLE
except ImportError as e:
pytest.skip(f"ApexBase not available: {e}", allow_module_level=True)
class TestApexClientInitialization:
def test_default_initialization(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
assert client._dirpath == Path(temp_dir)
assert client._current_table == "default"
assert client._batch_size == 1000
assert client._enable_cache is True
assert client._cache_size == 10000
assert client._prefer_arrow_format == ARROW_AVAILABLE
assert client._durability == 'fast'
assert client._auto_manage is True
assert not client._is_closed
assert client._db_path.name == "apexbase.apex"
client.close()
def test_initialization_with_custom_parameters(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(
dirpath=temp_dir,
batch_size=500,
drop_if_exists=False,
enable_cache=False,
cache_size=20000,
prefer_arrow_format=False,
durability='safe',
_auto_manage=False
)
assert client._batch_size == 500
assert client._enable_cache is False
assert client._cache_size == 20000
assert client._prefer_arrow_format is False
assert client._durability == 'safe'
assert client._auto_manage is False
client.close()
def test_durability_levels(self):
valid_durabilities = ['fast', 'safe', 'max']
for durability in valid_durabilities:
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, durability=durability)
client.create_table("default")
assert client._durability == durability
client.close()
def test_invalid_durability_level(self):
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(ValueError, match="durability must be 'fast', 'safe', or 'max'"):
ApexClient(dirpath=temp_dir, durability='invalid')
def test_drop_if_exists_true(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = Path(temp_dir) / "default.apex"
client1 = ApexClient(dirpath=temp_dir)
client1.create_table("default")
client1.store({"test": "data"})
client1.close()
assert db_path.exists()
client2 = ApexClient(dirpath=temp_dir, drop_if_exists=True)
assert not db_path.exists() client2.create_table("default")
client2.close()
def test_drop_if_exists_false(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = Path(temp_dir) / "default.apex"
client1 = ApexClient(dirpath=temp_dir)
client1.create_table("default")
client1.store({"test": "data"})
client1.close()
assert db_path.exists()
client2 = ApexClient(dirpath=temp_dir, drop_if_exists=False)
client2.use_table("default")
assert db_path.exists() client2.close()
def test_none_dirpath_uses_current_directory(self):
original_cwd = os.getcwd()
with tempfile.TemporaryDirectory() as temp_dir:
try:
os.chdir(temp_dir)
client = ApexClient(dirpath=None)
client.create_table("default")
assert client._dirpath == Path('.')
client.close()
finally:
os.chdir(original_cwd)
def test_relative_path(self):
with tempfile.TemporaryDirectory() as temp_dir:
relative_path = "./test_db"
original_cwd = os.getcwd()
os.chdir(temp_dir)
try:
client = ApexClient(dirpath=relative_path)
client.create_table("default")
assert client._dirpath.exists()
client.close()
finally:
os.chdir(original_cwd)
def test_nested_directory_creation(self):
with tempfile.TemporaryDirectory() as temp_dir:
nested_path = Path(temp_dir) / "level1" / "level2" / "level3"
client = ApexClient(dirpath=str(nested_path))
client.create_table("default")
assert nested_path.exists()
assert nested_path.is_dir()
client.close()
def test_create_clean_classmethod(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = Path(temp_dir) / "default.apex"
client1 = ApexClient(dirpath=temp_dir)
client1.create_table("default")
client1.store({"test": "data"})
client1.close()
assert db_path.exists()
client2 = ApexClient.create_clean(dirpath=temp_dir)
client2.create_table("default")
assert client2.count_rows() == 0
client2.close()
def test_version_and_constants(self):
assert isinstance(__version__, str)
assert len(__version__) > 0
assert isinstance(FTS_AVAILABLE, bool)
assert isinstance(ARROW_AVAILABLE, bool)
assert isinstance(POLARS_AVAILABLE, bool)
assert FTS_AVAILABLE is True
def test_repr_method(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
repr_str = repr(client)
assert "ApexClient" in repr_str
assert str(client._dirpath) in repr_str
assert "default" in repr_str
client.close()
def test_multiple_clients_same_directory(self):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir)
client1.create_table("default")
client1.store({"client": 1})
client2 = ApexClient(dirpath=temp_dir)
client2.use_table("default")
count = client2.count_rows()
assert count == 1
client1.close()
client2.close()
def test_closed_client_operations(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.close()
with pytest.raises(RuntimeError, match="connection has been closed"):
client.use_table("test")
with pytest.raises(RuntimeError, match="connection has been closed"):
client.store({"test": "data"})
with pytest.raises(RuntimeError, match="connection has been closed"):
client.query()
with pytest.raises(RuntimeError, match="connection has been closed"):
client.execute("SELECT * FROM default")
with pytest.raises(RuntimeError, match="connection has been closed"):
client.list_tables()
def test_edge_case_empty_string_dirpath(self):
original_cwd = os.getcwd()
with tempfile.TemporaryDirectory() as temp_dir:
try:
os.chdir(temp_dir)
client = ApexClient(dirpath="")
client.create_table("default")
assert client._dirpath == Path('.')
client.close()
finally:
os.chdir(original_cwd)
def test_edge_case_whitespace_dirpath(self):
with tempfile.TemporaryDirectory() as temp_dir:
whitespace_path = os.path.join(temp_dir, "test db")
try:
client = ApexClient(dirpath=whitespace_path)
client.create_table("default")
client.store({"test": "data"})
client.close()
except Exception as e:
print(f"Whitespace path handling: {e}")
def test_edge_case_large_batch_size(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, batch_size=1000000)
client.create_table("default")
assert client._batch_size == 1000000
client.close()
def test_edge_case_zero_batch_size(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, batch_size=0)
client.create_table("default")
assert client._batch_size == 0
client.close()
def test_edge_case_negative_batch_size(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, batch_size=-1)
client.create_table("default")
assert client._batch_size == -1
client.close()
def test_edge_case_large_cache_size(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, cache_size=1000000)
client.create_table("default")
assert client._cache_size == 1000000
client.close()
def test_fts_tables_initialization(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
assert isinstance(client._fts_tables, dict)
assert len(client._fts_tables) == 0
client.close()
def test_prefer_arrow_format_without_arrow(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, prefer_arrow_format=False)
client.create_table("default")
assert client._prefer_arrow_format is False
client.close()
def test_auto_manage_disabled(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, _auto_manage=False)
client.create_table("default")
assert client._auto_manage is False
client.close()
def test_database_file_permissions(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"test": "data"})
client.flush()
db_path = client._db_path
if db_path.exists():
assert db_path.is_file()
assert os.access(db_path, os.R_OK)
assert os.access(db_path, os.W_OK)
client.close()
class TestDurabilityLevels:
def test_fast_durability_default(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
assert client._durability == 'fast'
client.close()
def test_safe_durability(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, durability='safe')
client.create_table("default")
assert client._durability == 'safe'
client.store({"test": "safe"})
client.flush()
assert client.count_rows() == 1
client.close()
def test_max_durability(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, durability='max')
client.create_table("default")
assert client._durability == 'max'
client.store({"test": "max"})
client.flush()
assert client.count_rows() == 1
client.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_all_durability_levels_basic_operations(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, durability=durability)
client.create_table("default")
client.store({"name": "test", "value": 123})
result = client.retrieve(1)
assert result is not None
assert result["name"] == "test"
assert result["value"] == 123
client.close()
class TestDurabilityExceptionScenarios:
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_data_persistence_after_close_and_reopen(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
test_data = [
{"id": 1, "name": "Alice", "value": 100},
{"id": 2, "name": "Bob", "value": 200},
{"id": 3, "name": "Charlie", "value": 300},
]
client1.store(test_data)
if durability == 'fast':
client1.flush()
original_count = client1.count_rows()
assert original_count == 3
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
reopened_count = client2.count_rows()
assert reopened_count == original_count, \
f"Data not persisted with durability='{durability}': expected {original_count}, got {reopened_count}"
for i in range(3):
result = client2.retrieve(i + 1)
assert result is not None, f"Record {i} missing after reopen"
assert result["id"] == i + 1
client2.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_flush_ensures_persistence(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
client1.store({"test": "flush_data", "value": 42})
client1.flush()
count_before_close = client1.count_rows()
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
count_after_reopen = client2.count_rows()
assert count_after_reopen == count_before_close, \
f"flush() did not persist data with durability='{durability}'"
result = client2.retrieve(1)
assert result["test"] == "flush_data"
assert result["value"] == 42
client2.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_batch_write_persistence(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
batch_size = 100
batch_data = [{"idx": i, "data": f"item_{i}"} for i in range(batch_size)]
client1.store(batch_data)
client1.flush()
assert client1.count_rows() == batch_size
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
assert client2.count_rows() == batch_size, \
f"Batch data not fully persisted with durability='{durability}'"
for idx in [0, 49, 99]:
result = client2.retrieve(idx + 1)
assert result is not None
assert result["idx"] == idx
assert result["data"] == f"item_{idx}"
client2.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_multiple_write_sessions(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
total_records = 0
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
client1.store([{"session": 1, "idx": i} for i in range(10)])
client1.flush()
total_records += 10
assert client1.count_rows() == total_records
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
assert client2.count_rows() == total_records, "Data from session 1 not persisted"
client2.store([{"session": 2, "idx": i} for i in range(10)])
client2.flush()
total_records += 10
assert client2.count_rows() == total_records
client2.close()
client3 = ApexClient(dirpath=temp_dir, durability=durability)
client3.use_table("default")
assert client3.count_rows() == total_records, \
f"Expected {total_records} records, got {client3.count_rows()}"
client3.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_exception_during_write_recovery(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, durability=durability)
client.create_table("default")
client.store({"status": "before_error", "value": 1})
client.flush()
try:
client.store("invalid_data_type")
except (ValueError, TypeError):
pass
client.store({"status": "after_error", "value": 2})
client.flush()
assert client.count_rows() == 2
results = client.execute("SELECT * FROM default ORDER BY _id").to_dict()
assert len(results) == 2
assert results[0]["status"] == "before_error"
assert results[1]["status"] == "after_error"
client.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
assert client2.count_rows() == 2
client2.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_close_without_flush_behavior(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
client1.store({"test": "no_flush", "value": 123})
count_before = client1.count_rows()
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
count_after = client2.count_rows()
if durability in ('safe', 'max'):
assert count_after == count_before, \
f"durability='{durability}' should persist data even without explicit flush"
client2.close()
@pytest.mark.parametrize("durability", ['fast', 'safe', 'max'])
def test_large_data_persistence(self, durability):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability=durability)
client1.create_table("default")
large_string = "x" * 10000 data = [
{"id": i, "content": large_string, "index": i * 100}
for i in range(50)
]
client1.store(data)
client1.flush()
assert client1.count_rows() == 50
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability=durability)
client2.use_table("default")
assert client2.count_rows() == 50
result = client2.retrieve(26)
assert result["id"] == 25
assert result["content"] == large_string
assert result["index"] == 2500
client2.close()
def test_durability_upgrade_safe_to_max(self):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability='safe')
client1.create_table("default")
client1.store({"created_with": "safe"})
client1.flush()
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability='max')
client2.use_table("default")
assert client2.count_rows() == 1
result = client2.retrieve(1)
assert result["created_with"] == "safe"
client2.store({"created_with": "max"})
client2.flush()
assert client2.count_rows() == 2
client2.close()
client3 = ApexClient(dirpath=temp_dir, durability='fast')
client3.use_table("default")
assert client3.count_rows() == 2
client3.close()
def test_durability_downgrade_max_to_fast(self):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir, durability='max')
client1.create_table("default")
client1.store({"created_with": "max"})
client1.flush()
client1.close()
client2 = ApexClient(dirpath=temp_dir, durability='fast')
client2.use_table("default")
assert client2.count_rows() == 1
result = client2.retrieve(1)
assert result["created_with"] == "max"
client2.store({"created_with": "fast"})
client2.flush()
assert client2.count_rows() == 2
client2.close()
if __name__ == "__main__":
pytest.main([__file__, "-v"])