"""Tests for paste update endpoint (PUT /).""" import json class TestPasteUpdateEndpoint: """Tests for PUT / endpoint.""" def test_update_requires_auth(self, client, sample_text, auth_header): """Update requires authentication.""" # Create paste create = client.post("/", data=sample_text, content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Try to update without auth response = client.put(f"/{paste_id}", data="updated") assert response.status_code == 401 def test_update_requires_ownership(self, client, sample_text, auth_header, other_auth_header): """Update requires paste ownership.""" # Create paste as user A create = client.post("/", data=sample_text, content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Try to update as user B response = client.put( f"/{paste_id}", data="updated content", content_type="text/plain", headers=other_auth_header, ) assert response.status_code == 403 def test_update_content(self, client, auth_header): """Update paste content.""" # Create paste create = client.post("/", data="original", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Update content response = client.put( f"/{paste_id}", data="updated content", content_type="text/plain", headers=auth_header, ) assert response.status_code == 200 data = json.loads(response.data) assert data["size"] == len("updated content") # Verify content changed raw = client.get(f"/{paste_id}/raw") assert raw.data == b"updated content" def test_update_password_set(self, client, auth_header): """Set password on paste.""" # Create paste without password create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Add password response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Paste-Password": "secret123"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data.get("password_protected") is True # Verify password required raw = client.get(f"/{paste_id}/raw") assert raw.status_code == 401 # Verify correct password works raw = client.get(f"/{paste_id}/raw", headers={"X-Paste-Password": "secret123"}) assert raw.status_code == 200 def test_update_password_remove(self, client, auth_header): """Remove password from paste.""" # Create paste with password create = client.post( "/", data="content", content_type="text/plain", headers={**auth_header, "X-Paste-Password": "secret123"}, ) paste_id = json.loads(create.data)["id"] # Remove password response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Remove-Password": "true"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data.get("password_protected") is not True # Verify no password required raw = client.get(f"/{paste_id}/raw") assert raw.status_code == 200 def test_update_extend_expiry(self, client, auth_header): """Extend paste expiry.""" # Create paste with expiry create = client.post( "/", data="content", content_type="text/plain", headers={**auth_header, "X-Expiry": "3600"}, ) paste_id = json.loads(create.data)["id"] original_expiry = json.loads(create.data)["expires_at"] # Extend expiry response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Extend-Expiry": "7200"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data["expires_at"] == original_expiry + 7200 def test_update_add_expiry(self, client, auth_header): """Add expiry to paste without one.""" # Create paste without expiry create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Add expiry response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Extend-Expiry": "3600"}, ) assert response.status_code == 200 data = json.loads(response.data) assert "expires_at" in data def test_update_burn_after_read_forbidden(self, client, auth_header): """Cannot update burn-after-read pastes.""" # Create burn-after-read paste create = client.post( "/", data="content", content_type="text/plain", headers={**auth_header, "X-Burn-After-Read": "true"}, ) paste_id = json.loads(create.data)["id"] # Try to update response = client.put( f"/{paste_id}", data="updated", content_type="text/plain", headers=auth_header, ) assert response.status_code == 400 data = json.loads(response.data) assert "burn" in data["error"].lower() def test_update_not_found(self, client, auth_header): """Update non-existent paste returns 404.""" response = client.put( "/000000000000", data="updated", content_type="text/plain", headers=auth_header, ) assert response.status_code == 404 def test_update_no_changes(self, client, auth_header): """Update with no changes returns error.""" # Create paste create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Try to update with empty request response = client.put(f"/{paste_id}", data="", headers=auth_header) assert response.status_code == 400 data = json.loads(response.data) assert "no updates" in data["error"].lower() def test_update_combined(self, client, auth_header): """Update content and metadata together.""" # Create paste create = client.post("/", data="original", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Update content and add password response = client.put( f"/{paste_id}", data="new content", content_type="text/plain", headers={**auth_header, "X-Paste-Password": "secret"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data["size"] == len("new content") assert data.get("password_protected") is True class TestPasteUpdateDisplayName: """Tests for display_name update via PUT.""" def test_update_set_display_name(self, client, auth_header): """Set display_name on existing paste.""" create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Display-Name": "my label"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data["display_name"] == "my label" def test_update_change_display_name(self, client, auth_header): """Change existing display_name.""" create = client.post( "/", data="content", content_type="text/plain", headers={**auth_header, "X-Display-Name": "old name"}, ) paste_id = json.loads(create.data)["id"] response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Display-Name": "new name"}, ) assert response.status_code == 200 data = json.loads(response.data) assert data["display_name"] == "new name" def test_update_remove_display_name(self, client, auth_header): """Remove display_name via X-Remove-Display-Name header.""" create = client.post( "/", data="content", content_type="text/plain", headers={**auth_header, "X-Display-Name": "to remove"}, ) paste_id = json.loads(create.data)["id"] response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Remove-Display-Name": "true"}, ) assert response.status_code == 200 data = json.loads(response.data) assert "display_name" not in data def test_update_display_name_too_long(self, client, auth_header): """Reject display_name exceeding 128 chars in update.""" create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Display-Name": "x" * 129}, ) assert response.status_code == 400 def test_update_display_name_control_chars(self, client, auth_header): """Reject display_name with control characters in update.""" create = client.post("/", data="content", content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] response = client.put( f"/{paste_id}", data="", headers={**auth_header, "X-Display-Name": "bad\x01name"}, ) assert response.status_code == 400 class TestPasteUpdatePrivacy: """Privacy-focused tests for paste update.""" def test_cannot_update_anonymous_paste(self, client, sample_text, auth_header): """Cannot update paste without owner.""" # Create anonymous paste create = client.post("/", data=sample_text, content_type="text/plain") paste_id = json.loads(create.data)["id"] # Try to update response = client.put( f"/{paste_id}", data="updated", content_type="text/plain", headers=auth_header, ) assert response.status_code == 403 def test_cannot_update_other_user_paste( self, client, sample_text, auth_header, other_auth_header ): """Cannot update paste owned by another user.""" # Create paste as user A create = client.post("/", data=sample_text, content_type="text/plain", headers=auth_header) paste_id = json.loads(create.data)["id"] # Try to update as user B response = client.put( f"/{paste_id}", data="hijacked", content_type="text/plain", headers=other_auth_header, ) assert response.status_code == 403 # Verify original content unchanged raw = client.get(f"/{paste_id}/raw") assert raw.data == sample_text.encode()