phase 7
All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 8s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 8s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 3s
CI/CD Pipeline / Frontend Linting (push) Successful in 15s
CI/CD Pipeline / Nix Flake Check (push) Successful in 36s
CI/CD Pipeline / CI Summary (push) Successful in 0s
All checks were successful
CI/CD Pipeline / VM Test - backend-integration (push) Successful in 8s
CI/CD Pipeline / VM Test - full-stack (push) Successful in 7s
CI/CD Pipeline / VM Test - performance (push) Successful in 8s
CI/CD Pipeline / VM Test - security (push) Successful in 7s
CI/CD Pipeline / Backend Linting (push) Successful in 3s
CI/CD Pipeline / Frontend Linting (push) Successful in 15s
CI/CD Pipeline / Nix Flake Check (push) Successful in 36s
CI/CD Pipeline / CI Summary (push) Successful in 0s
This commit is contained in:
454
backend/tests/api/test_image_position.py
Normal file
454
backend/tests/api/test_image_position.py
Normal file
@@ -0,0 +1,454 @@
|
||||
"""Integration tests for image position update endpoint."""
|
||||
|
||||
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_update_image_position(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating image position on board."""
|
||||
# Create a 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)
|
||||
|
||||
# Create an image
|
||||
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)
|
||||
|
||||
# Add image to board
|
||||
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()
|
||||
|
||||
# Update position
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"position": {"x": 200, "y": 250}},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["position"]["x"] == 200
|
||||
assert data["position"]["y"] == 250
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_image_transformations(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating image transformations."""
|
||||
# Create board, image, and board_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"},
|
||||
)
|
||||
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()
|
||||
|
||||
# Update transformations
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={
|
||||
"transformations": {
|
||||
"scale": 1.5,
|
||||
"rotation": 45,
|
||||
"opacity": 0.8,
|
||||
"flipped_h": True,
|
||||
"flipped_v": False,
|
||||
"greyscale": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["transformations"]["scale"] == 1.5
|
||||
assert data["transformations"]["rotation"] == 45
|
||||
assert data["transformations"]["opacity"] == 0.8
|
||||
assert data["transformations"]["flipped_h"] is True
|
||||
assert data["transformations"]["greyscale"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_image_z_order(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating image Z-order."""
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
# Update Z-order
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"z_order": 5},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["z_order"] == 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_multiple_fields(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating position, transformations, and z-order together."""
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
# Update everything
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={
|
||||
"position": {"x": 300, "y": 400},
|
||||
"transformations": {"scale": 2.0, "rotation": 90},
|
||||
"z_order": 10,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["position"]["x"] == 300
|
||||
assert data["position"]["y"] == 400
|
||||
assert data["transformations"]["scale"] == 2.0
|
||||
assert data["transformations"]["rotation"] == 90
|
||||
assert data["z_order"] == 10
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_image_not_on_board(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating image that's not on the specified 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 update image that's not on board
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"position": {"x": 200, "y": 200}},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert "not on this board" in response.json()["detail"].lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_image_invalid_position(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test updating with invalid position data."""
|
||||
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)
|
||||
|
||||
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 update with missing y coordinate
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"position": {"x": 200}},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_image_unauthorized(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test that other users cannot update images on boards they don't own."""
|
||||
# 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 User's 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 update as current user (should fail)
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"position": {"x": 200, "y": 200}},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_preserves_other_fields(client: AsyncClient, test_user: User, db: AsyncSession):
|
||||
"""Test that updating one field preserves others."""
|
||||
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)
|
||||
|
||||
board_image = BoardImage(
|
||||
id=uuid4(),
|
||||
board_id=board.id,
|
||||
image_id=image.id,
|
||||
position={"x": 100, "y": 100},
|
||||
transformations={
|
||||
"scale": 1.5,
|
||||
"rotation": 45,
|
||||
"opacity": 0.9,
|
||||
"flipped_h": True,
|
||||
"flipped_v": False,
|
||||
"greyscale": False,
|
||||
},
|
||||
z_order=3,
|
||||
)
|
||||
db.add(board_image)
|
||||
await db.commit()
|
||||
|
||||
# Update only position
|
||||
response = await client.patch(
|
||||
f"/api/images/boards/{board.id}/images/{image.id}",
|
||||
json={"position": {"x": 200, "y": 200}},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Position should be updated
|
||||
assert data["position"]["x"] == 200
|
||||
assert data["position"]["y"] == 200
|
||||
|
||||
# Other fields should be preserved
|
||||
assert data["transformations"]["scale"] == 1.5
|
||||
assert data["transformations"]["rotation"] == 45
|
||||
assert data["transformations"]["opacity"] == 0.9
|
||||
assert data["z_order"] == 3
|
||||
|
||||
Reference in New Issue
Block a user