diff --git a/BACKLOG.md b/BACKLOG.md index fe3794b..5a4e939 100755 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -9,14 +9,11 @@ ### Medium Priority - [ ] feat: Promoter system with event creation, ticket types creation and metrics display -- [ ] feat: Multiple ticket types (early bird, VIP, general admission) - [ ] feat: Refund management system - [ ] feat: Real-time sales analytics dashboard - [ ] feat: Guest checkout without account creation - [ ] feat: Seat selection with interactive venue maps - [ ] feat: Dynamic pricing based on demand -- [ ] feat: Profesionnal account. User can ask to change from a customer to a professionnal account to create and manage events. -- [ ] feat: User can choose to create a professionnal account on sign-up page to be allowed to create and manage events - [ ] feat: Payout system for promoters (automated/manual payment processing) - [ ] feat: Platform commission tracking and fee structure display - [ ] feat: Tax reporting and revenue export for promoters @@ -53,7 +50,6 @@ ## 🚧 Doing -- [ ] feat: Promotion code on ticket - [ ] feat: Page to display all tickets for an event - [ ] feat: Add a link into notification email to order page that display all tickets @@ -65,7 +61,11 @@ - [x] Add login functionality - [x] refactor: Moving checkout to OrdersController - [x] feat: Payment gateway integration (Stripe) - PayPal not implemented +- [x] feat: Profesionnal account. User can ask to change from a customer to a professionnal account to create and manage events. - [x] feat: Digital tickets with QR codes - [x] feat: Ticket inventory management and capacity limits - [x] feat: Event discovery with search and filtering +- [x] feat: Multiple ticket types (early bird, VIP, general admission) - [x] feat: Email notifications (purchase confirmations, event reminders) +- [x] feat: Promotion code on ticket +- [x] feat: User can choose to create a professionnal account on sign-up page to be allowed to create and manage events diff --git a/app/controllers/api/v1/events_controller.rb b/app/controllers/api/v1/events_controller.rb index 7130ca7..5f00625 100755 --- a/app/controllers/api/v1/events_controller.rb +++ b/app/controllers/api/v1/events_controller.rb @@ -14,14 +14,14 @@ module Api # Retrieves all events sorted by creation date (most recent first) def index @events = Event.all.order(created_at: :desc) - render json: @events, status: :ok + render json: @events.map { |e| event_json(e) }, status: :ok end # GET /api/v1/events/:id # Retrieves a single event by its ID # Returns 404 if the event is not found def show - render json: @event, status: :ok + render json: event_json(@event), status: :ok end # POST /api/v1/events @@ -31,7 +31,7 @@ module Api def create @event = Event.new(event_params) if @event.save - render json: @event, status: :created + render json: event_json(@event), status: :created else render json: { errors: @event.errors.full_messages }, status: :unprocessable_entity end @@ -43,7 +43,7 @@ module Api # Returns 422 Unprocessable Entity with error messages on failure def update if @event.update(event_params) - render json: @event, status: :ok + render json: event_json(@event), status: :ok else render json: { errors: @event.errors.full_messages }, status: :unprocessable_entity end @@ -99,6 +99,32 @@ module Api :user_id ) end + + # Helper method to serialize event data safely + def event_json(event) + { + id: event.id, + name: event.name, + slug: event.slug, + description: event.description, + state: event.state, + venue_name: event.venue_name, + venue_address: event.venue_address, + start_time: event.start_time, + end_time: event.end_time, + latitude: event.latitude, + longitude: event.longitude, + featured: event.featured, + created_at: event.created_at, + updated_at: event.updated_at, + user: { + id: event.user.id, + email: event.user.email, # May be remove public email ? + first_name: event.user.first_name, # May be remove public name ? + last_name: event.user.last_name # May be remove public name ? + } + } + end end end end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index b3f572c..e74cd0c 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -164,6 +164,8 @@ class OrdersController < ApplicationController flash[:alert] = "Erreur lors de la création de la session de paiement" end end + + render :checkout end # Increment payment attempt - called via AJAX when user clicks pay button diff --git a/app/controllers/promoter/events_controller.rb b/app/controllers/promoter/events_controller.rb index a45c7c0..72af78d 100644 --- a/app/controllers/promoter/events_controller.rb +++ b/app/controllers/promoter/events_controller.rb @@ -29,6 +29,8 @@ class Promoter::EventsController < ApplicationController if @event.save redirect_to promoter_event_path(@event), notice: "Event créé avec succès!" else + # If validation fails and an image was attached, purge it + @event.image.purge if @event.image.attached? render :new, status: :unprocessable_entity end end diff --git a/app/javascript/controllers/event_form_controller.js b/app/javascript/controllers/event_form_controller.js index df1d144..0b7f431 100644 --- a/app/javascript/controllers/event_form_controller.js +++ b/app/javascript/controllers/event_form_controller.js @@ -664,4 +664,37 @@ export default class extends Controller { this.hideMessage("geocoding-success") this.hideMessage("geocoding-progress") } + + // Preview selected image + previewImage(event) { + const file = event.target.files[0] + if (!file) return + + // Validate file type + if (!file.type.startsWith('image/')) { + alert('Veuillez sélectionner une image valide.') + event.target.value = '' + return + } + + // Validate file size (5MB) + if (file.size > 5 * 1024 * 1024) { + alert('L\'image ne doit pas dépasser 5MB.') + event.target.value = '' + return + } + + // Show preview + const reader = new FileReader() + reader.onload = (e) => { + const previewContainer = document.getElementById('image-preview') + const previewImg = document.getElementById('preview-img') + + if (previewContainer && previewImg) { + previewImg.src = e.target.result + previewContainer.classList.remove('hidden') + } + } + reader.readAsDataURL(file) + } } diff --git a/app/models/event.rb b/app/models/event.rb index 9dd32d5..5038539 100755 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -22,7 +22,9 @@ class Event < ApplicationRecord has_many :tickets, through: :ticket_types has_many :orders has_many :promotion_codes + has_one_attached :image + # === Callbacks === before_validation :geocode_address, if: :should_geocode_address? @@ -32,7 +34,8 @@ class Event < ApplicationRecord validates :slug, presence: true, length: { minimum: 3, maximum: 100 } validates :description, presence: true, length: { minimum: 10, maximum: 2000 } validates :state, presence: true, inclusion: { in: states.keys } - validates :image, length: { maximum: 500 } # URL or path to image + validate :image_format, if: -> { image.attached? } + validate :image_size, if: -> { image.attached? } # Venue information validates :venue_name, presence: true, length: { maximum: 100 } @@ -58,6 +61,20 @@ class Event < ApplicationRecord # === Instance Methods === + # Get image variants for different display sizes + def event_image_variant(size = :medium) + case size + when :large + image.variant(resize_to_limit: [1200, 630]) + when :medium + image.variant(resize_to_limit: [800, 450]) + when :small + image.variant(resize_to_limit: [400, 225]) + else + image + end + end + # Check if coordinates were successfully geocoded or are fallback coordinates def geocoding_successful? coordinates_look_valid? @@ -131,6 +148,25 @@ class Event < ApplicationRecord nil end + # Validate image format + def image_format + return unless image.attached? + + allowed_types = %w[image/jpeg image/jpg image/png image/webp] + unless allowed_types.include?(image.content_type) + errors.add(:image, "doit être au format JPG, PNG ou WebP") + end + end + + # Validate image size + def image_size + return unless image.attached? + + if image.byte_size > 5.megabytes + errors.add(:image, "doit faire moins de 5MB") + end + end + private # Determine if we should perform server-side geocoding diff --git a/app/views/components/_event_item.html.erb b/app/views/components/_event_item.html.erb index a3dd4a2..4fa25f1 100755 --- a/app/views/components/_event_item.html.erb +++ b/app/views/components/_event_item.html.erb @@ -1,7 +1,7 @@ <%= link_to event_path(event.slug, event), class: "group block p-4 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 hover:shadow-md transition-all duration-200" do %>
- <%= image_tag event.image, alt: event.name, class: "w-full h-full object-cover" if event.image.present? %> + <%= image_tag event.event_image_variant(:small), alt: event.name, class: "w-full h-full object-cover" if event.image.attached? %>

diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 30e4f1e..768511a 100755 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -22,13 +22,9 @@ <% @events.each do |event| %>
<%= link_to event_path(event.slug, event), class: "block" do %> - <% if event.image.present? %> + <% if event.image.attached? %>
- <%= event.name %> + <%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %> <% if event.featured? %>
diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 00ccc34..ad7c236 100755 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -10,9 +10,9 @@
- <% if @event.image.present? %> + <% if @event.image.attached? %>
- <%= image_tag @event.image, class: "w-full h-full object-cover" %> + <%= image_tag @event.event_image_variant(:large), class: "w-full h-full object-cover" %>
diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index 5d671b6..f663a9a 100755 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -89,10 +89,8 @@
- <% if event.image.present? %> - <%= event.name %> + <% if event.image.attached? %> + <%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %> <% else %>
diff --git a/app/views/promoter/events/edit.html.erb b/app/views/promoter/events/edit.html.erb index 7e7f578..58ba4c6 100644 --- a/app/views/promoter/events/edit.html.erb +++ b/app/views/promoter/events/edit.html.erb @@ -67,9 +67,41 @@
- <%= form.label :image, "Image (URL)", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.url_field :image, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", placeholder: "https://example.com/image.jpg" %> -

URL de l'image de couverture de l'événement

