Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

15 KiB

Flask Testing Suite

Comprehensive testing setup for Flask applications with pytest.

Usage

# Run all tests
pytest

# Run with coverage
pytest --cov=app --cov-report=html

# Run specific test file
pytest tests/test_models.py

# Run with verbose output
pytest -v

Test Configuration

# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = 
    --cov=app
    --cov-report=term-missing
    --cov-report=html:htmlcov
    --strict-markers
    --disable-warnings
markers =
    unit: Unit tests
    integration: Integration tests
    slow: Slow running tests
    auth: Authentication tests

Test Fixtures

# tests/conftest.py
import pytest
import tempfile
import os
from app import create_app
from app.extensions import db
from app.models import User, Post, Category
from flask_login import login_user

@pytest.fixture(scope='session')
def app():
    """Create test application."""
    # Create temporary database
    db_fd, db_path = tempfile.mkstemp()
    
    app = create_app({
        'TESTING': True,
        'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
        'WTF_CSRF_ENABLED': False,
        'SECRET_KEY': 'test-secret-key'
    })
    
    with app.app_context():
        db.create_all()
        yield app
        
    # Cleanup
    os.close(db_fd)
    os.unlink(db_path)

@pytest.fixture
def client(app):
    """Create test client."""
    return app.test_client()

@pytest.fixture
def runner(app):
    """Create test CLI runner."""
    return app.test_cli_runner()

@pytest.fixture
def db_session(app):
    """Create database session for testing."""
    with app.app_context():
        connection = db.engine.connect()
        transaction = connection.begin()
        
        # Configure session to use the connection
        db.session.configure(bind=connection)
        
        yield db.session
        
        # Rollback transaction
        transaction.rollback()
        connection.close()
        db.session.remove()

@pytest.fixture
def user(db_session):
    """Create test user."""
    user = User(
        username='testuser',
        email='test@example.com',
        first_name='Test',
        last_name='User'
    )
    user.set_password('testpass123')
    user.save()
    return user

@pytest.fixture
def admin_user(db_session):
    """Create admin user."""
    admin = User(
        username='admin',
        email='admin@example.com',
        first_name='Admin',
        last_name='User',
        is_admin=True
    )
    admin.set_password('adminpass123')
    admin.save()
    return admin

@pytest.fixture
def category(db_session):
    """Create test category."""
    category = Category(
        name='Test Category',
        description='A test category'
    )
    category.save()
    return category

@pytest.fixture
def post(db_session, user, category):
    """Create test post."""
    post = Post(
        title='Test Post',
        content='This is a test post content.',
        slug='test-post',
        status='published',
        user_id=user.id,
        category_id=category.id
    )
    post.save()
    return post

@pytest.fixture
def auth_headers(user):
    """Create authentication headers."""
    # For API testing
    token = user.generate_auth_token()
    return {'Authorization': f'Bearer {token}'}

Model Testing

# tests/test_models.py
import pytest
from datetime import datetime
from app.models import User, Post, Category
from werkzeug.security import check_password_hash

class TestUser:
    """Test User model."""
    
    def test_user_creation(self, db_session):
        """Test user creation."""
        user = User(
            username='newuser',
            email='new@example.com',
            first_name='New',
            last_name='User'
        )
        user.set_password('password123')
        user.save()
        
        assert user.id is not None
        assert user.username == 'newuser'
        assert user.email == 'new@example.com'
        assert user.full_name == 'New User'
        assert user.is_active is True
        assert user.is_admin is False
        assert user.created_at is not None
    
    def test_password_hashing(self, user):
        """Test password hashing."""
        user.set_password('newpassword')
        assert user.password_hash != 'newpassword'
        assert check_password_hash(user.password_hash, 'newpassword')
        assert user.check_password('newpassword')
        assert not user.check_password('wrongpassword')
    
    def test_user_repr(self, user):
        """Test user string representation."""
        assert repr(user) == '<User testuser>'
    
    def test_user_to_dict(self, user):
        """Test user dictionary conversion."""
        user_dict = user.to_dict()
        assert 'username' in user_dict
        assert 'email' in user_dict
        assert 'password_hash' not in user_dict  # Should be excluded
    
    def test_user_relationships(self, user, post):
        """Test user relationships."""
        assert post in user.posts
        assert user.posts.count() == 1

