"""Integration tests for Story 2.1: Create Exchange.""" from datetime import datetime, timedelta from src.models import Exchange class TestCreateExchange: """Test cases for exchange creation flow (Story 2.1).""" def test_create_exchange_form_renders(self, client, db, admin): # noqa: ARG002 """Test that create exchange form renders correctly. Acceptance Criteria: - Form to create exchange with all required fields """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) response = client.get("/admin/exchange/new") assert response.status_code == 200 # Check for all required form fields assert b"name" in response.data.lower() assert b"description" in response.data.lower() assert b"budget" in response.data.lower() assert b"max_participants" in response.data.lower() assert b"registration_close_date" in response.data.lower() assert b"exchange_date" in response.data.lower() assert b"timezone" in response.data.lower() def test_create_exchange_with_valid_data(self, client, db, admin): # noqa: ARG002 """Test creating exchange with valid data. Acceptance Criteria: - Exchange created in "Draft" state - Exchange appears in admin dashboard after creation - Generate unique 12-char slug """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) # Create exchange future_close_date = (datetime.utcnow() + timedelta(days=7)).strftime( "%Y-%m-%dT%H:%M" ) future_exchange_date = (datetime.utcnow() + timedelta(days=14)).strftime( "%Y-%m-%dT%H:%M" ) response = client.post( "/admin/exchange/new", data={ "name": "Test Exchange", "description": "This is a test exchange", "budget": "$20-30", "max_participants": 10, "registration_close_date": future_close_date, "exchange_date": future_exchange_date, "timezone": "America/New_York", }, follow_redirects=False, ) # Should redirect to exchange detail page assert response.status_code == 302 # Verify exchange was created exchange = db.session.query(Exchange).filter_by(name="Test Exchange").first() assert exchange is not None assert exchange.slug is not None assert len(exchange.slug) == 12 assert exchange.state == Exchange.STATE_DRAFT assert exchange.description == "This is a test exchange" assert exchange.budget == "$20-30" assert exchange.max_participants == 10 assert exchange.timezone == "America/New_York" def test_create_exchange_minimum_participants_validation(self, client, db, admin): # noqa: ARG002 """Test that max_participants must be at least 3. Acceptance Criteria: - Maximum participants (required, minimum 3) """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) future_close_date = (datetime.utcnow() + timedelta(days=7)).strftime( "%Y-%m-%dT%H:%M" ) future_exchange_date = (datetime.utcnow() + timedelta(days=14)).strftime( "%Y-%m-%dT%H:%M" ) # Try to create with less than 3 participants response = client.post( "/admin/exchange/new", data={ "name": "Test Exchange", "budget": "$20-30", "max_participants": 2, # Invalid: less than 3 "registration_close_date": future_close_date, "exchange_date": future_exchange_date, "timezone": "America/New_York", }, follow_redirects=True, ) # Should show error assert response.status_code == 200 assert ( b"at least 3" in response.data.lower() or b"minimum" in response.data.lower() ) # Verify no exchange was created exchange = db.session.query(Exchange).filter_by(name="Test Exchange").first() assert exchange is None def test_create_exchange_date_validation(self, client, db, admin): # noqa: ARG002 """Test that exchange_date must be after registration_close_date. Acceptance Criteria: - Registration close date (required) - Exchange date (required) - Dates must be properly ordered """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) future_close_date = (datetime.utcnow() + timedelta(days=14)).strftime( "%Y-%m-%dT%H:%M" ) earlier_exchange_date = (datetime.utcnow() + timedelta(days=7)).strftime( "%Y-%m-%dT%H:%M" ) # Try to create with exchange date before close date response = client.post( "/admin/exchange/new", data={ "name": "Test Exchange", "budget": "$20-30", "max_participants": 10, "registration_close_date": future_close_date, "exchange_date": earlier_exchange_date, # Before close date "timezone": "America/New_York", }, follow_redirects=True, ) # Should show error assert response.status_code == 200 assert ( b"exchange date" in response.data.lower() and b"after" in response.data.lower() ) or b"must be after" in response.data.lower() def test_create_exchange_timezone_validation(self, client, db, admin): # noqa: ARG002 """Test that timezone must be valid. Acceptance Criteria: - Timezone (required) - Validate timezone with pytz """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) future_close_date = (datetime.utcnow() + timedelta(days=7)).strftime( "%Y-%m-%dT%H:%M" ) future_exchange_date = (datetime.utcnow() + timedelta(days=14)).strftime( "%Y-%m-%dT%H:%M" ) # Try to create with invalid timezone response = client.post( "/admin/exchange/new", data={ "name": "Test Exchange", "budget": "$20-30", "max_participants": 10, "registration_close_date": future_close_date, "exchange_date": future_exchange_date, "timezone": "Invalid/Timezone", }, follow_redirects=True, ) # Should show error assert response.status_code == 200 assert ( b"timezone" in response.data.lower() or b"invalid" in response.data.lower() ) def test_create_exchange_slug_is_unique(self, client, db, admin): # noqa: ARG002 """Test that each exchange gets a unique slug. Acceptance Criteria: - Generate unique 12-char slug """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) future_close_date = (datetime.utcnow() + timedelta(days=7)).strftime( "%Y-%m-%dT%H:%M" ) future_exchange_date = (datetime.utcnow() + timedelta(days=14)).strftime( "%Y-%m-%dT%H:%M" ) # Create first exchange client.post( "/admin/exchange/new", data={ "name": "Exchange 1", "budget": "$20-30", "max_participants": 10, "registration_close_date": future_close_date, "exchange_date": future_exchange_date, "timezone": "America/New_York", }, follow_redirects=True, ) # Create second exchange client.post( "/admin/exchange/new", data={ "name": "Exchange 2", "budget": "$20-30", "max_participants": 10, "registration_close_date": future_close_date, "exchange_date": future_exchange_date, "timezone": "America/New_York", }, follow_redirects=True, ) # Verify both have unique slugs exchanges = db.session.query(Exchange).all() assert len(exchanges) == 2 assert exchanges[0].slug != exchanges[1].slug def test_create_exchange_required_fields(self, client, db, admin): # noqa: ARG002 """Test that all required fields are validated. Acceptance Criteria: - Name (required) - Budget (required) - All other required fields """ # Login first client.post( "/admin/login", data={ "email": "admin@example.com", "password": "testpassword123", }, follow_redirects=True, ) # Try to create without required fields response = client.post( "/admin/exchange/new", data={}, follow_redirects=True, ) # Should show errors assert response.status_code == 200 # Verify no exchange was created exchange_count = db.session.query(Exchange).count() assert exchange_count == 0 def test_create_exchange_slug_generation(self): """Test the slug generation method. Acceptance Criteria: - Generate unique 12-char slug - Slug should be URL-safe alphanumeric """ slug1 = Exchange.generate_slug() slug2 = Exchange.generate_slug() # Verify length assert len(slug1) == 12 assert len(slug2) == 12 # Verify uniqueness (statistically very likely) assert slug1 != slug2 # Verify alphanumeric (letters and digits only) assert slug1.isalnum() assert slug2.isalnum()