2026-02-25 16:48:18 +00:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from app import db
|
2026-02-25 18:32:57 +00:00
|
|
|
from app.models import Board, Card, List
|
2026-02-25 16:48:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.integration
|
|
|
|
|
class TestCardRoutes:
|
|
|
|
|
"""Test Card API routes"""
|
|
|
|
|
|
|
|
|
|
def test_create_card_success(self, client, db_session, regular_user, auth_headers):
|
|
|
|
|
"""Test creating a new card in a 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()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/lists/{lst.id}/cards",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "New Card", "description": "Card description", "pos": 0},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "New Card"
|
|
|
|
|
assert data["description"] == "Card description"
|
|
|
|
|
assert data["list_id"] == lst.id
|
|
|
|
|
assert data["board_id"] == board.id
|
|
|
|
|
|
2026-02-25 18:32:57 +00:00
|
|
|
def test_create_card_missing_name(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers
|
|
|
|
|
):
|
2026-02-25 16:48:18 +00:00
|
|
|
"""Test creating a card without name"""
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/lists/{lst.id}/cards",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"description": "Test"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
def test_create_card_unauthorized(self, client, db_session, regular_user):
|
|
|
|
|
"""Test creating a card without authentication"""
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/lists/{lst.id}/cards",
|
|
|
|
|
json={"name": "New Card"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
def test_get_card_success(self, client, db_session, regular_user, auth_headers):
|
|
|
|
|
"""Test getting a single card with full details"""
|
|
|
|
|
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/cards/{card.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["id"] == card.id
|
|
|
|
|
assert data["name"] == "Test Card"
|
|
|
|
|
assert "labels" in data
|
|
|
|
|
assert "checklists" in data
|
|
|
|
|
assert "comments" in data
|
|
|
|
|
|
|
|
|
|
def test_get_card_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test getting a non-existent card"""
|
|
|
|
|
response = client.get("/api/cards/99999", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_update_card_success(self, client, db_session, regular_user, auth_headers):
|
|
|
|
|
"""Test updating a card"""
|
|
|
|
|
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)
|
|
|
|
|
db_session.add(lst1)
|
|
|
|
|
db_session.flush()
|
|
|
|
|
|
|
|
|
|
lst2 = List(name="Done", board_id=board.id, pos=1)
|
|
|
|
|
db_session.add(lst2)
|
|
|
|
|
db_session.flush()
|
|
|
|
|
|
|
|
|
|
card = Card(name="Original", board_id=board.id, list_id=lst1.id, pos=0)
|
|
|
|
|
db_session.add(card)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/cards/{card.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "Updated Name", "list_id": lst2.id, "pos": 1},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "Updated Name"
|
|
|
|
|
assert data["list_id"] == lst2.id
|
|
|
|
|
assert data["pos"] == 1
|
|
|
|
|
|
|
|
|
|
def test_update_card_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test updating a non-existent card"""
|
|
|
|
|
response = client.put(
|
|
|
|
|
"/api/cards/99999",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "Updated"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_delete_card_success(self, client, db_session, regular_user, auth_headers):
|
|
|
|
|
"""Test deleting a 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="To Delete", board_id=board.id, list_id=lst.id, pos=0)
|
|
|
|
|
db_session.add(card)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(f"/api/cards/{card.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "message" in data
|
|
|
|
|
|
|
|
|
|
# Verify card is deleted
|
|
|
|
|
deleted_card = db.session.get(Card, card.id)
|
|
|
|
|
assert deleted_card is None
|
|
|
|
|
|
|
|
|
|
def test_delete_card_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test deleting a non-existent card"""
|
|
|
|
|
response = client.delete("/api/cards/99999", headers=auth_headers)
|
|
|
|
|
|
2026-02-25 18:32:57 +00:00
|
|
|
assert response.status_code == 404
|
2026-02-27 10:54:34 +00:00
|
|
|
|
2026-02-27 12:41:44 +00:00
|
|
|
def test_update_card_position_within_same_list(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers
|
|
|
|
|
):
|
2026-02-27 10:54:34 +00:00
|
|
|
"""Test updating card position within the same list reorders other 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()
|
|
|
|
|
|
|
|
|
|
# Create 3 cards in sequential positions
|
|
|
|
|
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)
|
|
|
|
|
card3 = Card(name="Card 3", board_id=board.id, list_id=lst.id, pos=2)
|
|
|
|
|
db_session.add(card1)
|
|
|
|
|
db_session.add(card2)
|
|
|
|
|
db_session.add(card3)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Move card3 from position 2 to position 0 (top)
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/cards/{card3.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "Card 3", "pos": 0},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
# Verify all cards have unique, sequential positions
|
|
|
|
|
updated_cards = Card.query.filter_by(list_id=lst.id).order_by(Card.pos).all()
|
|
|
|
|
assert len(updated_cards) == 3
|
|
|
|
|
assert updated_cards[0].id == card3.id
|
|
|
|
|
assert updated_cards[0].pos == 0.0
|
|
|
|
|
assert updated_cards[1].id == card1.id
|
|
|
|
|
assert updated_cards[1].pos == 1.0
|
|
|
|
|
assert updated_cards[2].id == card2.id
|
|
|
|
|
assert updated_cards[2].pos == 2.0
|
|
|
|
|
|
2026-02-27 12:41:44 +00:00
|
|
|
def test_update_card_position_no_change(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers
|
|
|
|
|
):
|
2026-02-27 10:54:34 +00:00
|
|
|
"""Test updating card with same position doesn't reorder others"""
|
|
|
|
|
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(card1)
|
|
|
|
|
db_session.add(card2)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
original_pos1 = card1.pos
|
|
|
|
|
original_pos2 = card2.pos
|
|
|
|
|
|
|
|
|
|
# Update card2 but keep same position
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/cards/{card2.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "Updated Card 2", "pos": original_pos2},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
# Verify positions unchanged
|
|
|
|
|
updated_card1 = db.session.get(Card, card1.id)
|
|
|
|
|
updated_card2 = db.session.get(Card, card2.id)
|
|
|
|
|
assert updated_card1.pos == original_pos1
|
|
|
|
|
assert updated_card2.pos == original_pos2
|
|
|
|
|
|
2026-02-27 12:41:44 +00:00
|
|
|
def test_create_card_with_position(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers
|
|
|
|
|
):
|
2026-02-27 10:54:34 +00:00
|
|
|
"""Test creating card with specific position reorders existing 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()
|
|
|
|
|
|
|
|
|
|
# Create existing cards
|
|
|
|
|
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(card1)
|
|
|
|
|
db_session.add(card2)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Create new card at position 0 (should push others down)
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/lists/{lst.id}/cards",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "New Card", "pos": 0},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
|
|
|
|
|
# Note: create_card endpoint doesn't use CardPositionService yet
|
2026-02-27 12:41:44 +00:00
|
|
|
# This test documents current behavior - positions may not be unique
|
|
|
|
|
# after creation
|
2026-02-27 10:54:34 +00:00
|
|
|
# The reordering happens when cards are moved, not when created
|
|
|
|
|
all_cards = Card.query.filter_by(list_id=lst.id).order_by(Card.pos).all()
|
|
|
|
|
assert len(all_cards) == 3
|