require "test_helper" class OrderTest < ActiveSupport::TestCase 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 ) end # === Basic Model Tests === test "should be a class" do assert_kind_of Class, Order end # === Constants Tests === test "should have correct constants defined" do assert_equal 30.minutes, Order::DRAFT_EXPIRY_TIME assert_equal 3, Order::MAX_PAYMENT_ATTEMPTS end # === Association Tests === test "should belong to user" do association = Order.reflect_on_association(:user) assert_equal :belongs_to, association.macro end test "should belong to event" do association = Order.reflect_on_association(:event) assert_equal :belongs_to, association.macro end test "should have many tickets with dependent destroy" do association = Order.reflect_on_association(:tickets) assert_equal :has_many, association.macro assert_equal :destroy, association.options[:dependent] end # === Validation Tests === test "should not save order without user" do order = Order.new(event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0) assert_not order.save assert_includes order.errors[:user_id], "can't be blank" end test "should not save order without event" do order = Order.new(user: @user, total_amount_cents: 1000, status: "draft", payment_attempts: 0) assert_not order.save assert_includes order.errors[:event_id], "can't be blank" end test "should use default status when not provided" do order = Order.new(user: @user, event: @event) order.save! assert_equal "draft", order.status end test "should not save order with invalid status" do order = Order.new( user: @user, event: @event, total_amount_cents: 1000, status: "invalid_status", payment_attempts: 0 ) assert_not order.save assert_includes order.errors[:status], "is not included in the list" end test "should save order with valid statuses" do valid_statuses = %w[draft pending_payment paid completed cancelled expired] valid_statuses.each do |status| order = Order.new( user: @user, event: @event, total_amount_cents: 1000, status: status, payment_attempts: 0 ) assert order.save, "Should save with status: #{status}" end end test "should use default total_amount_cents when not provided" do order = Order.new(user: @user, event: @event) order.save! assert_equal 0, order.total_amount_cents end test "should not save order with negative total_amount_cents" do order = Order.new( user: @user, event: @event, total_amount_cents: -100 ) assert_not order.save assert_includes order.errors[:total_amount_cents], "must be greater than or equal to 0" end test "should save order with zero total_amount_cents" do order = Order.new( user: @user, event: @event, total_amount_cents: 0 ) assert order.save end test "should use default payment_attempts when not provided" do order = Order.new(user: @user, event: @event) order.save! assert_equal 0, order.payment_attempts end test "should not save order with negative payment_attempts" do order = Order.new( user: @user, event: @event, payment_attempts: -1 ) assert_not order.save assert_includes order.errors[:payment_attempts], "must be greater than or equal to 0" end # === Callback Tests === test "should set expiry time for draft order on create" do order = Order.new( user: @user, event: @event ) assert_nil order.expires_at order.save! assert_not_nil order.expires_at assert_in_delta Time.current + Order::DRAFT_EXPIRY_TIME, order.expires_at, 5.seconds end test "should not set expiry time for non-draft order on create" do order = Order.new( user: @user, event: @event, status: "paid" ) order.save! assert_nil order.expires_at end test "should not override existing expires_at on create" do custom_expiry = 1.hour.from_now order = Order.new( user: @user, event: @event, expires_at: custom_expiry ) order.save! assert_equal custom_expiry.to_i, order.expires_at.to_i end # === Scope Tests === test "draft scope should return only draft orders" do draft_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) paid_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0 ) draft_orders = Order.draft assert_includes draft_orders, draft_order assert_not_includes draft_orders, paid_order end test "active scope should return paid and completed orders" do draft_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) paid_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0 ) completed_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "completed", payment_attempts: 0 ) active_orders = Order.active assert_not_includes active_orders, draft_order assert_includes active_orders, paid_order assert_includes active_orders, completed_order end test "expired_drafts scope should return expired draft orders" do # Create an expired draft order expired_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0, expires_at: 1.hour.ago ) # Create a non-expired draft order active_draft = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) expired_drafts = Order.expired_drafts assert_includes expired_drafts, expired_order assert_not_includes expired_drafts, active_draft end test "can_retry_payment scope should return retryable orders" do # Create a retryable order retryable_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 1 ) # Create a non-retryable order (too many attempts) max_attempts_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: Order::MAX_PAYMENT_ATTEMPTS ) # Create an expired order expired_order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 1, expires_at: 1.hour.ago ) retryable_orders = Order.can_retry_payment assert_includes retryable_orders, retryable_order assert_not_includes retryable_orders, max_attempts_order assert_not_includes retryable_orders, expired_order end # === Instance Method Tests === test "total_amount_euros should convert cents to euros" do order = Order.new(total_amount_cents: 1500) assert_equal 15.0, order.total_amount_euros order = Order.new(total_amount_cents: 1050) assert_equal 10.5, order.total_amount_euros end test "can_retry_payment? should return true for retryable orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 1 ) assert order.can_retry_payment? end test "can_retry_payment? should return false for non-draft orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 1 ) assert_not order.can_retry_payment? end test "can_retry_payment? should return false for max attempts reached" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: Order::MAX_PAYMENT_ATTEMPTS ) assert_not order.can_retry_payment? end test "can_retry_payment? should return false for expired orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 1, expires_at: 1.hour.ago ) assert_not order.can_retry_payment? end test "expired? should return true for expired orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0, expires_at: 1.hour.ago ) assert order.expired? end test "expired? should return false for non-expired orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) assert_not order.expired? end test "expired? should return false when expires_at is nil" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0 ) assert_not order.expired? end test "expire_if_overdue! should mark expired draft as expired" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0, expires_at: 1.hour.ago ) order.expire_if_overdue! order.reload assert_equal "expired", order.status end test "expire_if_overdue! should not affect non-draft orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0, expires_at: 1.hour.ago ) order.expire_if_overdue! order.reload assert_equal "paid", order.status end test "expire_if_overdue! should not affect non-expired orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) order.expire_if_overdue! order.reload assert_equal "draft", order.status end test "increment_payment_attempt! should increment counter and set timestamp" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) assert_nil order.last_payment_attempt_at order.increment_payment_attempt! order.reload assert_equal 1, order.payment_attempts assert_not_nil order.last_payment_attempt_at assert_in_delta Time.current, order.last_payment_attempt_at, 5.seconds end test "expiring_soon? should return true for orders expiring within 5 minutes" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0, expires_at: 3.minutes.from_now ) assert order.expiring_soon? end test "expiring_soon? should return false for orders expiring later" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0, expires_at: 10.minutes.from_now ) assert_not order.expiring_soon? end test "expiring_soon? should return false for non-draft orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0, expires_at: 3.minutes.from_now ) assert_not order.expiring_soon? end test "expiring_soon? should return false when expires_at is nil" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) order.update_column(:expires_at, nil) # Bypass validation to test edge case assert_not order.expiring_soon? end test "mark_as_paid! should update status and activate tickets" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) # Create some tickets for the order ticket_type = TicketType.create!( name: "Test Ticket Type", description: "A valid description for the ticket type that is long enough", price_cents: 1000, quantity: 10, sale_start_at: Time.current, sale_end_at: Time.current + 1.day, requires_id: false, event: @event ) ticket1 = Ticket.create!( order: order, ticket_type: ticket_type, status: "draft", first_name: "John", last_name: "Doe" ) ticket2 = Ticket.create!( order: order, ticket_type: ticket_type, status: "draft", first_name: "Jane", last_name: "Doe" ) order.mark_as_paid! order.reload ticket1.reload ticket2.reload assert_equal "paid", order.status assert_equal "active", ticket1.status assert_equal "active", ticket2.status end test "calculate_total! should sum ticket prices plus 1€ service fee" do order = Order.create!( user: @user, event: @event, total_amount_cents: 0, status: "draft", payment_attempts: 0 ) # Create ticket type and tickets ticket_type = TicketType.create!( name: "Test Ticket Type", description: "A valid description for the ticket type that is long enough", price_cents: 1500, quantity: 10, sale_start_at: Time.current, sale_end_at: Time.current + 1.day, requires_id: false, event: @event ) Ticket.create!( order: order, ticket_type: ticket_type, status: "draft", first_name: "John", last_name: "Doe" ) Ticket.create!( order: order, ticket_type: ticket_type, status: "draft", first_name: "Jane", last_name: "Doe" ) order.calculate_total! order.reload assert_equal 3100, order.total_amount_cents # 2 tickets * 1500 cents + 100 cents (1€ fee) end # === Stripe Integration Tests (Mock) === test "create_stripe_invoice! should return nil for non-paid orders" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "draft", payment_attempts: 0 ) result = order.create_stripe_invoice! assert_nil result end test "stripe_invoice_pdf_url should return nil when no invoice ID present" do order = Order.create!( user: @user, event: @event, total_amount_cents: 1000, status: "paid", payment_attempts: 0 ) result = order.stripe_invoice_pdf_url assert_nil result end end