require "test_helper" class OrdersControllerTest < ActionDispatch::IntegrationTest def setup @user = User.create!( email: "test@example.com", password: "password123", password_confirmation: "password123" ) @event = Event.create!( name: "Test Event", slug: "test-event", description: "A valid description for the test event that is long enough", latitude: 48.8566, longitude: 2.3522, venue_name: "Test Venue", venue_address: "123 Test Street", user: @user, start_time: 1.week.from_now, end_time: 1.week.from_now + 3.hours, state: :published ) @ticket_type = TicketType.create!( name: "General Admission", description: "General admission tickets with full access to the event", price_cents: 2500, quantity: 100, sale_start_at: Time.current, sale_end_at: @event.start_time - 1.hour, requires_id: false, event: @event ) @order = Order.create!( user: @user, event: @event, status: "draft", total_amount_cents: 2500 ) @ticket = Ticket.create!( order: @order, ticket_type: @ticket_type, status: "draft", first_name: "John", last_name: "Doe" ) sign_in @user end # === Authentication Tests === test "should require authentication for all actions" do sign_out @user get event_order_new_path(@event.slug, @event.id) assert_redirected_to new_user_session_path post event_order_create_path(@event.slug, @event.id) assert_redirected_to new_user_session_path get order_path(@order) assert_redirected_to new_user_session_path get checkout_order_path(@order) assert_redirected_to new_user_session_path end # === New Action Tests === test "should get new with valid event" do # Pass cart data as parameter for testing get event_order_new_path(@event.slug, @event.id), params: { cart_data: { @ticket_type.id.to_s => { "quantity" => "2" } } } assert_response :success # Should assign tickets_needing_names tickets_needing_names = assigns(:tickets_needing_names) assert_not_nil tickets_needing_names assert_equal 2, tickets_needing_names.size assert_equal @ticket_type.id, tickets_needing_names.first[:ticket_type_id] end test "new should redirect when cart is empty" do # Pass empty cart data as parameter get event_order_new_path(@event.slug, @event.id), params: { cart_data: {} } assert_redirected_to event_path(@event.slug, @event) assert_match /sélectionner vos billets/, flash[:alert] end test "new should redirect when no cart data" do # No cart data passed as parameter get event_order_new_path(@event.slug, @event.id) assert_redirected_to event_path(@event.slug, @event) assert_match /sélectionner vos billets/, flash[:alert] end # === Create Action Tests === test "should create order with valid ticket data" do assert_difference "Order.count", 1 do assert_difference "Ticket.count", 1 do post event_order_create_path(@event.slug, @event.id), params: { cart_data: { @ticket_type.id.to_s => { "quantity" => "1" } }, tickets_attributes: { "0" => { ticket_type_id: @ticket_type.id, first_name: "Jane", last_name: "Smith" } } } end end new_order = Order.last assert_equal "draft", new_order.status assert_equal @user, new_order.user assert_equal @event, new_order.event assert_equal @ticket_type.price_cents + 100, new_order.total_amount_cents # includes 1€ service fee assert_redirected_to checkout_order_path(new_order) assert_equal new_order.id, session[:draft_order_id] assert_nil session[:pending_cart] end test "create should redirect when cart is empty" do assert_no_difference "Order.count" do post event_order_create_path(@event.slug, @event.id), params: { cart_data: {} } end assert_redirected_to event_path(@event.slug, @event) assert_match /Aucun billet sélectionné/, flash[:alert] end test "create should handle missing ticket names" do post event_order_create_path(@event.slug, @event.id), params: { cart_data: { @ticket_type.id.to_s => { "quantity" => "1" } }, tickets_attributes: { "0" => { ticket_type_id: @ticket_type.id, first_name: "", last_name: "" } } } # Should redirect back to new order page assert_redirected_to event_order_new_path(@event.slug, @event.id) assert_match /Aucun billet valide créé/, flash[:alert] end # === Show Action Tests === test "should show order" do get order_path(@order) assert_response :success order = assigns(:order) assert_equal @order, order tickets = assigns(:tickets) assert_includes tickets, @ticket end test "should not show other user's order" do other_user = User.create!( email: "other@example.com", password: "password123", password_confirmation: "password123" ) other_order = Order.create!( user: other_user, event: @event, status: "draft", total_amount_cents: 2500 ) get order_path(other_order) # Should redirect to dashboard/root with alert assert_redirected_to root_path assert_match /Commande non trouvée/, flash[:alert] end # === Checkout Action Tests === test "should show checkout page" do get checkout_order_path(@order) assert_response :success order = assigns(:order) assert_equal @order, order tickets = assigns(:tickets) assert_includes tickets, @ticket total_amount = assigns(:total_amount) assert_equal @order.total_amount_cents, total_amount expiring_soon = assigns(:expiring_soon) assert_not_nil expiring_soon end test "checkout should redirect expired order" do # Make order expired @order.update!(expires_at: 1.hour.ago) get checkout_order_path(@order) assert_redirected_to event_path(@event.slug, @event) assert_match /commande a expiré/, flash[:alert] @order.reload assert_equal "expired", @order.status end # === Retry Payment Tests === test "should allow retry payment for retryable order" do post retry_payment_order_path(@order) assert_redirected_to checkout_order_path(@order) end test "should not allow retry payment for non-retryable order" do # Make order non-retryable (too many attempts) @order.update!(payment_attempts: Order::MAX_PAYMENT_ATTEMPTS) post retry_payment_order_path(@order) assert_redirected_to event_path(@event.slug, @event) assert_match /ne peut plus être payée/, flash[:alert] end # === Increment Payment Attempt Tests === test "should increment payment attempt via AJAX" do initial_attempts = @order.payment_attempts post increment_payment_attempt_order_path(@order), xhr: true assert_response :success response_data = JSON.parse(@response.body) assert response_data["success"] assert_equal initial_attempts + 1, response_data["attempts"] @order.reload assert_equal initial_attempts + 1, @order.payment_attempts assert_not_nil @order.last_payment_attempt_at end # === Payment Success Tests (simplified) === test "payment_success should redirect when Stripe not configured" do # Mock the config to return nil Rails.application.config.stripe = { secret_key: nil } get order_payment_success_path, params: { session_id: "test_session" } assert_redirected_to root_path assert_match /système de paiement n'est pas correctement configuré/, flash[:alert] end # === Payment Cancel Tests === test "payment_cancel should redirect to checkout if order can retry" do get order_payment_cancel_path, params: { order_id: @order.id } assert_redirected_to checkout_order_path(@order) assert_match /paiement a été annulé.*réessayer/, flash[:alert] end test "payment_cancel should redirect to root if no order in session" do get order_payment_cancel_path assert_redirected_to root_path assert_match /paiement a été annulé/, flash[:alert] end # === Error Handling Tests === test "should handle non-existent event in new" do get event_order_new_path(@event.slug, 99999) assert_redirected_to events_path assert_match /Événement non trouvé/, flash[:alert] end test "should handle non-existent event in create" do post event_order_create_path(@event.slug, 99999) assert_redirected_to events_path assert_match /Événement non trouvé/, flash[:alert] end test "should handle non-existent order" do get order_path(99999) assert_redirected_to root_path assert_match /Commande non trouvée/, flash[:alert] end # === Route Helper Tests === test "should have correct route helpers" do # Test that the route helpers exist and work assert_not_nil event_order_new_path(@event.slug, @event.id) assert_not_nil event_order_create_path(@event.slug, @event.id) assert_not_nil order_path(@order) assert_not_nil checkout_order_path(@order) assert_not_nil retry_payment_order_path(@order) assert_not_nil increment_payment_attempt_order_path(@order) end end