phase 10
All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 7s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 7s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 2s
CI/CD Pipeline / Frontend Linting (push) Successful in 15s
CI/CD Pipeline / Nix Flake Check (push) Successful in 41s
CI/CD Pipeline / CI Summary (push) Successful in 1s
All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 7s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 7s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 2s
CI/CD Pipeline / Frontend Linting (push) Successful in 15s
CI/CD Pipeline / Nix Flake Check (push) Successful in 41s
CI/CD Pipeline / CI Summary (push) Successful in 1s
This commit is contained in:
220
backend/tests/api/test_image_delete.py
Normal file
220
backend/tests/api/test_image_delete.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""Integration tests for image deletion endpoints."""
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from uuid import uuid4
|
||||
|
||||
from app.database.models.user import User
|
||||
from app.database.models.board import Board
|
||||
from app.database.models.image import Image
|
||||
from app.database.models.board_image import BoardImage
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_image_from_board(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test removing image from board (not deleting)."""
|
||||
# Create board and image
|
||||
board = Board(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
title="Test Board",
|
||||
viewport_state={"x": 0, "y": 0, "zoom": 1.0, "rotation": 0},
|
||||
)
|
||||
db.add(board)
|
||||
|
||||
image = Image(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
filename="test.jpg",
|
||||
storage_path=f"{test_user.id}/test.jpg",
|
||||
file_size=1024,
|
||||
mime_type="image/jpeg",
|
||||
width=800,
|
||||
height=600,
|
||||
metadata={"format": "jpeg", "checksum": "abc123"},
|
||||
reference_count=1,
|
||||
)
|
||||
db.add(image)
|
||||
|
||||
board_image = BoardImage(
|
||||
id=uuid4(),
|
||||
board_id=board.id,
|
||||
image_id=image.id,
|
||||
position={"x": 100, "y": 100},
|
||||
transformations={
|
||||
"scale": 1.0,
|
||||
"rotation": 0,
|
||||
"opacity": 1.0,
|
||||
"flipped_h": False,
|
||||
"flipped_v": False,
|
||||
"greyscale": False,
|
||||
},
|
||||
z_order=0,
|
||||
)
|
||||
db.add(board_image)
|
||||
await db.commit()
|
||||
|
||||
# Remove from board
|
||||
response = await client.delete(f"/api/images/boards/{board.id}/images/{image.id}")
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_image_not_on_board(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test removing image that's not on the board."""
|
||||
board = Board(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
title="Test Board",
|
||||
viewport_state={"x": 0, "y": 0, "zoom": 1.0, "rotation": 0},
|
||||
)
|
||||
db.add(board)
|
||||
|
||||
image = Image(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
filename="test.jpg",
|
||||
storage_path=f"{test_user.id}/test.jpg",
|
||||
file_size=1024,
|
||||
mime_type="image/jpeg",
|
||||
width=800,
|
||||
height=600,
|
||||
metadata={"format": "jpeg", "checksum": "abc123"},
|
||||
)
|
||||
db.add(image)
|
||||
await db.commit()
|
||||
|
||||
# Try to remove (image not on board)
|
||||
response = await client.delete(f"/api/images/boards/{board.id}/images/{image.id}")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_image_unauthorized(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test removing image from board not owned by user."""
|
||||
# Create another user
|
||||
other_user = User(id=uuid4(), email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
|
||||
# Create board owned by other user
|
||||
board = Board(
|
||||
id=uuid4(),
|
||||
user_id=other_user.id,
|
||||
title="Other Board",
|
||||
viewport_state={"x": 0, "y": 0, "zoom": 1.0, "rotation": 0},
|
||||
)
|
||||
db.add(board)
|
||||
|
||||
image = Image(
|
||||
id=uuid4(),
|
||||
user_id=other_user.id,
|
||||
filename="test.jpg",
|
||||
storage_path=f"{other_user.id}/test.jpg",
|
||||
file_size=1024,
|
||||
mime_type="image/jpeg",
|
||||
width=800,
|
||||
height=600,
|
||||
metadata={"format": "jpeg", "checksum": "abc123"},
|
||||
)
|
||||
db.add(image)
|
||||
|
||||
board_image = BoardImage(
|
||||
id=uuid4(),
|
||||
board_id=board.id,
|
||||
image_id=image.id,
|
||||
position={"x": 100, "y": 100},
|
||||
transformations={
|
||||
"scale": 1.0,
|
||||
"rotation": 0,
|
||||
"opacity": 1.0,
|
||||
"flipped_h": False,
|
||||
"flipped_v": False,
|
||||
"greyscale": False,
|
||||
},
|
||||
z_order=0,
|
||||
)
|
||||
db.add(board_image)
|
||||
await db.commit()
|
||||
|
||||
# Try to remove as current user
|
||||
response = await client.delete(f"/api/images/boards/{board.id}/images/{image.id}")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_permanent_delete_image(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test permanently deleting image from library."""
|
||||
image = Image(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
filename="test.jpg",
|
||||
storage_path=f"{test_user.id}/test.jpg",
|
||||
file_size=1024,
|
||||
mime_type="image/jpeg",
|
||||
width=800,
|
||||
height=600,
|
||||
metadata={"format": "jpeg", "checksum": "abc123"},
|
||||
reference_count=0, # Not used on any boards
|
||||
)
|
||||
db.add(image)
|
||||
await db.commit()
|
||||
|
||||
# Delete permanently
|
||||
response = await client.delete(f"/api/images/{image.id}")
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cannot_delete_image_in_use(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test that images in use cannot be permanently deleted."""
|
||||
board = Board(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
title="Test Board",
|
||||
viewport_state={"x": 0, "y": 0, "zoom": 1.0, "rotation": 0},
|
||||
)
|
||||
db.add(board)
|
||||
|
||||
image = Image(
|
||||
id=uuid4(),
|
||||
user_id=test_user.id,
|
||||
filename="test.jpg",
|
||||
storage_path=f"{test_user.id}/test.jpg",
|
||||
file_size=1024,
|
||||
mime_type="image/jpeg",
|
||||
width=800,
|
||||
height=600,
|
||||
metadata={"format": "jpeg", "checksum": "abc123"},
|
||||
reference_count=1, # Used on a board
|
||||
)
|
||||
db.add(image)
|
||||
|
||||
board_image = BoardImage(
|
||||
id=uuid4(),
|
||||
board_id=board.id,
|
||||
image_id=image.id,
|
||||
position={"x": 100, "y": 100},
|
||||
transformations={
|
||||
"scale": 1.0,
|
||||
"rotation": 0,
|
||||
"opacity": 1.0,
|
||||
"flipped_h": False,
|
||||
"flipped_v": False,
|
||||
"greyscale": False,
|
||||
},
|
||||
z_order=0,
|
||||
)
|
||||
db.add(board_image)
|
||||
await db.commit()
|
||||
|
||||
# Try to delete
|
||||
response = await client.delete(f"/api/images/{image.id}")
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "still used" in response.json()["detail"].lower()
|
||||
|
||||
Reference in New Issue
Block a user