phase 4
This commit is contained in:
2
backend/tests/boards/__init__.py
Normal file
2
backend/tests/boards/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Board module tests."""
|
||||
|
||||
442
backend/tests/boards/test_repository.py
Normal file
442
backend/tests/boards/test_repository.py
Normal file
@@ -0,0 +1,442 @@
|
||||
"""Unit tests for board repository."""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.boards.repository import BoardRepository
|
||||
from app.database.models.board import Board
|
||||
from app.database.models.user import User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_user(db: Session) -> User:
|
||||
"""Create a test user."""
|
||||
user = User(email="test@example.com", password_hash="hashed_password")
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def board_repo(db: Session) -> BoardRepository:
|
||||
"""Create a board repository instance."""
|
||||
return BoardRepository(db)
|
||||
|
||||
|
||||
class TestCreateBoard:
|
||||
"""Test board creation."""
|
||||
|
||||
def test_create_board_minimal(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test creating board with only required fields."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
assert board.id is not None
|
||||
assert board.user_id == test_user.id
|
||||
assert board.title == "Test Board"
|
||||
assert board.description is None
|
||||
assert board.is_deleted is False
|
||||
assert board.created_at is not None
|
||||
assert board.updated_at is not None
|
||||
|
||||
def test_create_board_with_description(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test creating board with description."""
|
||||
board = board_repo.create_board(
|
||||
user_id=test_user.id, title="Test Board", description="This is a test description"
|
||||
)
|
||||
|
||||
assert board.description == "This is a test description"
|
||||
|
||||
def test_create_board_default_viewport(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that board is created with default viewport state."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
assert board.viewport_state is not None
|
||||
assert board.viewport_state["x"] == 0
|
||||
assert board.viewport_state["y"] == 0
|
||||
assert board.viewport_state["zoom"] == 1.0
|
||||
assert board.viewport_state["rotation"] == 0
|
||||
|
||||
def test_create_board_custom_viewport(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test creating board with custom viewport state."""
|
||||
custom_viewport = {"x": 100, "y": 200, "zoom": 2.0, "rotation": 45}
|
||||
|
||||
board = board_repo.create_board(
|
||||
user_id=test_user.id, title="Test Board", viewport_state=custom_viewport
|
||||
)
|
||||
|
||||
assert board.viewport_state == custom_viewport
|
||||
|
||||
def test_create_multiple_boards(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test creating multiple boards for same user."""
|
||||
board1 = board_repo.create_board(user_id=test_user.id, title="Board 1")
|
||||
board2 = board_repo.create_board(user_id=test_user.id, title="Board 2")
|
||||
board3 = board_repo.create_board(user_id=test_user.id, title="Board 3")
|
||||
|
||||
assert board1.id != board2.id
|
||||
assert board2.id != board3.id
|
||||
assert all(b.user_id == test_user.id for b in [board1, board2, board3])
|
||||
|
||||
|
||||
class TestGetBoardById:
|
||||
"""Test retrieving board by ID."""
|
||||
|
||||
def test_get_existing_board(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test getting existing board owned by user."""
|
||||
created = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
retrieved = board_repo.get_board_by_id(board_id=created.id, user_id=test_user.id)
|
||||
|
||||
assert retrieved is not None
|
||||
assert retrieved.id == created.id
|
||||
assert retrieved.title == created.title
|
||||
|
||||
def test_get_nonexistent_board(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test getting board that doesn't exist."""
|
||||
fake_id = uuid4()
|
||||
|
||||
result = board_repo.get_board_by_id(board_id=fake_id, user_id=test_user.id)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_get_board_wrong_owner(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that users can't access boards they don't own."""
|
||||
# Create another user
|
||||
other_user = User(email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
db.commit()
|
||||
db.refresh(other_user)
|
||||
|
||||
# Create board owned by test_user
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Try to get with other_user
|
||||
result = board_repo.get_board_by_id(board_id=board.id, user_id=other_user.id)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_get_deleted_board(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that soft-deleted boards are not returned."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Delete the board
|
||||
board_repo.delete_board(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
# Try to get it
|
||||
result = board_repo.get_board_by_id(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestGetUserBoards:
|
||||
"""Test listing user's boards."""
|
||||
|
||||
def test_get_user_boards_empty(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test getting boards when user has none."""
|
||||
boards, total = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
assert boards == []
|
||||
assert total == 0
|
||||
|
||||
def test_get_user_boards_multiple(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test getting multiple boards."""
|
||||
board1 = board_repo.create_board(user_id=test_user.id, title="Board 1")
|
||||
board2 = board_repo.create_board(user_id=test_user.id, title="Board 2")
|
||||
board3 = board_repo.create_board(user_id=test_user.id, title="Board 3")
|
||||
|
||||
boards, total = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
assert len(boards) == 3
|
||||
assert total == 3
|
||||
assert {b.id for b in boards} == {board1.id, board2.id, board3.id}
|
||||
|
||||
def test_get_user_boards_pagination(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test pagination of board list."""
|
||||
# Create 5 boards
|
||||
for i in range(5):
|
||||
board_repo.create_board(user_id=test_user.id, title=f"Board {i}")
|
||||
|
||||
# Get first 2
|
||||
boards_page1, total = board_repo.get_user_boards(user_id=test_user.id, limit=2, offset=0)
|
||||
|
||||
assert len(boards_page1) == 2
|
||||
assert total == 5
|
||||
|
||||
# Get next 2
|
||||
boards_page2, total = board_repo.get_user_boards(user_id=test_user.id, limit=2, offset=2)
|
||||
|
||||
assert len(boards_page2) == 2
|
||||
assert total == 5
|
||||
|
||||
# Ensure no overlap
|
||||
page1_ids = {b.id for b in boards_page1}
|
||||
page2_ids = {b.id for b in boards_page2}
|
||||
assert page1_ids.isdisjoint(page2_ids)
|
||||
|
||||
def test_get_user_boards_sorted_by_update(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that boards are sorted by updated_at descending."""
|
||||
board1 = board_repo.create_board(user_id=test_user.id, title="Oldest")
|
||||
board2 = board_repo.create_board(user_id=test_user.id, title="Middle")
|
||||
board3 = board_repo.create_board(user_id=test_user.id, title="Newest")
|
||||
|
||||
boards, _ = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
# Most recently updated should be first
|
||||
assert boards[0].id == board3.id
|
||||
assert boards[1].id == board2.id
|
||||
assert boards[2].id == board1.id
|
||||
|
||||
def test_get_user_boards_excludes_deleted(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that soft-deleted boards are excluded."""
|
||||
board1 = board_repo.create_board(user_id=test_user.id, title="Board 1")
|
||||
board2 = board_repo.create_board(user_id=test_user.id, title="Board 2")
|
||||
board3 = board_repo.create_board(user_id=test_user.id, title="Board 3")
|
||||
|
||||
# Delete board2
|
||||
board_repo.delete_board(board_id=board2.id, user_id=test_user.id)
|
||||
|
||||
boards, total = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
assert len(boards) == 2
|
||||
assert total == 2
|
||||
assert {b.id for b in boards} == {board1.id, board3.id}
|
||||
|
||||
def test_get_user_boards_isolation(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that users only see their own boards."""
|
||||
# Create another user
|
||||
other_user = User(email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
db.commit()
|
||||
db.refresh(other_user)
|
||||
|
||||
# Create boards for both users
|
||||
test_board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
other_board = board_repo.create_board(user_id=other_user.id, title="Other Board")
|
||||
|
||||
# Get test_user's boards
|
||||
test_boards, _ = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
assert len(test_boards) == 1
|
||||
assert test_boards[0].id == test_board.id
|
||||
|
||||
# Get other_user's boards
|
||||
other_boards, _ = board_repo.get_user_boards(user_id=other_user.id)
|
||||
|
||||
assert len(other_boards) == 1
|
||||
assert other_boards[0].id == other_board.id
|
||||
|
||||
|
||||
class TestUpdateBoard:
|
||||
"""Test board updates."""
|
||||
|
||||
def test_update_board_title(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test updating board title."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Original Title")
|
||||
|
||||
updated = board_repo.update_board(
|
||||
board_id=board.id, user_id=test_user.id, title="Updated Title"
|
||||
)
|
||||
|
||||
assert updated is not None
|
||||
assert updated.title == "Updated Title"
|
||||
assert updated.id == board.id
|
||||
|
||||
def test_update_board_description(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test updating board description."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
updated = board_repo.update_board(
|
||||
board_id=board.id, user_id=test_user.id, description="New description"
|
||||
)
|
||||
|
||||
assert updated is not None
|
||||
assert updated.description == "New description"
|
||||
|
||||
def test_update_board_viewport(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test updating viewport state."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
new_viewport = {"x": 100, "y": 200, "zoom": 1.5, "rotation": 90}
|
||||
updated = board_repo.update_board(
|
||||
board_id=board.id, user_id=test_user.id, viewport_state=new_viewport
|
||||
)
|
||||
|
||||
assert updated is not None
|
||||
assert updated.viewport_state == new_viewport
|
||||
|
||||
def test_update_multiple_fields(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test updating multiple fields at once."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Original")
|
||||
|
||||
updated = board_repo.update_board(
|
||||
board_id=board.id,
|
||||
user_id=test_user.id,
|
||||
title="Updated Title",
|
||||
description="Updated Description",
|
||||
viewport_state={"x": 50, "y": 50, "zoom": 2.0, "rotation": 45},
|
||||
)
|
||||
|
||||
assert updated is not None
|
||||
assert updated.title == "Updated Title"
|
||||
assert updated.description == "Updated Description"
|
||||
assert updated.viewport_state["zoom"] == 2.0
|
||||
|
||||
def test_update_nonexistent_board(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test updating board that doesn't exist."""
|
||||
fake_id = uuid4()
|
||||
|
||||
result = board_repo.update_board(board_id=fake_id, user_id=test_user.id, title="New Title")
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_update_board_wrong_owner(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that users can't update boards they don't own."""
|
||||
# Create another user
|
||||
other_user = User(email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
db.commit()
|
||||
db.refresh(other_user)
|
||||
|
||||
# Create board owned by test_user
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Try to update with other_user
|
||||
result = board_repo.update_board(
|
||||
board_id=board.id, user_id=other_user.id, title="Hacked Title"
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
# Verify original board unchanged
|
||||
original = board_repo.get_board_by_id(board_id=board.id, user_id=test_user.id)
|
||||
assert original.title == "Test Board"
|
||||
|
||||
def test_update_board_partial_update(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that partial updates don't affect unspecified fields."""
|
||||
board = board_repo.create_board(
|
||||
user_id=test_user.id, title="Original Title", description="Original Description"
|
||||
)
|
||||
|
||||
# Update only title
|
||||
updated = board_repo.update_board(board_id=board.id, user_id=test_user.id, title="New Title")
|
||||
|
||||
assert updated is not None
|
||||
assert updated.title == "New Title"
|
||||
assert updated.description == "Original Description" # Should be unchanged
|
||||
|
||||
|
||||
class TestDeleteBoard:
|
||||
"""Test board deletion."""
|
||||
|
||||
def test_delete_board_success(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test successfully deleting a board."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
success = board_repo.delete_board(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
assert success is True
|
||||
|
||||
def test_delete_board_soft_delete(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that delete is a soft delete (sets flag instead of removing)."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
board_repo.delete_board(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
# Board should still exist in database but marked as deleted
|
||||
db_board = db.get(Board, board.id)
|
||||
assert db_board is not None
|
||||
assert db_board.is_deleted is True
|
||||
|
||||
def test_delete_board_not_in_listings(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that deleted boards don't appear in listings."""
|
||||
board1 = board_repo.create_board(user_id=test_user.id, title="Board 1")
|
||||
board2 = board_repo.create_board(user_id=test_user.id, title="Board 2")
|
||||
|
||||
# Delete board1
|
||||
board_repo.delete_board(board_id=board1.id, user_id=test_user.id)
|
||||
|
||||
boards, total = board_repo.get_user_boards(user_id=test_user.id)
|
||||
|
||||
assert len(boards) == 1
|
||||
assert total == 1
|
||||
assert boards[0].id == board2.id
|
||||
|
||||
def test_delete_nonexistent_board(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test deleting board that doesn't exist."""
|
||||
fake_id = uuid4()
|
||||
|
||||
success = board_repo.delete_board(board_id=fake_id, user_id=test_user.id)
|
||||
|
||||
assert success is False
|
||||
|
||||
def test_delete_board_wrong_owner(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that users can't delete boards they don't own."""
|
||||
# Create another user
|
||||
other_user = User(email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
db.commit()
|
||||
db.refresh(other_user)
|
||||
|
||||
# Create board owned by test_user
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Try to delete with other_user
|
||||
success = board_repo.delete_board(board_id=board.id, user_id=other_user.id)
|
||||
|
||||
assert success is False
|
||||
|
||||
# Verify board still exists for original owner
|
||||
still_exists = board_repo.get_board_by_id(board_id=board.id, user_id=test_user.id)
|
||||
assert still_exists is not None
|
||||
assert still_exists.is_deleted is False
|
||||
|
||||
|
||||
class TestBoardExists:
|
||||
"""Test board existence check."""
|
||||
|
||||
def test_board_exists_true(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test checking if board exists."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
exists = board_repo.board_exists(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
assert exists is True
|
||||
|
||||
def test_board_exists_false(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test checking if board doesn't exist."""
|
||||
fake_id = uuid4()
|
||||
|
||||
exists = board_repo.board_exists(board_id=fake_id, user_id=test_user.id)
|
||||
|
||||
assert exists is False
|
||||
|
||||
def test_board_exists_wrong_owner(self, board_repo: BoardRepository, test_user: User, db: Session):
|
||||
"""Test that board_exists returns False for wrong owner."""
|
||||
# Create another user
|
||||
other_user = User(email="other@example.com", password_hash="hashed")
|
||||
db.add(other_user)
|
||||
db.commit()
|
||||
db.refresh(other_user)
|
||||
|
||||
# Create board owned by test_user
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Check with wrong owner
|
||||
exists = board_repo.board_exists(board_id=board.id, user_id=other_user.id)
|
||||
|
||||
assert exists is False
|
||||
|
||||
def test_board_exists_deleted(self, board_repo: BoardRepository, test_user: User):
|
||||
"""Test that deleted boards return False for existence check."""
|
||||
board = board_repo.create_board(user_id=test_user.id, title="Test Board")
|
||||
|
||||
# Delete board
|
||||
board_repo.delete_board(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
# Check existence
|
||||
exists = board_repo.board_exists(board_id=board.id, user_id=test_user.id)
|
||||
|
||||
assert exists is False
|
||||
|
||||
Reference in New Issue
Block a user