require "test_helper" class StripeInvoiceServiceTest < ActiveSupport::TestCase def setup @user = User.create!( email: "test@example.com", password: "password123", first_name: "John", last_name: "Doe" ) @event = Event.create!( name: "Test Concert", slug: "test-concert", description: "A test event", state: "published", venue_name: "Test Venue", venue_address: "123 Test St", latitude: 40.7128, longitude: -74.0060, start_time: 1.week.from_now, end_time: 1.week.from_now + 4.hours, user: @user ) @ticket_type = @event.ticket_types.create!( name: "Standard", description: "Standard admission ticket with general access", price_cents: 1000, quantity: 100, sale_start_at: 1.day.ago, sale_end_at: 1.day.from_now ) @order = @user.orders.create!( event: @event, status: "paid", total_amount_cents: 1000 ) @ticket = @order.tickets.create!( ticket_type: @ticket_type, first_name: "John", last_name: "Doe", status: "active", price_cents: 1000 ) @service = StripeInvoiceService.new(@order) end test "should validate order requirements" do # Test with nil order service = StripeInvoiceService.new(nil) result = service.create_post_payment_invoice assert_nil result assert_includes service.errors, "Order is required" # Test with unpaid order draft_order = @user.orders.create!( event: @event, status: "draft", total_amount_cents: 1000 ) service = StripeInvoiceService.new(draft_order) result = service.create_post_payment_invoice assert_nil result assert_includes service.errors, "Order must be paid to create invoice" end test "should return error for order without tickets" do order_without_tickets = @user.orders.create!( event: @event, status: "paid", total_amount_cents: 0 ) service = StripeInvoiceService.new(order_without_tickets) result = service.create_post_payment_invoice assert_nil result assert_includes service.errors, "Order must have tickets to create invoice" end test "get_invoice_pdf_url handles invalid invoice_id gracefully" do result = StripeInvoiceService.get_invoice_pdf_url("invalid_id") assert_nil result result = StripeInvoiceService.get_invoice_pdf_url(nil) assert_nil result result = StripeInvoiceService.get_invoice_pdf_url("") assert_nil result end test "customer_name handles various user data combinations" do # Test with first and last name @user.update(first_name: "John", last_name: "Doe") service = StripeInvoiceService.new(@order) assert_equal "John Doe", service.send(:customer_name) # Test with email only @user.update(first_name: nil, last_name: nil) service = StripeInvoiceService.new(@order) result = service.send(:customer_name) assert result.present? assert_includes result.downcase, @user.email.split("@").first.downcase end test "build_line_item_description formats correctly" do tickets = [ @ticket ] service = StripeInvoiceService.new(@order) description = service.send(:build_line_item_description, @ticket_type, tickets) assert_includes description, @event.name assert_includes description, @ticket_type.name assert_includes description, "€" end # === Additional Comprehensive Tests === test "should initialize with correct attributes" do assert_equal @order, @service.order assert_empty @service.errors end test "should validate order has user" do order_without_user = Order.new( event: @event, status: "paid", total_amount_cents: 1000 ) # Don't save to database to avoid NOT NULL constraint, just test the validation logic service = StripeInvoiceService.new(order_without_user) result = service.create_post_payment_invoice assert_nil result assert_includes service.errors, "Order must have an associated user" end test "should handle Stripe customer creation with existing customer ID" do @user.update!(stripe_customer_id: "cus_existing123") mock_customer = mock("customer") mock_customer.stubs(:id).returns("cus_existing123") Stripe::Customer.expects(:retrieve).with("cus_existing123").returns(mock_customer) # Mock the rest of the invoice creation process mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_invoice.stubs(:finalize_invoice).returns(mock_invoice) mock_invoice.expects(:pay) Stripe::Invoice.expects(:create).returns(mock_invoice) Stripe::InvoiceItem.expects(:create).once result = @service.create_post_payment_invoice assert_not_nil result end test "should handle invalid existing Stripe customer" do @user.update!(stripe_customer_id: "cus_invalid123") # First call fails, then create new customer Stripe::Customer.expects(:retrieve).with("cus_invalid123").raises(Stripe::InvalidRequestError.new("message", "param")) mock_customer = mock("customer") mock_customer.stubs(:id).returns("cus_new123") Stripe::Customer.expects(:create).returns(mock_customer) # Mock the rest of the invoice creation process mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_invoice.stubs(:finalize_invoice).returns(mock_invoice) mock_invoice.expects(:pay) Stripe::Invoice.expects(:create).returns(mock_invoice) Stripe::InvoiceItem.expects(:create).once result = @service.create_post_payment_invoice assert_not_nil result @user.reload assert_equal "cus_new123", @user.stripe_customer_id end test "should handle multiple tickets of same type" do # Create another ticket of the same type ticket2 = @order.tickets.create!( ticket_type: @ticket_type, first_name: "Jane", last_name: "Doe", status: "active", price_cents: 1000 ) mock_customer = mock("customer") mock_customer.stubs(:id).returns("cus_test123") Stripe::Customer.expects(:create).returns(mock_customer) expected_line_item = { customer: "cus_test123", invoice: "in_test123", amount: @ticket_type.price_cents * 2, # 2 tickets currency: "eur", description: "#{@event.name} - #{@ticket_type.name} - (2x €#{@ticket_type.price_cents / 100.0})", metadata: { ticket_type_id: @ticket_type.id, ticket_type_name: @ticket_type.name, quantity: 2, unit_price_cents: @ticket_type.price_cents } } mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_invoice.stubs(:finalize_invoice).returns(mock_invoice) mock_invoice.expects(:pay) Stripe::Invoice.expects(:create).returns(mock_invoice) Stripe::InvoiceItem.expects(:create).with(expected_line_item) result = @service.create_post_payment_invoice assert_not_nil result end test "should create invoice with correct metadata" do mock_customer = mock("customer") mock_customer.stubs(:id).returns("cus_test123") Stripe::Customer.expects(:create).returns(mock_customer) expected_invoice_data = { customer: "cus_test123", collection_method: "send_invoice", auto_advance: false, metadata: { order_id: @order.id, user_id: @user.id, event_name: @event.name, created_by: "aperonight_system", payment_method: "checkout_session" }, description: "Invoice for #{@event.name} - Order ##{@order.id}", footer: "Thank you for your purchase! This invoice is for your records as payment was already processed.", due_date: anything } mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_invoice.stubs(:finalize_invoice).returns(mock_invoice) mock_invoice.expects(:pay) Stripe::Invoice.expects(:create).with(expected_invoice_data).returns(mock_invoice) Stripe::InvoiceItem.expects(:create).once result = @service.create_post_payment_invoice assert_not_nil result end test "should handle Stripe errors gracefully" do Stripe::Customer.expects(:create).raises(Stripe::StripeError.new("Test Stripe error")) result = @service.create_post_payment_invoice assert_nil result assert_includes @service.errors, "Stripe invoice creation failed: Test Stripe error" end test "should handle generic errors gracefully" do Stripe::Customer.expects(:create).raises(StandardError.new("Generic error")) result = @service.create_post_payment_invoice assert_nil result assert_includes @service.errors, "Invoice creation failed: Generic error" end test "should finalize and mark invoice as paid" do mock_customer = mock("customer") mock_customer.stubs(:id).returns("cus_test123") Stripe::Customer.expects(:create).returns(mock_customer) mock_invoice = mock("invoice") mock_invoice.stubs(:id).returns("in_test123") mock_finalized_invoice = mock("finalized_invoice") mock_finalized_invoice.expects(:pay).with({ paid_out_of_band: true, payment_method: nil }) Stripe::Invoice.expects(:create).returns(mock_invoice) Stripe::InvoiceItem.expects(:create).once mock_invoice.expects(:finalize_invoice).returns(mock_finalized_invoice) result = @service.create_post_payment_invoice assert_equal mock_invoice, result end # === Class Method Tests === test "get_invoice_pdf_url should return PDF URL for valid invoice" do mock_invoice = mock("invoice") mock_invoice.expects(:invoice_pdf).returns("https://stripe.com/invoice.pdf") Stripe::Invoice.expects(:retrieve).with("in_test123").returns(mock_invoice) url = StripeInvoiceService.get_invoice_pdf_url("in_test123") assert_equal "https://stripe.com/invoice.pdf", url end test "get_invoice_pdf_url should handle Stripe errors" do Stripe::Invoice.expects(:retrieve).with("in_invalid").raises(Stripe::StripeError.new("Not found")) url = StripeInvoiceService.get_invoice_pdf_url("in_invalid") assert_nil url end end