Files
flaskpaste/tests/test_api.py
Username 70d9a7f9f7
All checks were successful
CI / Security Scan (push) Successful in 20s
CI / Lint & Format (push) Successful in 23s
CI / Advanced Security Tests (push) Successful in 16s
CI / Memory Leak Check (push) Successful in 20s
CI / Security Tests (push) Successful in 26s
CI / Unit Tests (push) Successful in 34s
CI / Fuzz Testing (push) Successful in 25s
CI / SBOM Generation (push) Successful in 19s
CI / Build & Push Image (push) Successful in 17s
CI / Harbor Vulnerability Scan (push) Successful in 33s
tests: remove name field assertion from index test
2026-01-21 10:18:01 +01:00

290 lines
10 KiB
Python

"""Tests for FlaskPaste API endpoints."""
import json
from app.config import VERSION
class TestIndex:
"""Tests for GET / endpoint."""
def test_get_api_info(self, client):
"""GET / returns API information."""
response = client.get("/")
assert response.status_code == 200
data = json.loads(response.data)
assert data["version"] == VERSION
assert "endpoints" in data
assert "usage" in data
def test_api_info_contains_endpoints(self, client):
"""API info lists all endpoints."""
response = client.get("/")
data = json.loads(response.data)
endpoints = data["endpoints"]
assert "GET /" in endpoints
assert "POST /" in endpoints
assert "GET /<id>" in endpoints
assert "GET /<id>/raw" in endpoints
assert "DELETE /<id>" in endpoints
class TestHealth:
"""Tests for GET /health endpoint."""
def test_health_endpoint_returns_ok(self, client):
"""Health endpoint returns healthy status."""
response = client.get("/health")
assert response.status_code == 200
data = json.loads(response.data)
assert data["status"] == "healthy"
assert data["database"] == "ok"
def test_health_endpoint_json_response(self, client):
"""Health endpoint returns JSON content type."""
response = client.get("/health")
assert "application/json" in response.content_type
class TestCreatePaste:
"""Tests for POST / endpoint."""
def test_create_paste_raw(self, client, sample_text):
"""Create paste with raw text body."""
response = client.post(
"/",
data=sample_text,
content_type="text/plain",
)
assert response.status_code == 201
data = json.loads(response.data)
assert "id" in data
assert len(data["id"]) == 12
assert data["mime_type"] == "text/plain"
assert "url" in data
assert "raw" in data
def test_create_paste_json(self, client, sample_json):
"""Create paste with JSON body."""
response = client.post(
"/",
data=json.dumps(sample_json),
content_type="application/json",
)
assert response.status_code == 201
data = json.loads(response.data)
assert "id" in data
assert data["mime_type"] == "text/plain"
def test_create_paste_binary(self, client, png_bytes):
"""Create paste with binary content returns octet-stream (magic detection disabled)."""
response = client.post(
"/",
data=png_bytes,
content_type="application/octet-stream",
)
assert response.status_code == 201
data = json.loads(response.data)
# Magic byte detection disabled - binary content is octet-stream
assert data["mime_type"] == "application/octet-stream"
def test_create_paste_empty_fails(self, client):
"""Create paste with empty content fails."""
response = client.post("/", data="")
assert response.status_code == 400
data = json.loads(response.data)
assert "error" in data
def test_create_paste_with_auth(self, client, sample_text, auth_header):
"""Create paste with authentication includes owner."""
response = client.post(
"/",
data=sample_text,
content_type="text/plain",
headers=auth_header,
)
assert response.status_code == 201
data = json.loads(response.data)
assert "owner" in data
assert data["owner"] == "a" * 40
def test_create_paste_invalid_auth_ignored(self, client, sample_text):
"""Invalid auth header is ignored (treated as anonymous)."""
response = client.post(
"/",
data=sample_text,
content_type="text/plain",
headers={"X-SSL-Client-SHA1": "invalid"},
)
assert response.status_code == 201
data = json.loads(response.data)
assert "owner" not in data
class TestGetPaste:
"""Tests for GET /<id> endpoint."""
def test_get_paste_metadata(self, client, sample_text):
"""Get paste returns metadata."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.get(f"/{paste_id}")
assert response.status_code == 200
data = json.loads(response.data)
assert data["id"] == paste_id
assert data["mime_type"] == "text/plain"
assert data["size"] == len(sample_text)
assert "created_at" in data
assert "raw" in data
def test_get_paste_not_found(self, client):
"""Get nonexistent paste returns 404."""
response = client.get("/abcd12345678")
assert response.status_code == 404
data = json.loads(response.data)
assert "error" in data
def test_get_paste_invalid_id(self, client):
"""Get paste with invalid ID returns 400."""
response = client.get("/invalid!")
assert response.status_code == 400
def test_get_paste_wrong_length_id(self, client):
"""Get paste with wrong length ID returns 400."""
response = client.get("/abc")
assert response.status_code == 400
def test_head_paste_metadata(self, client, sample_text):
"""HEAD returns headers without body."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.head(f"/{paste_id}")
assert response.status_code == 200
assert response.content_type == "application/json"
assert response.data == b"" # No body for HEAD
def test_head_paste_not_found(self, client):
"""HEAD on nonexistent paste returns 404."""
response = client.head("/abcd12345678")
assert response.status_code == 404
class TestGetPasteRaw:
"""Tests for GET /<id>/raw endpoint."""
def test_get_paste_raw_text(self, client, sample_text):
"""Get raw paste returns content with correct MIME type."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.get(f"/{paste_id}/raw")
assert response.status_code == 200
assert response.data.decode("utf-8") == sample_text
assert response.content_type == "text/plain; charset=utf-8"
def test_get_paste_raw_binary(self, client, png_bytes):
"""Get raw binary paste returns correct content."""
create = client.post(
"/",
data=png_bytes,
content_type="application/octet-stream",
)
paste_id = json.loads(create.data)["id"]
response = client.get(f"/{paste_id}/raw")
assert response.status_code == 200
assert response.data == png_bytes
# Magic byte detection disabled - binary served as octet-stream
assert response.content_type == "application/octet-stream"
def test_get_paste_raw_not_found(self, client):
"""Get raw nonexistent paste returns 404."""
response = client.get("/abcd12345678/raw")
assert response.status_code == 404
def test_get_paste_raw_inline_disposition(self, client, sample_text):
"""Text and image pastes have inline disposition."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.get(f"/{paste_id}/raw")
assert response.headers.get("Content-Disposition") == "inline"
def test_head_paste_raw(self, client, sample_text):
"""HEAD on raw returns content-type without body."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.head(f"/{paste_id}/raw")
assert response.status_code == 200
assert response.content_type == "text/plain; charset=utf-8"
assert response.data == b"" # No body for HEAD
class TestDeletePaste:
"""Tests for DELETE /<id> endpoint."""
def test_delete_paste_success(self, client, sample_text, auth_header):
"""Delete owned paste succeeds."""
create = client.post(
"/",
data=sample_text,
content_type="text/plain",
headers=auth_header,
)
paste_id = json.loads(create.data)["id"]
response = client.delete(f"/{paste_id}", headers=auth_header)
assert response.status_code == 200
data = json.loads(response.data)
assert "message" in data
# Verify deletion
get_response = client.get(f"/{paste_id}")
assert get_response.status_code == 404
def test_delete_paste_no_auth(self, client, sample_text):
"""Delete without authentication fails."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.delete(f"/{paste_id}")
assert response.status_code == 401
data = json.loads(response.data)
assert "error" in data
def test_delete_paste_wrong_owner(self, client, sample_text, auth_header, other_auth_header):
"""Delete paste owned by another user fails."""
create = client.post(
"/",
data=sample_text,
content_type="text/plain",
headers=auth_header,
)
paste_id = json.loads(create.data)["id"]
response = client.delete(f"/{paste_id}", headers=other_auth_header)
assert response.status_code == 403
data = json.loads(response.data)
assert "error" in data
def test_delete_anonymous_paste_fails(self, client, sample_text, auth_header):
"""Cannot delete anonymous paste (no owner)."""
create = client.post("/", data=sample_text, content_type="text/plain")
paste_id = json.loads(create.data)["id"]
response = client.delete(f"/{paste_id}", headers=auth_header)
assert response.status_code == 403
def test_delete_paste_not_found(self, client, auth_header):
"""Delete nonexistent paste returns 404."""
response = client.delete("/abcd12345678", headers=auth_header)
assert response.status_code == 404
def test_delete_paste_invalid_id(self, client, auth_header):
"""Delete with invalid ID returns 400."""
response = client.delete("/invalid!", headers=auth_header)
assert response.status_code == 400