"""High-level integration tests for soft delete functionality These tests verify that soft delete works correctly by checking: 1. The delete endpoint returns success 2. The record is marked as deleted in the database (deleted_at is set) 3. The record still exists (soft delete, not hard delete) """ import pytest from app import db from app.models import (Board, Card, CardLabel, CardLink, CheckItem, Checklist, Comment, Epic, Label, List, Wiki) @pytest.mark.integration class TestSoftDeleteIntegration: """High-level integration tests for soft delete across all resources""" def test_soft_delete_card_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a card marks it as deleted in the database""" # Create board, list and card 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() # Delete the card response = client.delete(f"/api/cards/{card.id}", headers=auth_headers) assert response.status_code == 200 # Verify card is marked as deleted in database deleted_card = db.session.get(Card, card.id) assert deleted_card is not None assert deleted_card.deleted_at is not None def test_soft_delete_list_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a list marks it as deleted in the database""" # Create board and list 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.commit() # Delete the list response = client.delete(f"/api/lists/{lst.id}", headers=auth_headers) assert response.status_code == 200 # Verify list is marked as deleted in database deleted_list = db.session.get(List, lst.id) assert deleted_list is not None assert deleted_list.deleted_at is not None def test_soft_delete_comment_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a comment marks it as deleted in the database""" # Create board, list, card and comment 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() comment = Comment(text="Test comment", card_id=card.id, user_id=regular_user.id) db_session.add(comment) db_session.commit() # Delete the comment response = client.delete(f"/api/comments/{comment.id}", headers=auth_headers) assert response.status_code == 200 # Verify comment is marked as deleted in database deleted_comment = db.session.get(Comment, comment.id) assert deleted_comment is not None assert deleted_comment.deleted_at is not None def test_soft_delete_checklist_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a checklist marks it as deleted in the database""" # Create board, list, card and checklist 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.commit() # Delete the checklist response = client.delete( f"/api/checklists/{checklist.id}", headers=auth_headers ) assert response.status_code == 200 # Verify checklist is marked as deleted in database deleted_checklist = db.session.get(Checklist, checklist.id) assert deleted_checklist is not None assert deleted_checklist.deleted_at is not None def test_soft_delete_check_item_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a check item marks it as deleted in the database""" # Create board, list, card, checklist and check item 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() item = CheckItem( name="Task", checklist_id=checklist.id, pos=0, state="incomplete" ) db_session.add(item) db_session.commit() # Delete the check item response = client.delete(f"/api/check-items/{item.id}", headers=auth_headers) assert response.status_code == 200 # Verify check item is marked as deleted in database deleted_item = db.session.get(CheckItem, item.id) assert deleted_item is not None assert deleted_item.deleted_at is not None def test_soft_delete_card_label_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that removing a label from a card marks the association as deleted""" # Create board, list, card and labels 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() label = Label(name="Urgent", color="red", board_id=board.id) db_session.add(label) db_session.flush() card_label = CardLabel(card_id=card.id, label_id=label.id) db_session.add(card_label) db_session.commit() # Remove label from card response = client.delete( f"/api/cards/{card.id}/labels/{label.id}", headers=auth_headers ) assert response.status_code == 200 # Verify card-label association is marked as deleted in database deleted_card_label = ( db_session.query(CardLabel) .filter_by(card_id=card.id, label_id=label.id) .first() ) assert deleted_card_label is not None assert deleted_card_label.deleted_at is not None def test_soft_delete_epic_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting an epic marks it as deleted in the database""" # Create board and epic board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() epic = Epic(name="Test Epic", board_id=board.id, color="#3b82f6") db_session.add(epic) db_session.commit() # Delete the epic response = client.delete(f"/api/epics/{epic.id}", headers=auth_headers) assert response.status_code == 200 # Verify epic is marked as deleted in database deleted_epic = db.session.get(Epic, epic.id) assert deleted_epic is not None assert deleted_epic.deleted_at is not None def test_soft_delete_epic_unlinks_card( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleting an epic unlinks associated cards""" # Create board, list, card and epic 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() epic = Epic(name="Test Epic", board_id=board.id, color="#3b82f6") db_session.add(epic) db_session.flush() card.epic_id = epic.id db_session.commit() # Delete epic response = client.delete(f"/api/epics/{epic.id}", headers=auth_headers) assert response.status_code == 200 # Verify card no longer has epic db.session.refresh(card) assert card.epic_id is None def test_soft_delete_wiki_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a wiki marks it as deleted in the database""" # Create board and wiki board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.flush() wiki = Wiki( name="Test Wiki", board_id=board.id, slug="test-wiki", content=[{"type": "paragraph", "children": [{"text": "Content"}]}], created_by=regular_user.id, ) db_session.add(wiki) db_session.commit() # Delete the wiki response = client.delete(f"/api/wikis/{wiki.id}", headers=auth_headers) assert response.status_code == 200 # Verify wiki is marked as deleted in database deleted_wiki = db.session.get(Wiki, wiki.id) assert deleted_wiki is not None assert deleted_wiki.deleted_at is not None def test_soft_delete_board_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a board marks it as deleted in the database""" # Create board board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.commit() # Delete the board response = client.delete(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 # Verify board is marked as deleted in database deleted_board = db.session.get(Board, board.id) assert deleted_board is not None assert deleted_board.status == "deleted" def test_soft_delete_card_link_marks_deleted( self, client, db_session, regular_user, auth_headers ): """Test that deleting a card link marks it as deleted in the database""" # Create board, list 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() parent_card = Card(name="Parent Card", board_id=board.id, list_id=lst.id, pos=0) child_card = Card(name="Child Card", board_id=board.id, list_id=lst.id, pos=1) db_session.add_all([parent_card, child_card]) db_session.commit() # Create card link link = CardLink( parent_card_id=parent_card.id, child_card_id=child_card.id, created_by=regular_user.id, ) db_session.add(link) db_session.commit() # Delete the link response = client.delete(f"/api/card-links/{link.id}", headers=auth_headers) assert response.status_code == 200 # Verify link is marked as deleted in database deleted_link = db.session.get(CardLink, link.id) assert deleted_link is not None assert deleted_link.deleted_at is not None def test_soft_delete_cascade_from_list_to_cards( self, client, db_session, regular_user, auth_headers ): """Test that deleting a list soft-deletes its cards""" # Create board, list 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() card1 = Card(name="Card 1", board_id=board.id, list_id=lst.id, pos=0) card2 = Card(name="Card 2", board_id=board.id, list_id=lst.id, pos=1) db_session.add_all([card1, card2]) db_session.commit() # Delete list response = client.delete(f"/api/lists/{lst.id}", headers=auth_headers) assert response.status_code == 200 # Verify list is soft-deleted deleted_list = db.session.get(List, lst.id) assert deleted_list is not None assert deleted_list.deleted_at is not None # Verify cards are also soft-deleted deleted_card1 = db.session.get(Card, card1.id) deleted_card2 = db.session.get(Card, card2.id) assert deleted_card1 is not None assert deleted_card1.deleted_at is not None assert deleted_card2 is not None assert deleted_card2.deleted_at is not None def test_soft_delete_multiple_resources_in_sequence( self, client, db_session, regular_user, auth_headers ): """Test deleting multiple resources in sequence and verifying soft delete""" # Create board with lists, cards, comments, checklists, labels 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() comment = Comment(text="Test comment", card_id=card.id, user_id=regular_user.id) db_session.add(comment) db_session.flush() label = Label(name="Urgent", color="red", board_id=board.id) db_session.add(label) db_session.flush() card_label = CardLabel(card_id=card.id, label_id=label.id) db_session.add(card_label) db_session.flush() checklist = Checklist(name="Tasks", board_id=board.id, card_id=card.id, pos=0) db_session.add(checklist) db_session.flush() item = CheckItem( name="Task", checklist_id=checklist.id, pos=0, state="incomplete" ) db_session.add(item) db_session.commit() # Delete multiple resources in sequence client.delete(f"/api/comments/{comment.id}", headers=auth_headers) client.delete(f"/api/check-items/{item.id}", headers=auth_headers) client.delete(f"/api/cards/{card.id}/labels/{label.id}", headers=auth_headers) # Verify all are marked as deleted in database deleted_comment = db.session.get(Comment, comment.id) deleted_item = db.session.get(CheckItem, item.id) deleted_card_label = ( db_session.query(CardLabel) .filter_by(card_id=card.id, label_id=label.id) .first() ) assert deleted_comment is not None assert deleted_comment.deleted_at is not None assert deleted_item is not None assert deleted_item.deleted_at is not None assert deleted_card_label is not None assert deleted_card_label.deleted_at is not None def test_soft_delete_record_still_exists( self, client, db_session, regular_user, auth_headers ): """Test that soft-deleted records still exist in the database""" # Create board board = Board(name="Test Board", user_id=regular_user.id) db_session.add(board) db_session.commit() # Delete the board response = client.delete(f"/api/boards/{board.id}", headers=auth_headers) assert response.status_code == 200 # Verify record still exists (not hard deleted) deleted_board = db.session.get(Board, board.id) assert deleted_board is not None assert deleted_board.id == board.id assert deleted_board.name == "Test Board" def test_convert_check_item_to_card_deletes_check_item( self, client, db_session, regular_user, auth_headers ): """Test that converting a check item to a card soft-deletes the check item""" # Create board, list, card, checklist and check item 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="Parent 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() item = CheckItem( name="Task to convert", checklist_id=checklist.id, pos=0, state="incomplete" ) db_session.add(item) db_session.commit() # Convert check item to card response = client.post( f"/api/cards/{card.id}/checklist-items/{item.id}/convert-to-card", headers=auth_headers, json={"list_id": lst.id}, ) assert response.status_code == 201 # Verify check item is soft-deleted deleted_item = db.session.get(CheckItem, item.id) assert deleted_item is not None assert deleted_item.deleted_at is not None