class TestPost:
    """Test Post model."""
    
    def test_post_creation(self, db_session, user, category):
        """Test post creation."""
        post = Post(
            title='New Post',
            content='New post content',
            slug='new-post',
            status='draft',
            user_id=user.id,
            category_id=category.id
        )
        post.save()
        
        assert post.id is not None
        assert post.title == 'New Post'
        assert post.author == user
        assert post.category == category
        assert not post.is_published
    
    def test_published_status(self, post):
        """Test post published status."""
        assert post.is_published  # Published with published_at
        
        post.status = 'draft'
        assert not post.is_published

View Testing

# tests/test_views.py
import pytest
from flask import url_for
from app.models import User

class TestMainViews:
    """Test main application views."""
    
    def test_home_page(self, client):
        """Test home page."""
        response = client.get('/')
        assert response.status_code == 200
        assert b'Welcome' in response.data
    
    def test_about_page(self, client):
        """Test about page."""
        response = client.get('/about')
        assert response.status_code == 200

class TestUserViews:
    """Test user-related views."""
    
    def test_user_list(self, client, user):
        """Test user list page."""
        response = client.get('/users/')
        assert response.status_code == 200
        assert user.username.encode() in response.data
    
    def test_user_detail(self, client, user):
        """Test user detail page."""
        response = client.get(f'/users/{user.id}')
        assert response.status_code == 200
        assert user.username.encode() in response.data
    
    def test_user_create_get(self, client):
        """Test user creation form."""
        response = client.get('/users/create')
        assert response.status_code == 200
        assert b'Create User' in response.data
    
    def test_user_create_post(self, client, db_session):
        """Test user creation submission."""
        data = {
            'username': 'newuser',
            'email': 'new@example.com',
            'first_name': 'New',
            'last_name': 'User',
            'password': 'password123',
            'confirm_password': 'password123'
        }
        response = client.post('/users/create', data=data, follow_redirects=True)
        assert response.status_code == 200
        
        # Check user was created
        user = User.query.filter_by(username='newuser').first()
        assert user is not None
        assert user.email == 'new@example.com'
    
    def test_user_edit(self, client, user):
        """Test user editing."""
        data = {
            'username': user.username,
            'email': 'updated@example.com',
            'first_name': 'Updated',
            'last_name': 'User'
        }
        response = client.post(f'/users/{user.id}/edit', data=data, follow_redirects=True)
        assert response.status_code == 200
        
        # Refresh user from database
        db_session.refresh(user)
        assert user.email == 'updated@example.com'
        assert user.first_name == 'Updated'

Authentication Testing

# tests/test_auth.py
import pytest
from flask import url_for
from app.models import User

class TestAuthentication:
    """Test authentication functionality."""
    
    def test_login_page(self, client):
        """Test login page access."""
        response = client.get('/auth/login')
        assert response.status_code == 200
        assert b'Login' in response.data
    
    def test_valid_login(self, client, user):
        """Test valid user login."""
        data = {
            'username': user.username,
            'password': 'testpass123'
        }
        response = client.post('/auth/login', data=data, follow_redirects=True)
        assert response.status_code == 200
        assert b'Welcome' in response.data
    
    def test_invalid_login(self, client, user):
        """Test invalid login credentials."""
        data = {
            'username': user.username,
            'password': 'wrongpassword'
        }
        response = client.post('/auth/login', data=data)
        assert response.status_code == 200
        assert b'Invalid' in response.data
    
    def test_logout(self, client, user):
        """Test user logout."""
        # Login first
        with client.session_transaction() as sess:
            sess['_user_id'] = str(user.id)
        
        response = client.get('/auth/logout', follow_redirects=True)
        assert response.status_code == 200
    
    def test_register_page(self, client):
        """Test registration page."""
        response = client.get('/auth/register')
        assert response.status_code == 200
        assert b'Register' in response.data
    
    def test_valid_registration(self, client, db_session):
        """Test valid user registration."""
        data = {
            'username': 'newuser',
            'email': 'new@example.com',
            'first_name': 'New',
            'last_name': 'User',
            'password': 'password123',
            'confirm_password': 'password123'
        }
        response = client.post('/auth/register', data=data, follow_redirects=True)
        assert response.status_code == 200
        
        # Check user was created
        user = User.query.filter_by(username='newuser').first()
        assert user is not None

