kanban-app/backend/tests/routes/test_soft_delete_integration.py
2026-05-01 18:03:00 +03:00

496 lines
18 KiB
Python

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