Improvements: 1. Added 15 comprehensive User model unit tests 2. Created TECHNICAL_DEBT.md to track test gaps and known issues 3. Updated constitution with Bug Fix Protocol (v1.1.0) User Model Tests (tests/unit/test_user_model.py): - Test user creation and properties - Test is_active property (Flask-Login integration) - Test password hashing and verification - Test serialization (to_dict/from_dict) - Test CRUD operations (create, load, update, delete) - Test error conditions (duplicate username, invalid role) Technical Debt Documentation (docs/TECHNICAL_DEBT.md): - Missing authentication route tests - CSRF testing disabled by design - Dashboard routes not implemented (planned) - ClamAV integration not fully tested - All 3 MVP bugs documented with lessons learned Constitution Amendment (v1.0.0 → v1.1.0): - Added Bug Fix Protocol requiring: - Write failing test before fix - Document in TECHNICAL_DEBT.md - Commit test and fix together - Lessons learned capture Test Coverage Improvement: - Before: 10 tests (8 contract + 2 integration) - After: 26 tests (8 contract + 2 integration + 15 unit + 1 skipped) - User model: 0% → 100% coverage Rationale: Bugs found during manual testing revealed insufficient test coverage. This addresses the gap and establishes process to prevent future coverage deficiencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
348 lines
9.9 KiB
Python
348 lines
9.9 KiB
Python
"""Unit tests for User model"""
|
|
import pytest
|
|
from app.models.user import User
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_creation(app):
|
|
"""Test User model instantiation"""
|
|
with app.app_context():
|
|
user = User(
|
|
user_id='usr_test',
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password_hash='hash123',
|
|
role='administrator',
|
|
product_ids=['prod_001'],
|
|
is_active=True
|
|
)
|
|
|
|
assert user.user_id == 'usr_test'
|
|
assert user.username == 'testuser'
|
|
assert user.email == 'test@example.com'
|
|
assert user.password_hash == 'hash123'
|
|
assert user.role == 'administrator'
|
|
assert user.product_ids == ['prod_001']
|
|
assert user.is_active == True
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_is_active_property(app):
|
|
"""Test is_active property (Flask-Login integration)
|
|
|
|
Bug: AttributeError: can't set attribute 'is_active'
|
|
Fix: Use private _is_active attribute with property decorator
|
|
"""
|
|
with app.app_context():
|
|
# Test active user
|
|
active_user = User(
|
|
user_id='usr_001',
|
|
username='active',
|
|
email='active@example.com',
|
|
password_hash='hash',
|
|
role='administrator',
|
|
is_active=True
|
|
)
|
|
assert active_user.is_active == True
|
|
|
|
# Test inactive user
|
|
inactive_user = User(
|
|
user_id='usr_002',
|
|
username='inactive',
|
|
email='inactive@example.com',
|
|
password_hash='hash',
|
|
role='administrator',
|
|
is_active=False
|
|
)
|
|
assert inactive_user.is_active == False
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_flask_login_properties(app):
|
|
"""Test Flask-Login required properties"""
|
|
with app.app_context():
|
|
user = User(
|
|
user_id='usr_001',
|
|
username='test',
|
|
email='test@example.com',
|
|
password_hash='hash',
|
|
role='administrator'
|
|
)
|
|
|
|
# Flask-Login required properties
|
|
assert user.is_authenticated == True
|
|
assert user.is_anonymous == False
|
|
assert user.is_active == True
|
|
assert user.get_id() == 'usr_001'
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_password_hashing(app):
|
|
"""Test password hashing with bcrypt"""
|
|
with app.app_context():
|
|
password = 'test_password_123'
|
|
hashed = User.hash_password(password)
|
|
|
|
# Hash should be different from plain password
|
|
assert hashed != password
|
|
|
|
# Hash should be bcrypt format
|
|
assert hashed.startswith('$2b$')
|
|
|
|
# Same password should produce different hashes (salt)
|
|
hashed2 = User.hash_password(password)
|
|
assert hashed != hashed2
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_password_verification(app):
|
|
"""Test password verification"""
|
|
with app.app_context():
|
|
password = 'correct_password'
|
|
wrong_password = 'wrong_password'
|
|
|
|
user = User(
|
|
user_id='usr_001',
|
|
username='test',
|
|
email='test@example.com',
|
|
password_hash=User.hash_password(password),
|
|
role='administrator'
|
|
)
|
|
|
|
# Correct password should verify
|
|
assert user.check_password(password) == True
|
|
|
|
# Wrong password should not verify
|
|
assert user.check_password(wrong_password) == False
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_to_dict(app):
|
|
"""Test user serialization to dictionary"""
|
|
with app.app_context():
|
|
user = User(
|
|
user_id='usr_001',
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password_hash='hash123',
|
|
role='product_owner',
|
|
product_ids=['prod_001', 'prod_002'],
|
|
is_active=True
|
|
)
|
|
|
|
user_dict = user.to_dict()
|
|
|
|
assert user_dict['user_id'] == 'usr_001'
|
|
assert user_dict['username'] == 'testuser'
|
|
assert user_dict['email'] == 'test@example.com'
|
|
assert user_dict['password_hash'] == 'hash123'
|
|
assert user_dict['role'] == 'product_owner'
|
|
assert user_dict['product_ids'] == ['prod_001', 'prod_002']
|
|
assert user_dict['is_active'] == True
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_from_dict(app):
|
|
"""Test user deserialization from dictionary"""
|
|
with app.app_context():
|
|
user_data = {
|
|
'user_id': 'usr_001',
|
|
'username': 'testuser',
|
|
'email': 'test@example.com',
|
|
'password_hash': 'hash123',
|
|
'role': 'administrator',
|
|
'product_ids': ['prod_001'],
|
|
'is_active': True
|
|
}
|
|
|
|
user = User.from_dict(user_data)
|
|
|
|
assert user.user_id == 'usr_001'
|
|
assert user.username == 'testuser'
|
|
assert user.email == 'test@example.com'
|
|
assert user.password_hash == 'hash123'
|
|
assert user.role == 'administrator'
|
|
assert user.product_ids == ['prod_001']
|
|
assert user.is_active == True
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_from_dict_with_defaults(app):
|
|
"""Test user deserialization with missing optional fields"""
|
|
with app.app_context():
|
|
user_data = {
|
|
'user_id': 'usr_001',
|
|
'username': 'testuser',
|
|
'email': 'test@example.com',
|
|
'password_hash': 'hash123',
|
|
'role': 'administrator'
|
|
# Missing product_ids and is_active
|
|
}
|
|
|
|
user = User.from_dict(user_data)
|
|
|
|
assert user.product_ids == [] # Default empty list
|
|
assert user.is_active == True # Default True
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_create(app):
|
|
"""Test user creation with auto-generated ID"""
|
|
with app.app_context():
|
|
user = User.create(
|
|
username='newuser',
|
|
email='new@example.com',
|
|
password='password123',
|
|
role='product_owner',
|
|
product_ids=['prod_001']
|
|
)
|
|
|
|
# User should be created
|
|
assert user.user_id.startswith('usr_')
|
|
assert user.username == 'newuser'
|
|
assert user.email == 'new@example.com'
|
|
assert user.role == 'product_owner'
|
|
assert user.product_ids == ['prod_001']
|
|
assert user.is_active == True
|
|
|
|
# Password should be hashed
|
|
assert user.password_hash != 'password123'
|
|
assert user.check_password('password123') == True
|
|
|
|
# Cleanup
|
|
user.delete()
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_create_duplicate_username(app):
|
|
"""Test user creation with duplicate username raises error"""
|
|
with app.app_context():
|
|
# Create first user
|
|
user1 = User.create(
|
|
username='duplicate',
|
|
email='user1@example.com',
|
|
password='password123',
|
|
role='administrator'
|
|
)
|
|
|
|
# Try to create second user with same username
|
|
with pytest.raises(ValueError, match="Username already exists"):
|
|
User.create(
|
|
username='duplicate',
|
|
email='user2@example.com',
|
|
password='password456',
|
|
role='administrator'
|
|
)
|
|
|
|
# Cleanup
|
|
user1.delete()
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_create_invalid_role(app):
|
|
"""Test user creation with invalid role raises error"""
|
|
with app.app_context():
|
|
with pytest.raises(ValueError, match="Invalid role"):
|
|
User.create(
|
|
username='testuser',
|
|
email='test@example.com',
|
|
password='password123',
|
|
role='invalid_role'
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_save_and_load(app):
|
|
"""Test user persistence (save and load)"""
|
|
with app.app_context():
|
|
# Create and save user
|
|
user = User.create(
|
|
username='persistent',
|
|
email='persist@example.com',
|
|
password='password123',
|
|
role='administrator'
|
|
)
|
|
user_id = user.user_id
|
|
|
|
# Load user from storage
|
|
loaded_user = User.get_by_id(user_id)
|
|
|
|
assert loaded_user is not None
|
|
assert loaded_user.user_id == user_id
|
|
assert loaded_user.username == 'persistent'
|
|
assert loaded_user.email == 'persist@example.com'
|
|
assert loaded_user.role == 'administrator'
|
|
assert loaded_user.check_password('password123') == True
|
|
|
|
# Cleanup
|
|
user.delete()
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_get_by_username(app):
|
|
"""Test loading user by username"""
|
|
with app.app_context():
|
|
# Create user
|
|
user = User.create(
|
|
username='findme',
|
|
email='findme@example.com',
|
|
password='password123',
|
|
role='product_owner'
|
|
)
|
|
|
|
# Find by username
|
|
found_user = User.get_by_username('findme')
|
|
|
|
assert found_user is not None
|
|
assert found_user.username == 'findme'
|
|
assert found_user.email == 'findme@example.com'
|
|
|
|
# Non-existent username
|
|
not_found = User.get_by_username('doesnotexist')
|
|
assert not_found is None
|
|
|
|
# Cleanup
|
|
user.delete()
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_get_all(app):
|
|
"""Test getting all users"""
|
|
with app.app_context():
|
|
# Create multiple users
|
|
user1 = User.create('user1', 'user1@example.com', 'pass1', 'administrator')
|
|
user2 = User.create('user2', 'user2@example.com', 'pass2', 'product_owner')
|
|
|
|
# Get all users
|
|
all_users = User.get_all()
|
|
|
|
# Should include at least our test users
|
|
usernames = [u.username for u in all_users]
|
|
assert 'user1' in usernames
|
|
assert 'user2' in usernames
|
|
|
|
# Cleanup
|
|
user1.delete()
|
|
user2.delete()
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_user_delete(app):
|
|
"""Test user deletion"""
|
|
with app.app_context():
|
|
# Create user
|
|
user = User.create(
|
|
username='deleteme',
|
|
email='delete@example.com',
|
|
password='password123',
|
|
role='administrator'
|
|
)
|
|
user_id = user.user_id
|
|
|
|
# Delete user
|
|
user.delete()
|
|
|
|
# User should no longer exist
|
|
deleted_user = User.get_by_id(user_id)
|
|
assert deleted_user is None
|