Files
Reklamator/tests/contract/test_submission_routes.py
T

210 lines
6.3 KiB
Python
Raw Normal View History

"""Contract tests for submission routes"""
import pytest
import io
import os
import yaml
@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.contract
def test_get_submission_form(client, test_product):
"""T030: Contract test for GET /submit/{product_slug}
Expected: 200 OK with HTML form containing textarea and file inputs
"""
response = client.get('/submit/test-product')
assert response.status_code == 200
assert b'<form' in response.data
assert b'textarea' in response.data or b'<textarea' in response.data
assert b'type="file"' in response.data
@pytest.mark.contract
def test_post_submission_text_only(client, test_product):
"""T031: Contract test for POST /submit/{product_slug} with text only
Expected: 200/302 success with confirmation message
"""
data = {
'feedback_text': 'This is my feedback about the product.'
}
response = client.post('/submit/test-product', data=data, follow_redirects=True)
assert response.status_code == 200
assert b'success' in response.data.lower() or b'thank' in response.data.lower()
@pytest.mark.contract
def test_post_submission_files_only(client, test_product):
"""T032: Contract test for POST /submit/{product_slug} with files only
Expected: 200/302 success with confirmation message
"""
data = {
'files': [
(io.BytesIO(b'test file content'), 'test.txt')
]
}
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data',
follow_redirects=True)
assert response.status_code == 200
assert b'success' in response.data.lower() or b'thank' in response.data.lower()
@pytest.mark.contract
def test_post_submission_text_and_files(client, test_product):
"""T033: Contract test for POST /submit/{product_slug} with text and files
Expected: 200/302 success with confirmation message
"""
data = {
'feedback_text': 'Here is my feedback with attachments.',
'files': [
(io.BytesIO(b'screenshot content'), 'screenshot.png'),
(io.BytesIO(b'log file content'), 'error.log')
]
}
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data',
follow_redirects=True)
assert response.status_code == 200
assert b'success' in response.data.lower() or b'thank' in response.data.lower()
@pytest.mark.contract
def test_empty_submission_rejected(client, test_product):
"""T034: Contract test for empty submission rejection (400)
Expected: 400 Bad Request - must provide either text or files
"""
data = {
'feedback_text': ''
}
response = client.post('/submit/test-product', data=data)
assert response.status_code == 400
@pytest.mark.contract
def test_too_many_files_rejected(client, test_product):
"""T035: Contract test for >3 files rejection (400)
Expected: 400 Bad Request - maximum 3 files allowed
"""
data = {
'files': [
(io.BytesIO(b'file1'), 'file1.txt'),
(io.BytesIO(b'file2'), 'file2.txt'),
(io.BytesIO(b'file3'), 'file3.txt'),
(io.BytesIO(b'file4'), 'file4.txt')
]
}
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data')
assert response.status_code == 400
assert b'maximum' in response.data.lower() or b'3' in response.data
@pytest.mark.contract
def test_large_file_rejected(client, test_product):
"""T036: Contract test for >10MB file rejection (413)
Expected: 413 Request Entity Too Large
"""
# Create a file larger than 10MB
large_content = b'x' * (11 * 1024 * 1024) # 11MB
data = {
'files': [
(io.BytesIO(large_content), 'large.txt')
]
}
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data')
# Flask will reject this with 413 due to MAX_CONTENT_LENGTH
assert response.status_code == 413
@pytest.mark.contract
def test_unsupported_file_type_rejected(client, test_product):
"""T037: Contract test for unsupported file type rejection (400)
Expected: 400 Bad Request - file type not allowed
"""
data = {
'files': [
(io.BytesIO(b'#!/bin/bash\necho malicious'), 'script.sh')
]
}
response = client.post('/submit/test-product',
data=data,
content_type='multipart/form-data')
assert response.status_code == 400
assert b'not allowed' in response.data.lower() or b'type' in response.data.lower()
@pytest.mark.contract
def test_rate_limiting(client, test_product, app):
"""T038: Contract test for rate limiting (429 after 10 submissions)
Expected: 429 Too Many Requests after exceeding rate limit
"""
# Skip if rate limiting is disabled
if not app.config.get('RATELIMIT_ENABLED'):
pytest.skip('Rate limiting disabled in test config')
# Make 10 successful submissions (the limit)
for i in range(10):
data = {'feedback_text': f'Feedback {i}'}
response = client.post('/submit/test-product', data=data)
# Should succeed (200 or 302)
assert response.status_code in [200, 302]
# 11th submission should be rate limited
data = {'feedback_text': 'This should be rate limited'}
response = client.post('/submit/test-product', data=data)
assert response.status_code == 429