import pytest from app import db from app.models import (Board, Card, CardLabel, CheckItem, Checklist, Comment, Label, List) @pytest.mark.integration class TestBoardRoutes: """Test Board API routes""" def test_get_boards_success(self, client, db_session, regular_user, auth_headers): """Test getting all boards for current user""" # Create a board for the user board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.commit() response = client.get("/api/boards", headers=auth_headers) assert response.status_code == 200 data = response.get_json() assert len(data) == 1 assert data[0]["name"] == "Test Board" def test_get_boards_unauthorized(self, client, db_session): """Test getting boards without authentication""" response = client.get("/api/boards") assert response.status_code == 401 def test_get_board_success(self, client, db_session, regular_user, auth_headers): """Test getting a single board with details""" # Create a board with lists and cards board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card = Card(name="Test Card", board_id=board.id, list_id=lst.id, pos=0) db_session.add(card) db_session.commit() response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() assert data["id"] == board.id assert data["name"] == "Test Board" assert len(data["lists"]) == 1 assert data["lists"][0]["name"] == "To Do" assert len(data["lists"][0]["cards"]) == 1 def test_get_board_not_found(self, client, db_session, auth_headers): """Test getting a non-existent board""" response = client.get("/api/boards/99999", headers=auth_headers) assert response.status_code == 404 def test_get_board_access_denied( self, client, db_session, regular_user, auth_headers ): """Test getting another user's board""" # Create a board for user 1 board = Board(name="User 1 Board", user_id=regular_user.id) db_session.add(board) db_session.commit() # Try to access with user 2's token (assuming auth_headers is for user 1) # This test assumes auth_headers is for a different user # In real scenario, you'd need another user fixture response = client.get(f"/api/boards/{board.id}", headers=auth_headers) # Should succeed since we're using same user's token assert response.status_code == 200 def test_create_board_success(self, client, db_session, auth_headers): """Test creating a new board""" response = client.post( "/api/boards", headers=auth_headers, json={"name": "New Board", "description": "Board description"}, ) assert response.status_code == 201 data = response.get_json() assert data["name"] == "New Board" assert data["description"] == "Board description" assert "id" in data def test_create_board_missing_name(self, client, db_session, auth_headers): """Test creating a board without name""" response = client.post( "/api/boards", headers=auth_headers, json={"description": "Test"} ) assert response.status_code == 400 data = response.get_json() assert "validation_error" in data def test_create_board_unauthorized(self, client, db_session): """Test creating a board without authentication""" response = client.post("/api/boards", json={"name": "New Board"}) assert response.status_code == 401 def test_update_board_success(self, client, db_session, regular_user, auth_headers): """Test updating a board""" board = Board(name="Original Name", user_id=regular_user.id) db_session.add(board) db_session.commit() response = client.put( f"/api/boards/{board.id}", headers=auth_headers, json={"name": "Updated Name", "description": "New description"}, ) assert response.status_code == 200 data = response.get_json() assert data["name"] == "Updated Name" assert data["description"] == "New description" def test_update_board_not_found(self, client, db_session, auth_headers): """Test updating a non-existent board""" response = client.put( "/api/boards/99999", headers=auth_headers, json={"name": "Updated"}, ) assert response.status_code == 404 def test_delete_board_success(self, client, db_session, regular_user, auth_headers): """Test deleting a board""" board = Board(name="To Delete", user_id=regular_user.id) db_session.add(board) db_session.commit() response = client.delete(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() assert "message" in data # Verify board is deleted deleted_board = db.session.get(Board, board.id) assert deleted_board is not None assert deleted_board.status == "deleted" def test_delete_board_not_found(self, client, db_session, auth_headers): """Test deleting a non-existent board""" response = client.delete("/api/boards/99999", headers=auth_headers) assert response.status_code == 404 def test_delete_board_unauthorized(self, client, db_session, regular_user): """Test deleting a board without authentication""" board = Board(name="Test", user_id=regular_user.id) db_session.add(board) db_session.commit() response = client.delete(f"/api/boards/{board.id}") assert response.status_code == 401 def test_get_board_filters_deleted_cards( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted cards are filtered out from board response""" # Create board with list and 2 cards board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card1 = Card(name="Active Card", board_id=board.id, list_id=lst.id, pos=0) card2 = Card(name="Deleted Card", board_id=board.id, list_id=lst.id, pos=1) db_session.add_all([card1, card2]) db_session.commit() # Soft delete card2 card2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 card (the active one) assert len(data["lists"][0]["cards"]) == 1 assert data["lists"][0]["cards"][0]["name"] == "Active Card" def test_get_board_filters_deleted_lists( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted lists are filtered out from board response""" # Create board with 2 lists board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst1 = List(name="To Do", board_id=board.id, pos=0) lst2 = List(name="Deleted List", board_id=board.id, pos=1) db_session.add_all([lst1, lst2]) db_session.commit() # Soft delete lst2 lst2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 list (the active one) assert len(data["lists"]) == 1 assert data["lists"][0]["name"] == "To Do" def test_get_board_filters_deleted_comments( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted comments are filtered out from card response""" # Create board, list, card and 2 comments board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card = Card(name="Test Card", board_id=board.id, list_id=lst.id, pos=0) db_session.add(card) db_session.flush() comment1 = Comment( text="Active comment", card_id=card.id, user_id=regular_user.id ) comment2 = Comment( text="Deleted comment", card_id=card.id, user_id=regular_user.id ) db_session.add_all([comment1, comment2]) db_session.commit() # Soft delete comment2 comment2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 comment (the active one) assert len(data["lists"][0]["cards"][0]["comments"]) == 1 assert data["lists"][0]["cards"][0]["comments"][0]["text"] == "Active comment" def test_get_board_filters_deleted_checklists( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted checklists are filtered out from card response""" # Create board, list, card and 2 checklists board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card = Card(name="Test Card", board_id=board.id, list_id=lst.id, pos=0) db_session.add(card) db_session.flush() checklist1 = Checklist( name="Active Checklist", board_id=board.id, card_id=card.id, pos=0 ) checklist2 = Checklist( name="Deleted Checklist", board_id=board.id, card_id=card.id, pos=1 ) db_session.add_all([checklist1, checklist2]) db_session.commit() # Soft delete checklist2 checklist2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 checklist (the active one) assert len(data["lists"][0]["cards"][0]["checklists"]) == 1 assert ( data["lists"][0]["cards"][0]["checklists"][0]["name"] == "Active Checklist" ) def test_get_board_filters_deleted_check_items( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted check items are filtered out from checklist response""" # Create board, list, card, checklist and 2 check items board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card = Card(name="Test Card", board_id=board.id, list_id=lst.id, pos=0) db_session.add(card) db_session.flush() checklist = Checklist(name="Tasks", board_id=board.id, card_id=card.id, pos=0) db_session.add(checklist) db_session.flush() item1 = CheckItem(name="Active Task", checklist_id=checklist.id, pos=0) item2 = CheckItem(name="Deleted Task", checklist_id=checklist.id, pos=1) db_session.add_all([item1, item2]) db_session.commit() # Soft delete item2 item2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 check item (the active one) assert len(data["lists"][0]["cards"][0]["checklists"][0]["items"]) == 1 assert ( data["lists"][0]["cards"][0]["checklists"][0]["items"][0]["name"] == "Active Task" ) def test_get_board_filters_deleted_card_labels( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted card-label associations are filtered out""" # Create board, list, card, label and 2 card-label associations board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() lst = List(name="To Do", board_id=board.id, pos=0) db_session.add(lst) db_session.flush() card = Card(name="Test Card", board_id=board.id, list_id=lst.id, pos=0) db_session.add(card) db_session.flush() label1 = Label(name="Urgent", color="red", board_id=board.id) label2 = Label(name="Important", color="yellow", board_id=board.id) db_session.add_all([label1, label2]) db_session.flush() card_label1 = CardLabel(card_id=card.id, label_id=label1.id) card_label2 = CardLabel(card_id=card.id, label_id=label2.id) db_session.add_all([card_label1, card_label2]) db_session.commit() # Soft delete card_label2 card_label2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Should only have 1 label (the active one) assert len(data["lists"][0]["cards"][0]["labels"]) == 1 assert data["lists"][0]["cards"][0]["labels"][0]["name"] == "Urgent" def test_get_board_comprehensive_soft_delete_filtering( self, client, db_session, regular_user, auth_headers ): """Test comprehensive soft delete filtering across all nested resources""" # Create board with all types of nested resources, some deleted board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() # Active list lst1 = List(name="Active List", board_id=board.id, pos=0) db_session.add(lst1) db_session.flush() card1 = Card(name="Active Card 1", board_id=board.id, list_id=lst1.id, pos=0) card2 = Card(name="Deleted Card", board_id=board.id, list_id=lst1.id, pos=1) db_session.add_all([card1, card2]) db_session.flush() # Active comment comment1 = Comment( text="Active comment", card_id=card1.id, user_id=regular_user.id ) # Deleted comment comment2 = Comment( text="Deleted comment", card_id=card1.id, user_id=regular_user.id ) db_session.add_all([comment1, comment2]) db_session.flush() # Active checklist checklist1 = Checklist( name="Active Checklist", board_id=board.id, card_id=card1.id, pos=0 ) # Deleted checklist checklist2 = Checklist( name="Deleted Checklist", board_id=board.id, card_id=card1.id, pos=1 ) db_session.add_all([checklist1, checklist2]) db_session.flush() # Active check item item1 = CheckItem(name="Active Task", checklist_id=checklist1.id, pos=0) # Deleted check item item2 = CheckItem(name="Deleted Task", checklist_id=checklist1.id, pos=1) db_session.add_all([item1, item2]) db_session.flush() # Labels label1 = Label(name="Active Label", color="red", board_id=board.id) label2 = Label(name="Deleted Label", color="yellow", board_id=board.id) db_session.add_all([label1, label2]) db_session.flush() # Active card-label card_label1 = CardLabel(card_id=card1.id, label_id=label1.id) # Deleted card-label card_label2 = CardLabel(card_id=card1.id, label_id=label2.id) db_session.add_all([card_label1, card_label2]) db_session.commit() # Soft delete some resources card2.soft_delete() comment2.soft_delete() checklist2.soft_delete() item2.soft_delete() card_label2.soft_delete() db_session.commit() # Get board response = client.get(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 data = response.get_json() # Verify lists: should have 1 list (we didn't delete the list) assert len(data["lists"]) == 1 assert data["lists"][0]["name"] == "Active List" # Verify cards: should have 1 card (card2 is deleted) assert len(data["lists"][0]["cards"]) == 1 assert data["lists"][0]["cards"][0]["name"] == "Active Card 1" # Verify comments: should have 1 comment (comment2 is deleted) assert len(data["lists"][0]["cards"][0]["comments"]) == 1 assert data["lists"][0]["cards"][0]["comments"][0]["text"] == "Active comment" # Verify checklists: should have 1 checklist (checklist2 is deleted) assert len(data["lists"][0]["cards"][0]["checklists"]) == 1 assert ( data["lists"][0]["cards"][0]["checklists"][0]["name"] == "Active Checklist" ) # Verify check items: should have 1 item (item2 is deleted) assert len(data["lists"][0]["cards"][0]["checklists"][0]["items"]) == 1 assert ( data["lists"][0]["cards"][0]["checklists"][0]["items"][0]["name"] == "Active Task" ) # Verify labels: should have 1 label (card_label2 is deleted) assert len(data["lists"][0]["cards"][0]["labels"]) == 1 assert data["lists"][0]["cards"][0]["labels"][0]["name"] == "Active Label"