496 lines
18 KiB
Python
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
|