Files
Reklamator/tests/integration/test_feedback_submission_flow.py
T
gurixandClaude 1bb117fd98 Implement Phase 4: AI-Powered Feedback Analysis (User Story 2)
Implement automatic AI analysis of feedback submissions using Claude API,
including language detection, categorization, summarization, and translation
to product owner's preferred language.

Tasks Completed (T065-T092):
- T065-T069: Unit tests for AI analyzer (5 tests)
- T070: Integration test for full AI analysis workflow
- T071: Created AIAnalyzer abstract base class interface
- T072: Added AnalysisResult dataclass to feedback model
- T073: Implemented ClaudeAnalyzer with Anthropic SDK
- T074: Integrated Claude API with 45s timeout
- T075: Designed single-call analysis prompt
- T076: Language detection implementation
- T077: Category extraction with validation
- T078: Summary generation (1-2 sentences)
- T079: Translation extraction
- T080: Timeout handling for Claude API
- T081: API error handling with proper exceptions
- T082: Analysis storage to analysis.md file
- T083: Formatted markdown output for analysis
- T084: Background analysis trigger on submission
- T085: Non-blocking async analysis via threading
- T086: Status update to 'analyzing' before analysis
- T087: Status update to 'analyzed' on success
- T088: Status update to 'analysis_failed' on error
- T089: Metadata update with category and language
- T090: Environment configuration for ANTHROPIC_API_KEY
- T091: Verification that original content.txt preserved (FR-016)
- T092: Verification that images not analyzed via OCR (FR-021)

Features:
- Abstract AIAnalyzer interface for multiple AI providers
- ClaudeAnalyzer implementation using Anthropic API
- Background threading for non-blocking analysis
- Flask app context management in background threads
- Comprehensive error handling and status tracking
- Original content preservation (FR-016 compliance)
- Image storage without OCR (FR-021 compliance)

Testing:
- 5 unit tests for AI analyzer components
- 2 integration tests for full analysis workflow
- All 46 tests passing (1 skipped)
- Mock-based testing to avoid API calls

Files Changed:
- app/models/feedback.py: Added AnalysisResult dataclass
- app/routes/submission.py: Background analysis integration
- app/services/ai_analyzer.py: NEW - AI analysis service
- app/services/feedback_storage.py: Analysis storage methods
- tests/unit/test_ai_analyzer.py: NEW - Unit tests (5 tests)
- tests/integration/test_ai_analysis_flow.py: NEW - Integration tests (2 tests)
- tests/integration/test_feedback_submission_flow.py: Threading mock added

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

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

170 lines
6.0 KiB
Python

"""Integration test for complete feedback submission flow"""
import pytest
import io
import os
import yaml
from unittest.mock import patch
@pytest.fixture
def test_product(app):
"""Create a test product"""
with app.app_context():
# Create test product directory and config
product_dir = os.path.join(app.config['DATA_DIR'], 'products', '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': 'test-product',
'name': 'Test Product',
'submission_url_slug': 'test-product',
'owner_language': 'en',
'assigned_owner_ids': ['usr_0001'],
'status': 'active'
}
with open(config_file, 'w') as f:
yaml.dump(config_data, f)
yield 'test-product'
@pytest.mark.integration
def test_complete_feedback_submission_flow(client, app, test_product):
"""T039: Integration test for complete feedback submission flow
Test the entire user journey:
1. User visits submission form
2. User fills in feedback text
3. User attaches files
4. User submits form
5. System validates input
6. System saves feedback to filesystem
7. System displays confirmation
8. Feedback is retrievable from storage
"""
# Step 1: Visit submission form
response = client.get('/submit/test-product')
assert response.status_code == 200
assert b'<form' in response.data
# Step 2-4: Submit feedback with text and files
feedback_text = 'I found a bug in the login page. When I enter my password, it does not accept special characters.'
data = {
'feedback_text': feedback_text,
'files': [
(io.BytesIO(b'PNG fake image data'), 'screenshot.png'),
(io.BytesIO(b'Error log contents\nLine 2\nLine 3'), 'error.log')
]
}
# Mock threading to prevent background analysis (keep original Phase 3 behavior)
with patch('app.routes.submission.threading.Thread'):
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data',
follow_redirects=True)
# Step 7: Verify success confirmation
assert response.status_code == 200
assert b'success' in response.data.lower() or b'thank' in response.data.lower()
# Step 8: Verify feedback was saved to filesystem
with app.app_context():
data_dir = app.config['DATA_DIR']
products_dir = os.path.join(data_dir, 'products', 'test-product', 'feedback')
# Check that feedback directory was created
assert os.path.exists(products_dir)
# Find the created feedback directory (should be UUID-named)
feedback_dirs = [d for d in os.listdir(products_dir)
if os.path.isdir(os.path.join(products_dir, d))]
assert len(feedback_dirs) > 0, "No feedback directory was created"
feedback_dir = os.path.join(products_dir, feedback_dirs[0])
# Verify metadata.yaml exists
metadata_file = os.path.join(feedback_dir, 'metadata.yaml')
assert os.path.exists(metadata_file)
# Verify metadata content
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f)
assert metadata['feedback_id'] == feedback_dirs[0]
assert metadata['product_id'] == 'test-product'
assert metadata['status'] == 'new'
assert 'submitted_at' in metadata
assert metadata.get('has_attachments') == True
assert metadata.get('attachment_count') == 2
# Verify content.txt exists and contains the feedback
content_file = os.path.join(feedback_dir, 'content.txt')
assert os.path.exists(content_file)
with open(content_file, 'r') as f:
saved_content = f.read()
assert feedback_text in saved_content
# Verify attachments directory and files exist
attachments_dir = os.path.join(feedback_dir, 'attachments')
assert os.path.exists(attachments_dir)
attachments = os.listdir(attachments_dir)
assert len(attachments) == 2
# Verify specific attachment files
attachment_names = [a for a in attachments]
assert 'screenshot.png' in attachment_names
assert 'error.log' in attachment_names
# Verify no IP address is stored (FR-055 compliance)
assert 'ip_address' not in metadata
assert 'submitter_ip' not in metadata
@pytest.mark.integration
def test_feedback_submission_without_attachments(client, app, test_product):
"""Integration test for feedback submission with text only (no files)"""
feedback_text = 'Simple text feedback without attachments.'
data = {
'feedback_text': feedback_text
}
response = client.post('/submit/test-product',
data=data,
follow_redirects=True)
assert response.status_code == 200
# Verify feedback was saved
with app.app_context():
data_dir = app.config['DATA_DIR']
products_dir = os.path.join(data_dir, 'products', 'test-product', 'feedback')
feedback_dirs = [d for d in os.listdir(products_dir)
if os.path.isdir(os.path.join(products_dir, d))]
# Find the most recent feedback
feedback_dir = os.path.join(products_dir, feedback_dirs[-1])
# Verify metadata shows no attachments
metadata_file = os.path.join(feedback_dir, 'metadata.yaml')
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f)
assert metadata.get('has_attachments') == False
assert metadata.get('attachment_count') == 0
# Verify attachments directory doesn't exist or is empty
attachments_dir = os.path.join(feedback_dir, 'attachments')
if os.path.exists(attachments_dir):
assert len(os.listdir(attachments_dir)) == 0