"""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