from __future__ import annotations
import os
import tempfile
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.engine.url import make_url
from app.database import (
create_engine_for_url,
get_database_url,
is_postgres_url,
wait_for_db,
)
from app.models import CreateItemDTO, LifecycleType
from app.store import DatabaseStore, get_store
def test_database_url_resolution(monkeypatch):
# Default fallback
monkeypatch.delenv("DATABASE_URL", raising=False)
monkeypatch.delenv("DB_URL", raising=False)
assert get_database_url() == "sqlite:///./lifecycle.db"
# DB_URL fallback
monkeypatch.setenv("DB_URL", "sqlite:///./custom.db")
assert get_database_url() == "sqlite:///./custom.db"
# DATABASE_URL priority
monkeypatch.setenv("DATABASE_URL", "sqlite:///./priority.db")
assert get_database_url() == "sqlite:///./priority.db"
# Postgres URL scheme normalization (e.g. for future PostgreSQL support)
monkeypatch.setenv("DATABASE_URL", "postgres://user:pass@localhost:5432/lifecycledb")
normalized = get_database_url()
assert normalized == "postgresql://user:pass@localhost:5432/lifecycledb"
parsed = make_url(normalized)
assert parsed.drivername == "postgresql"
assert parsed.host == "localhost"
assert parsed.port == 5432
assert parsed.database == "lifecycledb"
def test_is_postgres_url():
assert is_postgres_url("postgresql://user:pass@localhost:5432/db") is True
assert is_postgres_url("postgres://user:pass@localhost:5432/db") is True
assert is_postgres_url("postgresql+psycopg2://user:pass@localhost:5432/db") is True
assert is_postgres_url("sqlite:///./lifecycle.db") is False
assert is_postgres_url("sqlite:///:memory:") is False
def test_database_engine_dialect_handling(monkeypatch):
# SQLite engine creation
sqlite_engine = create_engine_for_url("sqlite:///:memory:")
assert sqlite_engine.dialect.name == "sqlite"
# PostgreSQL engine creation with connection pooling
monkeypatch.setenv("DB_POOL_SIZE", "15")
monkeypatch.setenv("DB_MAX_OVERFLOW", "25")
pg_engine = create_engine_for_url("postgresql://user:pass@localhost:5432/lifecycledb")
assert pg_engine.dialect.name == "postgresql"
assert pg_engine.dialect.driver in ("psycopg", "psycopg2")
assert pg_engine.pool.size() == 15
# Explicit psycopg2 driver URL
pg2_engine = create_engine_for_url("postgresql+psycopg2://user:pass@localhost:5432/lifecycledb")
assert pg2_engine.dialect.name == "postgresql"
assert pg2_engine.dialect.driver == "psycopg2"
def test_sqlite_persistence_across_connections():
with tempfile.TemporaryDirectory() as temp_dir:
db_file = os.path.join(temp_dir, "test_persist.db")
db_url = f"sqlite:///{db_file}"
# 1. Initialize store instance 1 and seed
store_1 = DatabaseStore(database_url=db_url)
store_1.seed()
user = store_1.get_default_user()
new_item = store_1.create_item(
user=user,
data=CreateItemDTO(
name="Persistent Refrigerator Warranty",
lifecycleType=LifecycleType.warranty,
dueDate="2027-01-01",
),
)
assert new_item.name == "Persistent Refrigerator Warranty"
item_id = new_item.id
# 2. Initialize brand new store instance 2 pointing to the exact same file
store_2 = DatabaseStore(database_url=db_url)
# Should not wipe existing data because user count > 0
persisted_item = store_2.get_item_by_id(item_id)
assert persisted_item is not None
assert persisted_item.name == "Persistent Refrigerator Warranty"
assert persisted_item.lifecycleType == LifecycleType.warranty
def test_database_healthcheck_endpoint(client: TestClient):
res = client.get("/health")
assert res.status_code == 200
data = res.json()
assert data["status"] == "healthy"
assert data["database"] == "connected"