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
- 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>
292 lines
8.1 KiB
Python
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")
|