paste-bin/tests/test_app.py
Jarian Cottingham d37e14a2d9
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / docker-build (push) Waiting to run
CI / security (push) Waiting to run
CI / build-result (push) Blocked by required conditions
security: block path traversal, SVG upload, duplicate defs; add tests, docs, license
- Validate paste IDs against ^[a-f0-9]{16}$ before filesystem access
  (blocks read/delete traversal via crafted URLs)
- Remove duplicate ALLOWED_IMAGE_EXTENSIONS that re-allowed SVG
  (XSS via stored SVG); keep magic-byte validation
- Remove duplicate cleanup thread (two background loops were started)
- Unknown expiry keys now default to 1 day instead of never
- request.secure -> request.is_secure (werkzeug 3.x)
- Add gunicorn to requirements (Dockerfile CMD referenced it)
- 11 unit tests, ruff clean, MIT LICENSE, full README (was empty)
2026-08-20 21:26:54 +00:00

120 lines
3.6 KiB
Python

"""Unit tests for paste-bin."""
import io
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
import app as app_module # noqa: E402
@pytest.fixture()
def client(tmp_path, monkeypatch):
app_module.app.config['TESTING'] = True
app_module.app.config['UPLOAD_FOLDER'] = str(tmp_path / 'uploads')
app_module.app.config['STORE_FOLDER'] = str(tmp_path / 'store')
app_module.ensure_dirs()
with app_module.app.test_client() as c:
yield c
def test_index_returns_200(client):
resp = client.get('/')
assert resp.status_code == 200
def test_text_paste_roundtrip(client):
resp = client.post('/paste', data={'paste_type': 'text', 'content': 'hello world', 'expiry': '1d'})
assert resp.status_code == 302
paste_id = resp.headers['Location'].rsplit('/', 1)[-1]
assert len(paste_id) == 16
view = client.get(f'/{paste_id}')
assert view.status_code == 200
assert b'hello world' in view.data
raw = client.get(f'/{paste_id}/raw')
assert raw.status_code == 200
assert raw.data == b'hello world'
dl = client.get(f'/{paste_id}/download')
assert dl.status_code == 200
assert dl.data == b'hello world'
def test_empty_content_rejected(client):
resp = client.post('/paste', data={'paste_type': 'text', 'content': ' ', 'expiry': '1d'})
assert resp.status_code == 400
def test_invalid_paste_type_rejected(client):
resp = client.post('/paste', data={'paste_type': 'bogus', 'content': 'x'})
assert resp.status_code == 400
def test_unknown_paste_404(client):
assert client.get('/' + 'a' * 16).status_code == 404
def test_path_traversal_paste_id_blocked(client, tmp_path):
outside = tmp_path / 'secret.txt'
outside.write_text('top secret')
for evil in ['..%2F..%2Fsecret.txt', '....//secret.txt', 'a' * 14 + '/../../secret.txt']:
resp = client.get('/' + evil)
assert resp.status_code == 404
assert outside.exists()
def test_svg_upload_rejected(client):
png_bytes = b'<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'
resp = client.post(
'/paste',
data={'paste_type': 'image', 'file': (io.BytesIO(png_bytes), 'x.svg', 'image/svg+xml')},
content_type='multipart/form-data',
)
assert resp.status_code == 400
def test_png_magic_validation(client):
fake = b'not-a-real-png'
resp = client.post(
'/paste',
data={'paste_type': 'image', 'file': (io.BytesIO(fake), 'x.png', 'image/png')},
content_type='multipart/form-data',
)
assert resp.status_code == 400
def test_valid_png_upload(client):
png = b'\x89PNG\r\n\x1a\n' + b'\x00' * 32
resp = client.post(
'/paste',
data={'paste_type': 'image', 'file': (io.BytesIO(png), 'pic.png', 'image/png')},
content_type='multipart/form-data',
)
assert resp.status_code == 302
paste_id = resp.headers['Location'].rsplit('/', 1)[-1]
assert client.get(f'/{paste_id}').status_code == 200
def test_expiry_parsing():
assert app_module.parse_expiry('forever') is None
for key in ['1h', '1d', '1w', '1m', 'bogus-key']:
result = app_module.parse_expiry(key)
assert result is not None
from datetime import datetime
datetime.fromisoformat(result)
def test_store_and_load_paste(tmp_path, monkeypatch):
app_module.app.config['STORE_FOLDER'] = str(tmp_path / 'store')
app_module.ensure_dirs()
app_module.store_paste('ab' * 8, {'type': 'text', 'title': 't'})
paste = app_module.load_paste('ab' * 8)
assert paste['title'] == 't'
assert app_module.load_paste('../etc/passwd') is None