- Add login page with JWT authentication - Add dashboard with stats and quick actions - Add links management page (full CRUD with search) - Add collections management page - Add API key management page with copy-to-clipboard - Add admin user management page (admin only) - Fix UUID type mismatches across all endpoints - Add updated_at column to api_keys and audit_log in schema.sql - Fix DB_PASSWORD default in docker-compose.yml - Add PyJWT to requirements.txt - Fix API docs URL (/docs instead of /api/docs) - Improve JS error handling (show actual messages) - Rewrite conftest.py with proper DB lifecycle management - Add 42 new integration tests (84 total, all passing) - test_admin.py: 15 tests for admin endpoints - test_auth_extended.py: 9 tests for API key CRUD - test_tags.py: 12 tests for tag endpoints - test_sync.py: 6 tests for sync endpoints
142 lines
3.7 KiB
Python
142 lines
3.7 KiB
Python
"""
|
|
LinkSyncServer - Test Configuration
|
|
|
|
Database lifecycle:
|
|
- pytest_configure: Creates fresh test database before any tests run
|
|
- pytest_unconfigure: Destroys test database after all tests complete
|
|
- Per-test: Transaction rollback ensures test isolation
|
|
"""
|
|
|
|
import os
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine, event
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
from models.base import Base
|
|
import models.base
|
|
|
|
|
|
TEST_DATABASE_URL = "sqlite:///test_linksync.db"
|
|
|
|
|
|
def pytest_configure(config):
|
|
"""Called before any tests run. Creates fresh test database."""
|
|
if os.path.exists("test_linksync.db"):
|
|
os.remove("test_linksync.db")
|
|
|
|
test_engine = create_engine(
|
|
TEST_DATABASE_URL,
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
)
|
|
Base.metadata.create_all(bind=test_engine)
|
|
|
|
# Override the module-level get_engine to return our test engine
|
|
original_get_engine = models.base.get_engine
|
|
original_get_session = models.base.get_session
|
|
|
|
def test_get_engine():
|
|
return test_engine
|
|
|
|
def test_get_session():
|
|
Session = sessionmaker(bind=test_engine)
|
|
return Session()
|
|
|
|
models.base.get_engine = test_get_engine
|
|
models.base.get_session = test_get_session
|
|
|
|
# Store originals for cleanup
|
|
config._test_engine = test_engine
|
|
config._original_get_engine = original_get_engine
|
|
config._original_get_session = original_get_session
|
|
|
|
|
|
def pytest_unconfigure(config):
|
|
"""Called after all tests complete. Destroys test database."""
|
|
# Restore original functions
|
|
if hasattr(config, "_original_get_engine"):
|
|
models.base.get_engine = config._original_get_engine
|
|
models.base.get_session = config._original_get_session
|
|
|
|
# Drop all tables
|
|
if hasattr(config, "_test_engine"):
|
|
Base.metadata.drop_all(bind=config._test_engine)
|
|
config._test_engine.dispose()
|
|
|
|
# Remove the test database file
|
|
if os.path.exists("test_linksync.db"):
|
|
os.remove("test_linksync.db")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def test_engine(pytestconfig):
|
|
"""Provides the test engine for direct database access in tests."""
|
|
return pytestconfig._test_engine
|
|
|
|
|
|
@pytest.fixture
|
|
def db_session(test_engine):
|
|
"""Provides a transactional session that rolls back after each test."""
|
|
connection = test_engine.connect()
|
|
transaction = connection.begin()
|
|
Session = sessionmaker(bind=connection)
|
|
session = Session()
|
|
|
|
yield session
|
|
|
|
session.close()
|
|
transaction.rollback()
|
|
connection.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Provides a TestClient with the test database already configured."""
|
|
from app import app
|
|
with TestClient(app) as c:
|
|
yield c
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_token(client):
|
|
response = client.post(
|
|
"/api/auth/login",
|
|
data={"username": "admin", "password": "admin123"},
|
|
)
|
|
assert response.status_code == 200
|
|
return response.json()["access_token"]
|
|
|
|
|
|
@pytest.fixture
|
|
def auth_headers(admin_token):
|
|
return {"Authorization": f"Bearer {admin_token}"}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_bookmark_data():
|
|
return {
|
|
"url": "https://example.com",
|
|
"title": "Example Site",
|
|
"description": "An example website",
|
|
"notes": "Test notes",
|
|
"tags": ["test", "example"],
|
|
"favicon_url": "https://example.com/favicon.ico",
|
|
"path": "/Test",
|
|
"visit_count": 0,
|
|
"is_bookmarked": True,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_collection_data():
|
|
return {
|
|
"name": "Test Collection",
|
|
"description": "A test collection",
|
|
"query_type": "static",
|
|
"query_expression": None,
|
|
"is_public": False,
|
|
"link_ids": [],
|
|
}
|