Files
Reklamator/tests/integration/test_dashboard_access_flow.py
gurixandClaude adbfd23c26 Implement Phase 5: Product Owner Dashboard (User Story 3)
Add complete dashboard functionality for product owners and administrators to view, filter, search, and manage feedback submissions following Test-First Discipline.

Tests (T093-T105):
- Add 12 contract tests for dashboard routes (authentication, listing, filtering, search, detail view, status updates, attachment downloads, access control)
- Add 2 integration tests for complete dashboard workflow and access control enforcement
- All tests written first and verified to fail before implementation

Services (T111-T118):
- Enhance FeedbackStorageService with load_feedback_list() for pagination, filtering, searching, and sorting
- Add load_feedback_detail() to load complete feedback with attachments and analysis
- Add update_feedback_status_by_id() for status management
- Add get_attachment_path() with path traversal prevention

Routes (T119-T134):
- Implement GET /dashboard with filters, search, and pagination (50 items/page)
- Implement GET /feedback/<id> detail view with role-based access control
- Implement POST /feedback/<id>/status for status updates
- Implement GET /feedback/<id>/attachment/<filename> for secure file downloads
- Add access control helpers (administrators see all products, owners see only assigned)

Templates (T135-T136):
- Create dashboard/list.html with filter form, search, and pagination
- Create dashboard/detail.html with status update form and attachment links
- Create error_403.html for access denied
- Create error_404.html for not found

Integration & Bug Fixes:
- Update auth routes to remove /auth prefix and redirect to dashboard after login
- Update Feedback.VALID_STATUSES to include dashboard statuses (in_progress, resolved, closed)
- Register error handlers for 403 and 404 in app factory
- Fix test fixtures to use correct users.yaml format and User.hash_password()