API Testing

# tests/test_api.py
import pytest
import json
from flask import url_for

class TestUserAPI:
    """Test User API endpoints."""
    
    def test_get_users(self, client, user):
        """Test GET /api/users."""
        response = client.get('/api/users')
        assert response.status_code == 200
        
        data = json.loads(response.data)
        assert 'users' in data
        assert len(data['users']) >= 1
    
    def test_get_user(self, client, user):
        """Test GET /api/users/<id>."""
        response = client.get(f'/api/users/{user.id}')
        assert response.status_code == 200
        
        data = json.loads(response.data)
        assert data['username'] == user.username
        assert data['email'] == user.email
    
    def test_create_user(self, client, db_session):
        """Test POST /api/users."""
        user_data = {
            'username': 'apiuser',
            'email': 'api@example.com',
            'first_name': 'API',
            'last_name': 'User',
            'password': 'password123'
        }
        
        response = client.post(
            '/api/users',
            data=json.dumps(user_data),
            content_type='application/json'
        )
        
        assert response.status_code == 201
        data = json.loads(response.data)
        assert data['username'] == 'apiuser'
    
    def test_update_user(self, client, user, auth_headers):
        """Test PUT /api/users/<id>."""
        update_data = {
            'email': 'updated@example.com',
            'first_name': 'Updated'
        }
        
        response = client.put(
            f'/api/users/{user.id}',
            data=json.dumps(update_data),
            content_type='application/json',
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = json.loads(response.data)
        assert data['email'] == 'updated@example.com'
    
    def test_delete_user(self, client, user, auth_headers):
        """Test DELETE /api/users/<id>."""
        response = client.delete(
            f'/api/users/{user.id}',
            headers=auth_headers
        )
        
        assert response.status_code == 204

Test Utilities

# tests/utils.py
import json
from flask import url_for

def login_user(client, username, password):
    """Helper to login user in tests."""
    return client.post('/auth/login', data={
        'username': username,
        'password': password
    }, follow_redirects=True)

def logout_user(client):
    """Helper to logout user in tests."""
    return client.get('/auth/logout', follow_redirects=True)

def assert_json_response(response, expected_status=200):
    """Assert JSON response format and status."""
    assert response.status_code == expected_status
    assert response.content_type == 'application/json'
    return json.loads(response.data)

def create_test_data(db_session):
    """Create common test data."""
    from app.models import User, Category, Post
    
    # Create test users
    users = []
    for i in range(3):
        user = User(
            username=f'user{i}',
            email=f'user{i}@example.com',
            first_name=f'User{i}',
            last_name='Test'
        )
        user.set_password('password123')
        user.save()
        users.append(user)
    
    return {'users': users}

Performance Testing

# tests/test_performance.py
import pytest
import time
from app.models import User

@pytest.mark.slow
class TestPerformance:
    """Test application performance."""
    
    def test_user_query_performance(self, db_session):
        """Test user query performance."""
        # Create multiple users
        users = []
        for i in range(100):
            user = User(
                username=f'perfuser{i}',
                email=f'perf{i}@example.com',
                first_name=f'Perf{i}',
                last_name='User'
            )
            users.append(user)
        
        db_session.bulk_save_objects(users)
        db_session.commit()
        
        # Test query performance
        start_time = time.time()
        result = User.query.all()
        end_time = time.time()
        
        assert len(result) >= 100
        assert (end_time - start_time) < 0.1  # Should complete in under 100ms
    
    def test_endpoint_response_time(self, client):
        """Test endpoint response time."""
        start_time = time.time()
        response = client.get('/')
        end_time = time.time()
        
        assert response.status_code == 200
        assert (end_time - start_time) < 0.5  # Should respond in under 500ms