import io import re import uuid import requests from PIL import Image from tests.conftest import BASE_URL def _user(prefix="up"): s = requests.Session() name = f"{prefix}_{uuid.uuid4().hex[:10]}" s.post(f"{BASE_URL}/auth/signup", data={ "username": name, "email": f"{name}@test.dev", "password": "secret123", "confirm_password": "secret123", }, allow_redirects=True) return s, name def _session(): s, _ = _user() return s def _png_bytes(): buf = io.BytesIO() Image.new("RGB", (4, 4), (255, 0, 0)).save(buf, "PNG") return buf.getvalue() def _upload(s, name="a.png"): r = s.post(f"{BASE_URL}/uploads/upload", files={"file": (name, _png_bytes(), "image/png")}) assert r.status_code == 201, r.text return r.json()["uid"] def test_upload_allowed_png(app_server): s = _session() r = s.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.png", _png_bytes(), "image/png")}) assert r.status_code == 201, r.text assert "url" in r.json() def test_upload_rejects_svg(app_server): s = _session() r = s.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.svg", b"", "image/svg+xml")}) assert r.status_code == 415 def test_upload_rejects_html(app_server): s = _session() r = s.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.html", b"", "text/html")}) assert r.status_code == 415 def test_upload_rejects_oversize(app_server): s = _session() big = b"\x89PNG\r\n" + b"\x00" * (11 * 1024 * 1024) r = s.post(f"{BASE_URL}/uploads/upload", files={"file": ("big.png", big, "image/png")}) assert r.status_code == 413 def test_uploaded_file_served_as_attachment(app_server): s = _session() r = s.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.png", _png_bytes(), "image/png")}) url = r.json()["url"] served = s.get(f"{BASE_URL}{url}") assert served.status_code == 200 assert served.headers.get("Content-Disposition") == "attachment" def test_upload_requires_login(app_server): r = requests.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.png", _png_bytes(), "image/png")}, allow_redirects=False) assert r.status_code == 401 def test_delete_own_allowed_other_user_forbidden(app_server): alice = _session() uid = alice.post(f"{BASE_URL}/uploads/upload", files={"file": ("x.png", _png_bytes(), "image/png")}).json()["uid"] bob = _session() assert bob.delete(f"{BASE_URL}/uploads/delete/{uid}").status_code == 403 assert alice.delete(f"{BASE_URL}/uploads/delete/{uid}").status_code == 200 def test_post_links_multiple_attachments(app_server): s = _session() u1, u2 = _upload(s), _upload(s) r = s.post(f"{BASE_URL}/posts/create", data={ "content": "Post body with two attachments here", "title": "attach post", "topic": "random", "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert "/posts/" in r.url, r.url assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed on the post" def test_comment_links_multiple_attachments(app_server): s = _session() post = s.post(f"{BASE_URL}/posts/create", data={ "content": "Host post for comment attachments", "title": "host", "topic": "random", }, allow_redirects=True) target_uid = re.search(r'name="target_uid"\s+value="([^"]+)"', post.text).group(1) u1, u2 = _upload(s), _upload(s) r = s.post(f"{BASE_URL}/comments/create", data={ "content": "Comment with attachments", "target_uid": target_uid, "target_type": "post", "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed on the comment" def test_project_links_multiple_attachments(app_server): s = _session() u1, u2 = _upload(s), _upload(s) r = s.post(f"{BASE_URL}/projects/create", data={ "title": "Attach Project", "description": "Project with attachments", "project_type": "software", "platforms": "linux", "status": "In Development", "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert "/projects/" in r.url, r.url assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed on the project" def test_gist_links_multiple_attachments(app_server): s = _session() u1, u2 = _upload(s), _upload(s) r = s.post(f"{BASE_URL}/gists/create", data={ "title": "Attach Gist", "description": "Gist with attachments", "source_code": "print('hi')", "language": "python", "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert "/gists/" in r.url, r.url assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed on the gist" def test_bug_links_multiple_attachments(app_server): s = _session() u1, u2 = _upload(s), _upload(s) r = s.post(f"{BASE_URL}/bugs/create", data={ "title": "Attach Bug", "description": "Bug report with attachments", "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed on the bug" def test_message_links_multiple_attachments(app_server): alice = _session() bob, bob_name = _user("bob") found = alice.get(f"{BASE_URL}/messages/search", params={"q": bob_name}).json()["results"] bob_uid = found[0]["uid"] u1, u2 = _upload(alice), _upload(alice) r = alice.post(f"{BASE_URL}/messages/send", data={ "content": "Message with attachments", "receiver_uid": bob_uid, "attachment_uids": f"{u1},{u2}", }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert u1 in r.text and u2 in r.text, "both attachments must be linked and displayed in the conversation" def test_single_attachment_links_to_post(app_server): s = _session() u1 = _upload(s) r = s.post(f"{BASE_URL}/posts/create", data={ "content": "Post body with one attachment", "title": "one", "topic": "random", "attachment_uids": u1, }, allow_redirects=True) assert r.status_code == 200, r.text[:300] assert u1 in r.text, "single attachment must be linked and displayed"