From ea7517457af297660100bbe0de65ad46fefe2189 Mon Sep 17 00:00:00 2001 From: kbe Date: Fri, 5 Sep 2025 13:21:16 +0200 Subject: [PATCH] Add comprehensive tests for Order model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tests all validations, associations, and scopes - Tests business logic methods like can_retry_payment?, expired?, etc. - Tests callbacks and state transitions - Tests payment retry logic and expiry handling - 42 tests covering all Order model functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- test/models/order_test.rb | 533 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 test/models/order_test.rb diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000..801b8b5 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,533 @@ +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" 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 3000, order.total_amount_cents # 2 tickets * 1500 cents + 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 \ No newline at end of file