nordabiz/tests/conftest.py
Maciej Pienczyn a57187e05f
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
test: Add comprehensive testing infrastructure
- pytest framework with fixtures for auth (auth_client, admin_client)
- Unit tests for SearchService
- Integration tests for auth flow
- Security tests (OWASP Top 10: SQL injection, XSS, CSRF)
- Smoke tests for production health and backup monitoring
- E2E tests with Playwright (basic structure)
- DR tests for backup/restore procedures
- GitHub Actions CI/CD workflow (.github/workflows/test.yml)
- Coverage configuration (.coveragerc) with 80% minimum
- DR documentation and restore script

Staging environment: VM 248, staging.nordabiznes.pl

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:52:34 +01:00

292 lines
8.1 KiB
Python

"""
NordaBiz Test Configuration and Fixtures
=========================================
Shared fixtures for all test types:
- app: Flask application instance
- client: Test client for HTTP requests
- auth_client: Client with logged-in regular user session
- admin_client: Client with logged-in admin session
- db_session: Database session for integration tests
"""
import os
import sys
from pathlib import Path
import pytest
# Add project root to path for imports
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
# Set testing environment before importing app
os.environ['FLASK_ENV'] = 'testing'
os.environ['TESTING'] = 'true'
# If no DATABASE_URL, use SQLite for tests
if not os.environ.get('DATABASE_URL'):
os.environ['DATABASE_URL'] = 'sqlite:///:memory:'
# =============================================================================
# Database Availability Check
# =============================================================================
def is_database_available():
"""Check if database is available for testing."""
try:
from database import engine
with engine.connect() as conn:
pass
return True
except Exception:
return False
# =============================================================================
# Flask Application Fixtures
# =============================================================================
@pytest.fixture(scope='session')
def app():
"""Create Flask application for testing."""
from app import app as flask_app
flask_app.config.update({
'TESTING': True,
'WTF_CSRF_ENABLED': False, # Disable CSRF for tests
'LOGIN_DISABLED': False,
'SERVER_NAME': 'localhost',
'SESSION_COOKIE_SECURE': False,
'SESSION_COOKIE_HTTPONLY': True,
})
with flask_app.app_context():
yield flask_app
@pytest.fixture
def client(app):
"""Create test client for HTTP requests."""
return app.test_client()
@pytest.fixture
def runner(app):
"""Create CLI runner for testing CLI commands."""
return app.test_cli_runner()
# =============================================================================
# Authentication Fixtures
# =============================================================================
# Test credentials (from CLAUDE.md)
TEST_USER_EMAIL = 'test@nordabiznes.pl'
TEST_USER_PASSWORD = '&Rc2LdbSw&jiGR0ek@Bz'
TEST_ADMIN_EMAIL = 'testadmin@nordabiznes.pl'
TEST_ADMIN_PASSWORD = 'cSfQbbwegwv1v3Q2Dm0Q'
def _scrypt_available():
"""Check if hashlib.scrypt is available (required for password verification)."""
try:
import hashlib
hashlib.scrypt(b'test', salt=b'salt', n=2, r=1, p=1)
return True
except (AttributeError, ValueError):
return False
SCRYPT_AVAILABLE = _scrypt_available()
@pytest.fixture
def auth_client(client, app):
"""
Create test client with logged-in regular user session.
Uses test account: test@nordabiznes.pl
Role: MEMBER (regular user)
"""
if not SCRYPT_AVAILABLE:
pytest.skip("hashlib.scrypt not available - cannot test login")
with app.app_context():
response = client.post('/login', data={
'email': TEST_USER_EMAIL,
'password': TEST_USER_PASSWORD,
}, follow_redirects=True)
# Check if login succeeded by verifying we're not redirected back to login
if '/login' in response.request.path:
pytest.skip("Login failed - check test user credentials or database")
return client
@pytest.fixture
def admin_client(client, app):
"""
Create test client with logged-in admin session.
Uses test account: testadmin@nordabiznes.pl
Role: ADMIN
"""
if not SCRYPT_AVAILABLE:
pytest.skip("hashlib.scrypt not available - cannot test login")
with app.app_context():
response = client.post('/login', data={
'email': TEST_ADMIN_EMAIL,
'password': TEST_ADMIN_PASSWORD,
}, follow_redirects=True)
# Check if login succeeded
if '/login' in response.request.path:
pytest.skip("Admin login failed - check test admin credentials or database")
return client
# =============================================================================
# Database Fixtures
# =============================================================================
class DatabaseWrapper:
"""Wrapper for database access in tests."""
def __init__(self):
from database import engine, SessionLocal
self.engine = engine
self.SessionLocal = SessionLocal
self._session = None
@property
def session(self):
if self._session is None:
self._session = self.SessionLocal()
return self._session
def close(self):
if self._session:
self._session.close()
self._session = None
@pytest.fixture(scope='session')
def db(app):
"""Get database wrapper instance."""
wrapper = DatabaseWrapper()
yield wrapper
wrapper.close()
@pytest.fixture
def db_session(db, app):
"""
Create database session for integration tests.
Rolls back changes after each test.
"""
with app.app_context():
session = db.SessionLocal()
yield session
session.rollback()
session.close()
# =============================================================================
# Mock Fixtures
# =============================================================================
@pytest.fixture
def mock_gemini(mocker):
"""Mock Gemini AI service for unit tests."""
mock = mocker.patch('gemini_service.generate_content')
mock.return_value = {
'text': 'Mocked AI response for testing',
'companies': []
}
return mock
@pytest.fixture
def mock_email(mocker):
"""Mock email sending for tests."""
return mocker.patch('flask_mail.Mail.send')
# =============================================================================
# Test Data Fixtures
# =============================================================================
@pytest.fixture
def sample_company():
"""Sample company data for tests."""
return {
'name': 'Test Company Sp. z o.o.',
'slug': 'test-company-sp-z-o-o',
'nip': '1234567890',
'category': 'IT',
'short_description': 'Test company for automated tests',
'website': 'https://test-company.pl',
'email': 'contact@test-company.pl',
'phone': '+48 123 456 789',
}
@pytest.fixture
def sample_user():
"""Sample user data for tests."""
return {
'email': 'newuser@test.pl',
'password': 'SecurePassword123!',
'first_name': 'Test',
'last_name': 'User',
}
# =============================================================================
# URL Fixtures
# =============================================================================
@pytest.fixture
def production_url():
"""Production URL for smoke tests."""
return 'https://nordabiznes.pl'
@pytest.fixture
def staging_url():
"""Staging URL for E2E tests."""
return 'https://staging.nordabiznes.pl'
# =============================================================================
# Cleanup Fixtures
# =============================================================================
@pytest.fixture(autouse=True)
def cleanup_after_test():
"""Cleanup after each test."""
yield
# Add any cleanup logic here if needed
# =============================================================================
# Markers Configuration
# =============================================================================
def pytest_configure(config):
"""Configure pytest markers."""
config.addinivalue_line("markers", "unit: Unit tests")
config.addinivalue_line("markers", "integration: Integration tests")
config.addinivalue_line("markers", "e2e: End-to-end tests")
config.addinivalue_line("markers", "smoke: Smoke tests")
config.addinivalue_line("markers", "security: Security tests")
config.addinivalue_line("markers", "migration: Migration tests")
config.addinivalue_line("markers", "dr: Disaster recovery tests")
config.addinivalue_line("markers", "slow: Slow tests")