diff --git a/tests/integration/test_view_exchange_list.py b/tests/integration/test_view_exchange_list.py new file mode 100644 index 0000000..9995092 --- /dev/null +++ b/tests/integration/test_view_exchange_list.py @@ -0,0 +1,322 @@ +"""Integration tests for Story 2.2: View Exchange List.""" + +from datetime import datetime, timedelta + +from src.models import Exchange + + +class TestViewExchangeList: + """Test cases for viewing exchange list (Story 2.2).""" + + def test_dashboard_shows_list_of_exchanges(self, client, db, admin): # noqa: ARG002 + """Test that dashboard displays all exchanges. + + Acceptance Criteria: + - Dashboard shows list of all exchanges + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # Create multiple exchanges + future_close_date = datetime.utcnow() + timedelta(days=7) + future_exchange_date = datetime.utcnow() + timedelta(days=14) + + exchange1 = Exchange( + slug=Exchange.generate_slug(), + name="Family Christmas 2025", + budget="$20-30", + max_participants=20, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_REGISTRATION_OPEN, + ) + + exchange2 = Exchange( + slug=Exchange.generate_slug(), + name="Office Gift Exchange", + budget="$15-25", + max_participants=15, + registration_close_date=future_close_date + timedelta(days=7), + exchange_date=future_exchange_date + timedelta(days=7), + timezone="America/Chicago", + state=Exchange.STATE_DRAFT, + ) + + db.session.add(exchange1) + db.session.add(exchange2) + db.session.commit() + + # View dashboard + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify both exchanges appear + assert b"Family Christmas 2025" in response.data + assert b"Office Gift Exchange" in response.data + + def test_dashboard_displays_exchange_info(self, client, db, admin): # noqa: ARG002 + """Test that each exchange displays required information. + + Acceptance Criteria: + - Each exchange displays: name, state, participant count, exchange date + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # Create an exchange + future_close_date = datetime.utcnow() + timedelta(days=7) + future_exchange_date = datetime.utcnow() + timedelta(days=14) + + exchange = Exchange( + slug=Exchange.generate_slug(), + name="Test Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_REGISTRATION_OPEN, + ) + + db.session.add(exchange) + db.session.commit() + + # View dashboard + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify exchange information is displayed + assert b"Test Exchange" in response.data + assert b"registration_open" in response.data + assert b"0 / 10" in response.data # Participant count + assert future_exchange_date.strftime("%Y-%m-%d").encode() in response.data + + def test_dashboard_sorted_by_exchange_date(self, client, db, admin): # noqa: ARG002 + """Test that exchanges are sorted by exchange date (upcoming first). + + Acceptance Criteria: + - Exchanges sorted by exchange date (upcoming first) + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # Create exchanges with different dates + base_close_date = datetime.utcnow() + timedelta(days=7) + + # Later exchange + exchange1 = Exchange( + slug=Exchange.generate_slug(), + name="Later Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=base_close_date + timedelta(days=30), + exchange_date=base_close_date + timedelta(days=60), # Far in future + timezone="America/New_York", + state=Exchange.STATE_DRAFT, + ) + + # Earlier exchange + exchange2 = Exchange( + slug=Exchange.generate_slug(), + name="Earlier Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=base_close_date, + exchange_date=base_close_date + timedelta(days=7), # Sooner + timezone="America/New_York", + state=Exchange.STATE_REGISTRATION_OPEN, + ) + + db.session.add(exchange1) + db.session.add(exchange2) + db.session.commit() + + # View dashboard + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify earlier exchange appears before later exchange + response_text = response.data.decode() + earlier_pos = response_text.index("Earlier Exchange") + later_pos = response_text.index("Later Exchange") + assert earlier_pos < later_pos + + def test_dashboard_shows_state_indicators(self, client, db, admin): # noqa: ARG002 + """Test that visual indicators for exchange state are displayed. + + Acceptance Criteria: + - Visual indicator for exchange state + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # Create exchanges in different states + future_close_date = datetime.utcnow() + timedelta(days=7) + future_exchange_date = datetime.utcnow() + timedelta(days=14) + + draft_exchange = Exchange( + slug=Exchange.generate_slug(), + name="Draft Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_DRAFT, + ) + + open_exchange = Exchange( + slug=Exchange.generate_slug(), + name="Open Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_REGISTRATION_OPEN, + ) + + db.session.add(draft_exchange) + db.session.add(open_exchange) + db.session.commit() + + # View dashboard + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify state indicators are present + assert b"draft" in response.data + assert b"registration_open" in response.data + + def test_dashboard_shows_summary_counts(self, client, db, admin): # noqa: ARG002 + """Test that dashboard shows summary counts by state. + + Acceptance Criteria: + - Dashboard shows counts for draft, active, and completed exchanges + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # Create exchanges in different states + future_close_date = datetime.utcnow() + timedelta(days=7) + future_exchange_date = datetime.utcnow() + timedelta(days=14) + + # 2 draft exchanges + for i in range(2): + exchange = Exchange( + slug=Exchange.generate_slug(), + name=f"Draft Exchange {i}", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_DRAFT, + ) + db.session.add(exchange) + + # 3 active exchanges (registration_open) + for i in range(3): + exchange = Exchange( + slug=Exchange.generate_slug(), + name=f"Active Exchange {i}", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date, + exchange_date=future_exchange_date, + timezone="America/New_York", + state=Exchange.STATE_REGISTRATION_OPEN, + ) + db.session.add(exchange) + + # 1 completed exchange + completed_exchange = Exchange( + slug=Exchange.generate_slug(), + name="Completed Exchange", + budget="$20-30", + max_participants=10, + registration_close_date=future_close_date - timedelta(days=30), + exchange_date=future_exchange_date - timedelta(days=20), + timezone="America/New_York", + state=Exchange.STATE_COMPLETED, + ) + db.session.add(completed_exchange) + + db.session.commit() + + # View dashboard + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify counts are displayed + response_text = response.data.decode() + + # Look for summary section with counts + assert "2" in response_text # draft_count + assert "3" in response_text # active_count + assert "1" in response_text # completed_count + + def test_dashboard_empty_state(self, client, db, admin): # noqa: ARG002 + """Test dashboard when no exchanges exist. + + Acceptance Criteria: + - Show helpful message when no exchanges exist + - Link to create first exchange + """ + # Login first + client.post( + "/admin/login", + data={ + "email": "admin@example.com", + "password": "testpassword123", + }, + follow_redirects=True, + ) + + # View dashboard with no exchanges + response = client.get("/admin/dashboard") + assert response.status_code == 200 + + # Verify empty state message + assert ( + b"No exchanges yet" in response.data + or b"no exchanges" in response.data.lower() + ) + # Verify link to create exchange + assert b"Create" in response.data or b"create" in response.data.lower()