+ <%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %> +
+ + <% if @event.image.attached? %> +
+ <%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %> +
+ +
+
+ <% end %> + + +
+ <%= form.file_field :image, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-purple-50 file:text-purple-700 hover:file:bg-purple-100", accept: "image/png,image/jpeg,image/jpg,image/webp", data: { action: "change->event-form#previewImage" } %> +
+ Formats acceptés : PNG, JPG, JPEG, WebP (max 5MB) + <% if @event.image.attached? %> +
Laissez vide pour conserver l'image actuelle + <% end %> +
+
+ + + +
diff --git a/app/views/promoter/events/new.html.erb b/app/views/promoter/events/new.html.erb index c7dcde1..7d6ef4d 100644 --- a/app/views/promoter/events/new.html.erb +++ b/app/views/promoter/events/new.html.erb @@ -60,9 +60,38 @@
- <%= form.label :image, "Image (URL)", class: "block text-sm font-medium text-gray-700 mb-2" %> - <%= form.url_field :image, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", placeholder: "https://example.com/image.jpg" %> -

URL de l'image de couverture de l'événement

+ <%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %> +
+ + <% if @event.image.attached? %> +
+ <%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %> +
+ +
+
+ <% end %> + + +
+ <%= form.file_field :image, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-purple-50 file:text-purple-700 hover:file:bg-purple-100", accept: "image/png,image/jpeg,image/jpg,image/webp", data: { action: "change->event-form#previewImage" } %> +
+ Formats acceptés : PNG, JPG, JPEG, WebP (max 5MB) +
+
+ + + +
diff --git a/app/views/promoter/events/show.html.erb b/app/views/promoter/events/show.html.erb index f1dc632..6596c20 100644 --- a/app/views/promoter/events/show.html.erb +++ b/app/views/promoter/events/show.html.erb @@ -174,9 +174,9 @@
- <% if @event.image.present? %> + <% if @event.image.attached? %>
- <%= @event.name %> + <%= image_tag @event.event_image_variant(:large), alt: @event.name, class: "w-full h-full object-cover" %>
<% end %> diff --git a/config/environments/test.rb b/config/environments/test.rb index c2095b1..63f2c1d 100755 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -50,4 +50,11 @@ Rails.application.configure do # Raise error when a before_action's only/except options reference missing actions. config.action_controller.raise_on_missing_callback_actions = true + + # Configure Stripe for testing + config.stripe = { + publishable_key: "pk_test_test", + secret_key: "sk_test_test", + signing_secret: "whsec_test_test" + } end diff --git a/db/migrate/20250929222613_create_active_storage_tables.active_storage.rb b/db/migrate/20250929222613_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..6bd8bd0 --- /dev/null +++ b/db/migrate/20250929222613_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [ primary_key_type, foreign_key_type ] + end +end diff --git a/db/migrate/20250929222616_add_image_to_events.rb b/db/migrate/20250929222616_add_image_to_events.rb new file mode 100644 index 0000000..6056dca --- /dev/null +++ b/db/migrate/20250929222616_add_image_to_events.rb @@ -0,0 +1,4 @@ +class AddImageToEvents < ActiveRecord::Migration[8.0] + def change + end +end diff --git a/db/schema.rb b/db/schema.rb index ed5c678..5cc56c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,35 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_09_28_181311) do +ActiveRecord::Schema[8.0].define(version: 2025_09_29_222616) do + create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "events", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "slug", null: false @@ -130,6 +158,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_28_181311) do t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "order_promotion_codes", "orders" add_foreign_key "order_promotion_codes", "promotion_codes" add_foreign_key "promotion_codes", "events" diff --git a/debug_promotion_test.rb b/debug_promotion_test.rb new file mode 100755 index 0000000..aa4caf6 --- /dev/null +++ b/debug_promotion_test.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +# Debug script to understand the test failure +require_relative './config/environment' + +# Load test data +user = User.find_by(email: 'user1@example.com') +event = Event.find_by(name: 'Summer Concert') + +puts "User: #{user.inspect}" +puts "Event: #{event.inspect}" + +# Create a new order for the test +order = user.orders.create!(event: event, status: "draft", expires_at: 15.minutes.from_now, total_amount_cents: 2000) +puts "Order: #{order.inspect}" + +# Create ticket type and ticket +ticket_type = TicketType.create!( + name: "Test Ticket Type", + description: "A valid description for the ticket type that is long enough", + price_cents: 2000, + quantity: 10, + sale_start_at: Time.current, + sale_end_at: Time.current + 1.day, + requires_id: false, + event: event +) + +ticket = Ticket.create!( + order: order, + ticket_type: ticket_type, + status: "draft", + first_name: "John", + last_name: "Doe", + price_cents: 2000 +) + +puts "Ticket: #{ticket.inspect}" +puts "Ticket valid?: #{ticket.valid?}" +puts "Order tickets count: #{order.tickets.count}" + +# Recalculate the order total +order.calculate_total! +puts "Order total: #{order.total_amount_cents}" + +# Create a unique promotion code +unique_code = "TESTDISCOUNT_#{SecureRandom.hex(4)}" +puts "Creating promotion code with code: #{unique_code}" + +promotion_code = PromotionCode.create( + code: unique_code, + discount_amount_cents: 500, + expires_at: 1.month.from_now, + active: true, + user: user, + event: event +) + +puts "Promotion code: #{promotion_code.inspect}" +puts "Promotion code valid?: #{promotion_code.valid?}" + +# Check if order already has promotion codes +puts "Order promotion codes before: #{order.promotion_codes.count}" + +# Try to apply the promotion code +begin + order.promotion_codes << promotion_code + puts "Successfully added promotion code to order" +rescue => e + puts "Error adding promotion code: #{e.message}" + puts e.backtrace.first(5) +end + +puts "Order promotion codes after: #{order.promotion_codes.count}" \ No newline at end of file diff --git a/debug_test.rb b/debug_test.rb new file mode 100755 index 0000000..6fa4f32 --- /dev/null +++ b/debug_test.rb @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +# Debug script to understand the test failure +require_relative './config/environment' + +# Load test data +user = User.find_by(email: 'user1@example.com') +event = Event.find_by(name: 'Summer Concert') +order = Order.find_by(status: 'draft') + +puts "User: #{user.inspect}" +puts "Event: #{event.inspect}" +puts "Order: #{order.inspect}" + +# Check if the user can manage events +puts "User can manage events: #{user.can_manage_events?}" + +# Create a promotion code +promotion_code = PromotionCode.create( + code: "TESTDISCOUNT", + discount_amount_cents: 500, + expires_at: 1.month.from_now, + active: true, + user: user, + event: event +) + +puts "Promotion code: #{promotion_code.inspect}" +puts "Promotion code valid?: #{promotion_code.valid_for_use?}" + +# Try to create a ticket type and ticket +ticket_type = TicketType.create!( + name: "Test Ticket Type", + description: "A valid description for the ticket type that is long enough", + price_cents: 2000, + quantity: 10, + sale_start_at: Time.current, + sale_end_at: Time.current + 1.day, + requires_id: false, + event: event +) + +puts "Ticket type: #{ticket_type.inspect}" + +# Create ticket with all required fields +ticket = Ticket.create!( + order: order, + ticket_type: ticket_type, + status: "draft", + first_name: "John", + last_name: "Doe", + price_cents: 2000 +) + +puts "Ticket: #{ticket.inspect}" +puts "Ticket valid?: #{ticket.valid?}" +puts "Ticket errors: #{ticket.errors.full_messages}" unless ticket.valid? + +# Recalculate order total +order.calculate_total! +puts "Order total: #{order.total_amount_cents}" + +# Test the promotion code application +puts "Applying promotion code..." +order.promotion_codes << promotion_code +order.calculate_total! +puts "Order total after promotion: #{order.total_amount_cents}" \ No newline at end of file diff --git a/test/controllers/orders_controller_promotion_test.rb b/test/controllers/orders_controller_promotion_test.rb index fe3c958..d9d3370 100644 --- a/test/controllers/orders_controller_promotion_test.rb +++ b/test/controllers/orders_controller_promotion_test.rb @@ -1,4 +1,5 @@ require "test_helper" +require "securerandom" class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers @@ -7,7 +8,8 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest def setup @user = users(:one) @event = events(:concert_event) - @order = orders(:draft_order) + # Create a new order for the test to ensure proper associations + @order = @user.orders.create!(event: @event, status: "draft", expires_at: 15.minutes.from_now, total_amount_cents: 2000) sign_in @user end @@ -25,19 +27,27 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest event: @event ) - Ticket.create!( + ticket = Ticket.create!( order: @order, ticket_type: ticket_type, status: "draft", first_name: "John", - last_name: "Doe" + last_name: "Doe", + price_cents: 2000 ) + # Debug the ticket creation + puts "Ticket saved: #{ticket.persisted?}" + puts "Ticket errors: #{ticket.errors.full_messages}" unless ticket.valid? + puts "Order tickets count: #{@order.tickets.count}" + # Recalculate the order total @order.calculate_total! + # Use a unique code for each test run + unique_code = "TESTDISCOUNT_#{SecureRandom.hex(4)}" promotion_code = PromotionCode.create( - code: "TESTDISCOUNT", + code: unique_code, discount_amount_cents: 500, # €5.00 expires_at: 1.month.from_now, active: true, @@ -45,7 +55,9 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest event: @event ) - get checkout_order_path(@order), params: { promotion_code: "TESTDISCOUNT" } + get checkout_order_path(@order), params: { promotion_code: unique_code } + puts "Response status: #{response.status}" + puts "Response body: #{response.body}" if response.status != 200 assert_response :success assert_not_nil flash.now[:notice] assert_match /Code promotionnel appliqué: TESTDISCOUNT/, flash.now[:notice] diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 18b375a..d3edc98 100755 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -5,6 +5,7 @@ one: encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %> last_name: Trump first_name: Donald + is_professionnal: true onboarding_completed: true two: diff --git a/test/test_helper.rb b/test/test_helper.rb index 07293c9..b7c4747 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -19,6 +19,14 @@ module ActiveSupport # Add more helper methods to be used by all tests here... + # Mock Stripe for tests + setup do + # Mock Stripe checkout session creation + Stripe::Checkout::Session.stubs(:create).returns( + Struct.new(:id, :url).new("cs_test_session", "https://checkout.stripe.com/test") + ) + end + # Helper to create users with completed onboarding by default for tests def create_test_user(attributes = {}) User.create!({