Test Results: 39 passed, 1 skipped (all Phase 5 tests passing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 20:11:16 +02:00

248 lines
8.7 KiB
Python

"""Integration test for complete dashboard access flow"""
import pytest
import os
import yaml
from app.models.user import User
@pytest.fixture
def test_product_with_feedback(app):
"""Create a test product with multiple feedback items"""
with app.app_context():
# Create test product directory and config
product_dir = os.path.join(app.config['DATA_DIR'], 'products', 'dashboard-test-product')
os.makedirs(product_dir, exist_ok=True)
# Create product config
config_file = os.path.join(product_dir, 'config.yaml')
config_data = {
'product_id': 'dashboard-test-product',
'name': 'Dashboard Test Product',
'submission_url_slug': 'dashboard-test-product',
'owner_language': 'en',
'assigned_owner_ids': ['usr_dashboard_owner'],
'status': 'active'
}
with open(config_file, 'w') as f:
yaml.dump(config_data, f)
# Create feedback directory
feedback_dir = os.path.join(product_dir, 'feedback')
os.makedirs(feedback_dir, exist_ok=True)
# Create multiple test feedback items
feedback_items = [
{
'id': 'feedback-bug-001',
'category': 'bug',
'status': 'new',
'content': 'Found a critical bug in the login system',
'has_attachments': True,
'attachment': 'bug-screenshot.png'
},
{
'id': 'feedback-feature-001',
'category': 'feature_request',
'status': 'new',
'content': 'Please add dark mode to the application',
'has_attachments': False,
'attachment': None
},
{
'id': 'feedback-bug-002',
'category': 'bug',
'status': 'in_progress',
'content': 'Error when uploading large files',
'has_attachments': True,
'attachment': 'error.log'
}
]
for item in feedback_items:
feedback_path = os.path.join(feedback_dir, item['id'])
os.makedirs(feedback_path, exist_ok=True)
# Create metadata
metadata = {
'feedback_id': item['id'],
'product_id': 'dashboard-test-product',
'status': item['status'],
'submitted_at': '2025-10-16T10:00:00Z',
'has_attachments': item['has_attachments'],
'attachment_count': 1 if item['has_attachments'] else 0,
'category': item['category'],
'original_language': 'en'
}
with open(os.path.join(feedback_path, 'metadata.yaml'), 'w') as f:
yaml.dump(metadata, f)
# Create content
with open(os.path.join(feedback_path, 'content.txt'), 'w') as f:
f.write(item['content'])
# Create attachment if needed
if item['has_attachments']:
attachments_dir = os.path.join(feedback_path, 'attachments')
os.makedirs(attachments_dir, exist_ok=True)
with open(os.path.join(attachments_dir, item['attachment']), 'w') as f:
f.write(f'Attachment content for {item["id"]}')
yield {
'product_id': 'dashboard-test-product',
'feedback_items': feedback_items
}
@pytest.fixture
def dashboard_test_users(app):
"""Create test users for dashboard testing"""
users_file = os.path.join(app.config['DATA_DIR'], 'users.yaml')
# User model expects format: {'users': {user_id: user_data}}
users_data = {
'users': {
'usr_dashboard_owner': {
'user_id': 'usr_dashboard_owner',
'username': 'dashboard_owner',
'email': 'dashboard@example.com',
'password_hash': User.hash_password('dashboard123'),
'role': 'product_owner',
'product_ids': ['dashboard-test-product'],
'is_active': True
}
}
}
with open(users_file, 'w') as f:
yaml.dump(users_data, f)
yield users_data
@pytest.mark.integration
def test_complete_dashboard_access_flow(client, app, test_product_with_feedback, dashboard_test_users):
"""T105: Integration test for complete dashboard access flow
Test the entire product owner journey:
1. Owner logs in with credentials
2. Owner views dashboard with feedback list
3. Owner filters feedback by category
4. Owner searches for specific feedback
5. Owner views feedback detail
6. Owner downloads attachment
7. Owner updates feedback status
8. Owner logs out
"""
# Step 1: Login as product owner
login_response = client.post('/login', data={
'username': 'dashboard_owner',
'password': 'dashboard123'
}, follow_redirects=False)
assert login_response.status_code == 302 # Redirect after successful login
# Step 2: View dashboard with feedback list
dashboard_response = client.get('/dashboard')
assert dashboard_response.status_code == 200
assert b'feedback' in dashboard_response.data.lower() or b'dashboard' in dashboard_response.data.lower()
# Verify feedback items are shown
# (At least should show some feedback indicators)
# Step 3: Filter feedback by category (bug)
filter_response = client.get('/dashboard?category=bug')
assert filter_response.status_code == 200
# Step 4: Search for specific feedback
search_response = client.get('/dashboard?search=login')
assert search_response.status_code == 200
# Step 5: View feedback detail
feedback_id = test_product_with_feedback['feedback_items'][0]['id']
detail_response = client.get(f'/feedback/{feedback_id}')
assert detail_response.status_code == 200
# Should show the feedback content
assert b'Found a critical bug in the login system' in detail_response.data or b'feedback' in detail_response.data.lower()
# Step 6: Download attachment
attachment_response = client.get(f'/feedback/{feedback_id}/attachment/bug-screenshot.png')
assert attachment_response.status_code == 200
assert b'Attachment content' in attachment_response.data
# Should have download headers
assert 'Content-Disposition' in attachment_response.headers or 'content-disposition' in attachment_response.headers
# Step 7: Update feedback status
status_update_response = client.post(f'/feedback/{feedback_id}/status', data={
'status': 'in_progress'
}, follow_redirects=False)
assert status_update_response.status_code in [200, 302]
# Verify status was updated in filesystem
with app.app_context():
data_dir = app.config['DATA_DIR']
metadata_file = os.path.join(
data_dir,
'products',
'dashboard-test-product',
'feedback',
feedback_id,
'metadata.yaml'
)
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f)
assert metadata['status'] == 'in_progress'
# Step 8: Logout
logout_response = client.get('/logout', follow_redirects=False)
assert logout_response.status_code == 302
# Verify user is logged out (accessing dashboard should redirect to login)
protected_response = client.get('/dashboard', follow_redirects=False)
assert protected_response.status_code == 302
assert '/login' in protected_response.location
@pytest.mark.integration
def test_dashboard_access_control_enforcement(client, app, test_product_with_feedback, dashboard_test_users):
"""Integration test for access control - owner can only see assigned products"""
# Create another product owner with different product access
users_file = os.path.join(app.config['DATA_DIR'], 'users.yaml')
with open(users_file, 'r') as f:
users_data = yaml.safe_load(f)
# Add new user to the users dict
users_data['users']['usr_other_owner'] = {
'user_id': 'usr_other_owner',
'username': 'other_owner',
'email': 'other@example.com',
'password_hash': User.hash_password('other123'),
'role': 'product_owner',
'product_ids': ['different-product'],
'is_active': True
}
with open(users_file, 'w') as f:
yaml.dump(users_data, f)
# Login as owner without access to dashboard-test-product
client.post('/login', data={
'username': 'other_owner',
'password': 'other123'
})
# Try to access feedback from product they don't own
feedback_id = test_product_with_feedback['feedback_items'][0]['id']
response = client.get(f'/feedback/{feedback_id}')
# Should be denied access (403 Forbidden)
assert response.status_code == 403