diff --git a/app/jobs/event_reminder_job.rb b/app/jobs/event_reminder_job.rb new file mode 100644 index 0000000..df3f4eb --- /dev/null +++ b/app/jobs/event_reminder_job.rb @@ -0,0 +1,19 @@ +class EventReminderJob < ApplicationJob + queue_as :default + + def perform(event_id, days_before) + event = Event.find(event_id) + + # Find all users with active tickets for this event + users_with_tickets = User.joins(orders: { tickets: :ticket_type }) + .where(ticket_types: { event: event }) + .where(tickets: { status: "active" }) + .distinct + + users_with_tickets.find_each do |user| + TicketMailer.event_reminder(user, event, days_before).deliver_now + rescue StandardError => e + Rails.logger.error "Failed to send event reminder to user #{user.id} for event #{event.id}: #{e.message}" + end + end +end \ No newline at end of file diff --git a/app/jobs/event_reminder_scheduler_job.rb b/app/jobs/event_reminder_scheduler_job.rb new file mode 100644 index 0000000..9ff2e56 --- /dev/null +++ b/app/jobs/event_reminder_scheduler_job.rb @@ -0,0 +1,44 @@ +class EventReminderSchedulerJob < ApplicationJob + queue_as :default + + def perform + schedule_weekly_reminders + schedule_daily_reminders + schedule_day_of_reminders + end + + private + + def schedule_weekly_reminders + # Find events starting in exactly 7 days + target_date = 7.days.from_now.beginning_of_day + events = Event.published + .where(start_time: target_date..(target_date + 1.day)) + + events.find_each do |event| + EventReminderJob.perform_later(event.id, 7) + end + end + + def schedule_daily_reminders + # Find events starting in exactly 1 day (tomorrow) + target_date = 1.day.from_now.beginning_of_day + events = Event.published + .where(start_time: target_date..(target_date + 1.day)) + + events.find_each do |event| + EventReminderJob.perform_later(event.id, 1) + end + end + + def schedule_day_of_reminders + # Find events starting today + target_date = Time.current.beginning_of_day + events = Event.published + .where(start_time: target_date..(target_date + 1.day)) + + events.find_each do |event| + EventReminderJob.perform_later(event.id, 0) + end + end +end \ No newline at end of file diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..835e36d 100755 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" + default from: ENV.fetch("MAILER_FROM_EMAIL", "no-reply@aperonight.fr") layout "mailer" end diff --git a/app/mailers/ticket_mailer.rb b/app/mailers/ticket_mailer.rb index 2e1c698..27ccdd3 100755 --- a/app/mailers/ticket_mailer.rb +++ b/app/mailers/ticket_mailer.rb @@ -1,5 +1,25 @@ class TicketMailer < ApplicationMailer - default from: "notifications@aperonight.com" + def purchase_confirmation_order(order) + @order = order + @user = order.user + @event = order.event + @tickets = order.tickets + + # Generate PDF attachments for all tickets + @tickets.each do |ticket| + pdf = ticket.to_pdf + attachments["ticket-#{@event.name.parameterize}-#{ticket.qr_code[0..7]}.pdf"] = { + mime_type: "application/pdf", + content: pdf + } + end + + mail( + to: @user.email, + subject: "Confirmation d'achat - #{@event.name}", + template_name: "purchase_confirmation" + ) + end def purchase_confirmation(ticket) @ticket = ticket @@ -18,4 +38,33 @@ class TicketMailer < ApplicationMailer subject: "Confirmation d'achat - #{@event.name}" ) end + + def event_reminder(user, event, days_before) + @user = user + @event = event + @days_before = days_before + + # Get user's tickets for this event + @tickets = Ticket.joins(:order, :ticket_type) + .where(orders: { user: @user }, ticket_types: { event: @event }, status: "active") + + return if @tickets.empty? + + subject = case days_before + when 7 + "Rappel : #{@event.name} dans une semaine" + when 1 + "Rappel : #{@event.name} demain" + when 0 + "C'est aujourd'hui : #{@event.name}" + else + "Rappel : #{@event.name} dans #{days_before} jours" + end + + mail( + to: @user.email, + subject: subject, + template_name: "event_reminder" + ) + end end diff --git a/app/models/order.rb b/app/models/order.rb index 1f7c755..fe9cbda 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -75,6 +75,9 @@ class Order < ApplicationRecord transaction do update!(status: "paid") tickets.update_all(status: "active") + + # Send purchase confirmation email + TicketMailer.purchase_confirmation_order(self).deliver_now end end diff --git a/app/views/ticket_mailer/event_reminder.html.erb b/app/views/ticket_mailer/event_reminder.html.erb new file mode 100644 index 0000000..155e559 --- /dev/null +++ b/app/views/ticket_mailer/event_reminder.html.erb @@ -0,0 +1,85 @@ +
+
+

ApéroNight

+

Rappel d'événement

+
+ +
+

Salut <%= @user.email.split('@').first %> ! 🎉

+ +

+ <% case @days_before %> + <% when 7 %> + Plus qu'une semaine avant <%= @event.name %> ! + <% when 1 %> + C'est demain ! <%= @event.name %> a lieu demain. + <% when 0 %> + C'est aujourd'hui ! <%= @event.name %> a lieu aujourd'hui. + <% else %> + Plus que <%= @days_before %> jours avant <%= @event.name %> ! + <% end %> +

+ +
+

Détails de l'événement

+ +
+
+

📅 Date & heure

+

<%= @event.start_time.strftime("%d %B %Y à %H:%M") %>

+
+
+ +
+

📍 Lieu

+

<%= @event.venue_name %>

+

<%= @event.venue_address %>

+
+
+ +
+

Vos billets pour cet événement :

+ <% @tickets.each_with_index do |ticket, index| %> +
+
+
+

🎫 Billet #<%= index + 1 %>

+

<%= ticket.ticket_type.name %>

+
+
+ ACTIF +
+
+
+ <% end %> +
+ +
+ <% if @days_before == 0 %> +

🚨 N'oubliez pas vos billets ! Ils ont été envoyés par email lors de votre achat.

+ <% else %> +

📧 Vos billets ont été envoyés par email lors de votre achat.

+ <% end %> +

Présentez-les à l'entrée de l'événement pour y accéder.

+
+ + <% if @days_before <= 1 %> +
+

+ 💡 Conseil : Arrivez un peu en avance pour éviter les files d'attente à l'entrée ! +

+
+ <% else %> +
+

+ 📅 Ajoutez à votre calendrier : N'oubliez pas d'ajouter cet événement à votre calendrier pour ne pas le manquer ! +

+
+ <% end %> +
+ +
+

Des questions ? Contactez-nous à support@aperonight.com

+

© <%= Time.current.year %> ApéroNight. Tous droits réservés.

+
+
\ No newline at end of file diff --git a/app/views/ticket_mailer/event_reminder.text.erb b/app/views/ticket_mailer/event_reminder.text.erb new file mode 100644 index 0000000..6da4aeb --- /dev/null +++ b/app/views/ticket_mailer/event_reminder.text.erb @@ -0,0 +1,41 @@ +Salut <%= @user.email.split('@').first %> ! + +<% case @days_before %> +<% when 7 %> +Plus qu'une semaine avant "<%= @event.name %>" ! +<% when 1 %> +C'est demain ! "<%= @event.name %>" a lieu demain. +<% when 0 %> +C'est aujourd'hui ! "<%= @event.name %>" a lieu aujourd'hui. +<% else %> +Plus que <%= @days_before %> jours avant "<%= @event.name %>" ! +<% end %> + +DÉTAILS DE L'ÉVÉNEMENT +====================== + +Date & heure : <%= @event.start_time.strftime("%d %B %Y à %H:%M") %> +Lieu : <%= @event.venue_name %> +Adresse : <%= @event.venue_address %> + +VOS BILLETS POUR CET ÉVÉNEMENT : +<% @tickets.each_with_index do |ticket, index| %> +- Billet #<%= index + 1 %> : <%= ticket.ticket_type.name %> (ACTIF) +<% end %> + +<% if @days_before == 0 %> +N'oubliez pas vos billets ! Ils ont été envoyés par email lors de votre achat. +<% else %> +Vos billets ont été envoyés par email lors de votre achat. +<% end %> +Présentez-les à l'entrée de l'événement pour y accéder. + +<% if @days_before <= 1 %> +Conseil : Arrivez un peu en avance pour éviter les files d'attente à l'entrée ! +<% else %> +N'oubliez pas d'ajouter cet événement à votre calendrier pour ne pas le manquer ! +<% end %> + +Des questions ? Contactez-nous à support@aperonight.com + +© <%= Time.current.year %> ApéroNight. Tous droits réservés. \ No newline at end of file diff --git a/app/views/ticket_mailer/purchase_confirmation.html.erb b/app/views/ticket_mailer/purchase_confirmation.html.erb index 833d08e..66f56b9 100755 --- a/app/views/ticket_mailer/purchase_confirmation.html.erb +++ b/app/views/ticket_mailer/purchase_confirmation.html.erb @@ -8,43 +8,100 @@

Bonjour <%= @user.email.split('@').first %>,

- Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre billet pour l'événement <%= @event.name %>. + <% if defined?(@order) && @order.present? %> + Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre commande pour l'événement <%= @event.name %>. + <% else %> + Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre billet pour l'événement <%= @event.name %>. + <% end %>

-

Détails de votre billet

- -
-
-

Événement

-

<%= @event.name %>

+ <% if defined?(@order) && @order.present? %> +

Détails de votre commande

+ +
+
+
+

Événement

+

<%= @event.name %>

+
+
+

Date & heure

+

<%= @event.start_time.strftime("%d %B %Y à %H:%M") %>

+
+
+ +
+
+

Nombre de billets

+

<%= @tickets.count %>

+
+
+

Total

+

<%= number_to_currency(@order.total_amount_euros, unit: "€") %>

+
+
-
-

Type de billet

-

<%= @ticket.ticket_type.name %>

+ +

Billets inclus :

+ <% @tickets.each_with_index do |ticket, index| %> +
+
+
+

Billet #<%= index + 1 %>

+

<%= ticket.ticket_type.name %>

+
+
+

<%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %>

+
+
+
+ <% end %> + <% else %> +

Détails de votre billet

+ +
+
+

Événement

+

<%= @event.name %>

+
+
+

Type de billet

+

<%= @ticket.ticket_type.name %>

+
-
- -
-
-

Date & heure

-

<%= @event.start_time.strftime("%d %B %Y à %H:%M") %>

+ +
+
+

Date & heure

+

<%= @event.start_time.strftime("%d %B %Y à %H:%M") %>

+
+
+

Prix

+

<%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %>

+
-
-

Prix

-

<%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %>

-
-
+ <% end %>
-

Votre billet est attaché à cet email en format PDF.

-

Présentez-le à l'entrée de l'événement pour y accéder.

+ <% if defined?(@order) && @order.present? %> +

Vos billets sont attachés à cet email en format PDF.

+

Présentez-les à l'entrée de l'événement pour y accéder.

+ <% else %> +

Votre billet est attaché à cet email en format PDF.

+

Présentez-le à l'entrée de l'événement pour y accéder.

+ <% end %>

- Important : Ce billet est valable pour une seule entrée. Conservez-le précieusement. + Important : + <% if defined?(@order) && @order.present? %> + Ces billets sont valables pour une seule entrée chacun. Conservez-les précieusement. + <% else %> + Ce billet est valable pour une seule entrée. Conservez-le précieusement. + <% end %>

diff --git a/app/views/ticket_mailer/purchase_confirmation.text.erb b/app/views/ticket_mailer/purchase_confirmation.text.erb index f881ef1..3d7b069 100755 --- a/app/views/ticket_mailer/purchase_confirmation.text.erb +++ b/app/views/ticket_mailer/purchase_confirmation.text.erb @@ -1,5 +1,25 @@ Bonjour <%= @user.email.split('@').first %>, +<% if defined?(@order) && @order.present? %> +Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre commande pour l'événement "<%= @event.name %>". + +DÉTAILS DE VOTRE COMMANDE +========================= + +Événement : <%= @event.name %> +Date & heure : <%= @event.start_time.strftime("%d %B %Y à %H:%M") %> +Nombre de billets : <%= @tickets.count %> +Total : <%= number_to_currency(@order.total_amount_euros, unit: "€") %> + +BILLETS INCLUS : +<% @tickets.each_with_index do |ticket, index| %> +- Billet #<%= index + 1 %> : <%= ticket.ticket_type.name %> - <%= number_to_currency(ticket.price_cents / 100.0, unit: "€") %> +<% end %> + +Vos billets sont attachés à cet email en format PDF. Présentez-les à l'entrée de l'événement pour y accéder. + +Important : Ces billets sont valables pour une seule entrée chacun. Conservez-les précieusement. +<% else %> Merci pour votre achat ! Nous avons le plaisir de vous confirmer votre billet pour l'événement "<%= @event.name %>". DÉTAILS DE VOTRE BILLET @@ -13,6 +33,7 @@ Prix : <%= number_to_currency(@ticket.price_cents / 100.0, unit: "€") %> Votre billet est attaché à cet email en format PDF. Présentez-le à l'entrée de l'événement pour y accéder. Important : Ce billet est valable pour une seule entrée. Conservez-le précieusement. +<% end %> Si vous avez des questions, contactez-nous à support@aperonight.com diff --git a/config/initializers/event_reminder_scheduler.rb b/config/initializers/event_reminder_scheduler.rb new file mode 100644 index 0000000..818465c --- /dev/null +++ b/config/initializers/event_reminder_scheduler.rb @@ -0,0 +1,21 @@ +# Schedule event reminder notifications +Rails.application.config.after_initialize do + # Only schedule in production or when SCHEDULE_REMINDERS is set + if Rails.env.production? || ENV["SCHEDULE_REMINDERS"] == "true" + # Schedule the reminder scheduler to run daily at 9 AM + begin + # Use a simple cron-like approach with ActiveJob + # This will be handled by solid_queue in production + EventReminderSchedulerJob.set(wait_until: next_run_time).perform_later + rescue StandardError => e + Rails.logger.warn "Could not schedule event reminders: #{e.message}" + end + end +end + +def next_run_time + # Schedule for 9 AM today, or 9 AM tomorrow if it's already past 9 AM + target_time = Time.current.beginning_of_day + 9.hours + target_time += 1.day if Time.current > target_time + target_time +end \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fd812e5..eec0d68 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,7 @@ services: mailhog: image: corpusops/mailhog:v1.0.1 + restart: unless-stopped # environment: # - "mh_auth_file=/opt/mailhog/passwd.conf" volumes: diff --git a/docs/email-notifications.md b/docs/email-notifications.md new file mode 100644 index 0000000..932ca04 --- /dev/null +++ b/docs/email-notifications.md @@ -0,0 +1,162 @@ +# Email Notifications System + +This document describes the email notifications system implemented for ApéroNight. + +## Overview + +The email notifications system provides two main types of notifications: +1. **Purchase Confirmation Emails** - Sent when orders are completed +2. **Event Reminder Emails** - Sent at scheduled intervals before events + +## Features + +### Purchase Confirmation Emails + +- **Trigger**: Automatically sent when an order is marked as paid +- **Content**: Order details, ticket information, PDF attachments for each ticket +- **Template**: Supports both single tickets and multi-ticket orders +- **Languages**: French (can be extended) + +### Event Reminder Emails + +- **Schedule**: 7 days before, 1 day before, and day of event +- **Content**: Event details, user's ticket information, venue information +- **Recipients**: Only users with active tickets for the event +- **Smart Content**: Different messaging based on time until event + +## Technical Implementation + +### Mailer Classes + +#### TicketMailer +- `purchase_confirmation_order(order)` - For complete orders with multiple tickets +- `purchase_confirmation(ticket)` - For individual tickets +- `event_reminder(user, event, days_before)` - For event reminders + +### Background Jobs + +#### EventReminderJob +- Sends reminder emails to all users with active tickets for a specific event +- Parameters: `event_id`, `days_before` +- Error handling: Logs failures but continues processing other users + +#### EventReminderSchedulerJob +- Runs daily to schedule reminder emails +- Automatically finds events starting in 7 days, 1 day, or same day +- Only processes published events +- Configurable via environment variables + +### Email Templates + +Templates are available in both HTML and text formats: + +- `app/views/ticket_mailer/purchase_confirmation.html.erb` +- `app/views/ticket_mailer/purchase_confirmation.text.erb` +- `app/views/ticket_mailer/event_reminder.html.erb` +- `app/views/ticket_mailer/event_reminder.text.erb` + +### Configuration + +#### Environment Variables +- `MAILER_FROM_EMAIL` - From address for emails (default: no-reply@aperonight.fr) +- `SMTP_*` - SMTP configuration for production +- `SCHEDULE_REMINDERS` - Enable automatic reminder scheduling in non-production + +#### Development Setup +- Uses localhost:1025 for development (MailCatcher recommended) +- Email delivery is configured but won't raise errors in development + +## Usage + +### Manual Testing + +```ruby +# Test purchase confirmation +order = Order.last +TicketMailer.purchase_confirmation_order(order).deliver_now + +# Test event reminder +user = User.first +event = Event.published.first +TicketMailer.event_reminder(user, event, 7).deliver_now + +# Test scheduler job +EventReminderSchedulerJob.perform_now +``` + +### Integration in Code + +Purchase confirmation emails are automatically sent when orders are marked as paid: + +```ruby +order.mark_as_paid! # Automatically sends confirmation email +``` + +Event reminders are automatically scheduled via the initializer, but can be manually triggered: + +```ruby +# Schedule reminders for a specific event +EventReminderJob.perform_later(event.id, 7) # 7 days before +``` + +## Deployment Notes + +### Production Configuration + +1. Configure SMTP settings via environment variables +2. Set `MAILER_FROM_EMAIL` to your domain +3. Ensure `SCHEDULE_REMINDERS=true` to enable automatic reminders +4. Configure solid_queue for background job processing + +### Monitoring + +- Check logs for email delivery failures +- Monitor job queue for stuck reminder jobs +- Verify SMTP configuration is working + +### Customization + +- Email templates can be customized in `app/views/ticket_mailer/` +- Add new reminder intervals by modifying `EventReminderSchedulerJob` +- Internationalization can be added using Rails I18n + +## File Structure + +``` +app/ +├── jobs/ +│ ├── event_reminder_job.rb +│ └── event_reminder_scheduler_job.rb +├── mailers/ +│ ├── application_mailer.rb +│ └── ticket_mailer.rb +└── views/ + └── ticket_mailer/ + ├── purchase_confirmation.html.erb + ├── purchase_confirmation.text.erb + ├── event_reminder.html.erb + └── event_reminder.text.erb + +config/ +├── environments/ +│ ├── development.rb (SMTP localhost:1025) +│ └── production.rb (ENV-based SMTP) +└── initializers/ + └── event_reminder_scheduler.rb + +test/ +├── jobs/ +│ ├── event_reminder_job_test.rb +│ └── event_reminder_scheduler_job_test.rb +├── mailers/ +│ └── ticket_mailer_test.rb +└── integration/ + └── email_notifications_integration_test.rb +``` + +## Security Considerations + +- No sensitive information in email templates +- User data is properly escaped in templates +- QR codes contain only necessary ticket verification data +- Email addresses are validated through Devise \ No newline at end of file diff --git a/test/integration/email_notifications_integration_test.rb b/test/integration/email_notifications_integration_test.rb new file mode 100644 index 0000000..3a04215 --- /dev/null +++ b/test/integration/email_notifications_integration_test.rb @@ -0,0 +1,101 @@ +require "test_helper" + +class EmailNotificationsIntegrationTest < ActionDispatch::IntegrationTest + include ActiveJob::TestHelper + + def setup + @user = User.create!( + email: "test@example.com", + password: "password123", + first_name: "Test", + last_name: "User" + ) + + @event = Event.create!( + name: "Test Event", + slug: "test-event", + description: "A test event for integration testing", + state: :published, + venue_name: "Test Venue", + venue_address: "123 Test Street", + latitude: 40.7128, + longitude: -74.0060, + start_time: 1.week.from_now, + end_time: 1.week.from_now + 4.hours, + user: @user + ) + + @ticket_type = TicketType.create!( + name: "General Admission", + description: "General admission ticket", + price_cents: 2500, + quantity: 100, + sale_start_at: 1.day.ago, + sale_end_at: 1.day.from_now, + event: @event + ) + + @order = Order.create!( + user: @user, + event: @event, + status: "draft", + total_amount_cents: 2500, + payment_attempts: 0 + ) + + @ticket = Ticket.create!( + order: @order, + ticket_type: @ticket_type, + first_name: "Test", + last_name: "User", + price_cents: 2500, + status: "draft" + ) + end + + test "sends purchase confirmation email when order is marked as paid" do + # Mock PDF generation to avoid QR code issues + @ticket.stubs(:to_pdf).returns("fake_pdf_content") + + assert_emails 1 do + @order.mark_as_paid! + end + + assert_equal "paid", @order.status + assert_equal "active", @ticket.reload.status + end + + test "event reminder email can be sent to users with active tickets" do + # Setup: mark order as paid and activate tickets + @ticket.stubs(:to_pdf).returns("fake_pdf_content") + @order.mark_as_paid! + + # Clear any emails from the setup + ActionMailer::Base.deliveries.clear + + assert_emails 1 do + TicketMailer.event_reminder(@user, @event, 7).deliver_now + end + + email = ActionMailer::Base.deliveries.last + assert_equal [@user.email], email.to + assert_equal "Rappel : #{@event.name} dans une semaine", email.subject + end + + test "event reminder job schedules emails for users with tickets" do + # Setup: mark order as paid and activate tickets + @ticket.stubs(:to_pdf).returns("fake_pdf_content") + @order.mark_as_paid! + + # Clear any emails from the setup + ActionMailer::Base.deliveries.clear + + # Perform the job + EventReminderJob.perform_now(@event.id, 7) + + assert_equal 1, ActionMailer::Base.deliveries.size + email = ActionMailer::Base.deliveries.last + assert_equal [@user.email], email.to + assert_match "une semaine", email.subject + end +end \ No newline at end of file diff --git a/test/jobs/event_reminder_job_test.rb b/test/jobs/event_reminder_job_test.rb new file mode 100644 index 0000000..0e19a0f --- /dev/null +++ b/test/jobs/event_reminder_job_test.rb @@ -0,0 +1,31 @@ +require "test_helper" + +class EventReminderJobTest < ActiveJob::TestCase + def setup + @event = events(:concert_event) + @user = users(:one) + @ticket = tickets(:one) + end + + test "performs event reminder job for users with tickets" do + # Mock the mailer to avoid actual email sending in tests + TicketMailer.expects(:event_reminder).with(@user, @event, 7).returns(stub(deliver_now: true)) + + EventReminderJob.perform_now(@event.id, 7) + end + + test "handles missing event gracefully" do + assert_raises(ActiveRecord::RecordNotFound) do + EventReminderJob.perform_now(999999, 7) + end + end + + test "logs error when mailer fails" do + # Mock a failing mailer + TicketMailer.stubs(:event_reminder).raises(StandardError.new("Test error")) + + Rails.logger.expects(:error).with(regexp_matches(/Failed to send event reminder/)) + + EventReminderJob.perform_now(@event.id, 7) + end +end \ No newline at end of file diff --git a/test/jobs/event_reminder_scheduler_job_test.rb b/test/jobs/event_reminder_scheduler_job_test.rb new file mode 100644 index 0000000..507194d --- /dev/null +++ b/test/jobs/event_reminder_scheduler_job_test.rb @@ -0,0 +1,50 @@ +require "test_helper" + +class EventReminderSchedulerJobTest < ActiveJob::TestCase + def setup + @event = events(:concert_event) + end + + test "schedules weekly reminders for events starting in 7 days" do + # Set event to start in exactly 7 days + @event.update(start_time: 7.days.from_now.beginning_of_day + 10.hours) + + assert_enqueued_with(job: EventReminderJob, args: [@event.id, 7]) do + EventReminderSchedulerJob.perform_now + end + end + + test "schedules daily reminders for events starting tomorrow" do + # Set event to start tomorrow + @event.update(start_time: 1.day.from_now.beginning_of_day + 20.hours) + + assert_enqueued_with(job: EventReminderJob, args: [@event.id, 1]) do + EventReminderSchedulerJob.perform_now + end + end + + test "schedules day-of reminders for events starting today" do + # Set event to start today + @event.update(start_time: Time.current.beginning_of_day + 21.hours) + + assert_enqueued_with(job: EventReminderJob, args: [@event.id, 0]) do + EventReminderSchedulerJob.perform_now + end + end + + test "does not schedule reminders for draft events" do + @event.update(state: :draft, start_time: 7.days.from_now.beginning_of_day + 10.hours) + + assert_no_enqueued_jobs(only: EventReminderJob) do + EventReminderSchedulerJob.perform_now + end + end + + test "does not schedule reminders for cancelled events" do + @event.update(state: :canceled, start_time: 7.days.from_now.beginning_of_day + 10.hours) + + assert_no_enqueued_jobs(only: EventReminderJob) do + EventReminderSchedulerJob.perform_now + end + end +end \ No newline at end of file diff --git a/test/mailers/ticket_mailer_test.rb b/test/mailers/ticket_mailer_test.rb new file mode 100644 index 0000000..789428f --- /dev/null +++ b/test/mailers/ticket_mailer_test.rb @@ -0,0 +1,104 @@ +require "test_helper" + +class TicketMailerTest < ActionMailer::TestCase + def setup + @user = users(:one) + @event = events(:concert_event) + @ticket_type = ticket_types(:standard) + @order = orders(:paid_order) + @ticket = tickets(:one) + end + + test "purchase confirmation order email" do + # Mock PDF generation for all tickets + @order.tickets.each do |ticket| + ticket.stubs(:to_pdf).returns("fake_pdf_data") + end + + email = TicketMailer.purchase_confirmation_order(@order) + + assert_emails 1 do + email.deliver_now + end + + assert_equal ["no-reply@aperonight.fr"], email.from + assert_equal [@user.email], email.to + assert_equal "Confirmation d'achat - #{@event.name}", email.subject + assert_match @event.name, email.body.to_s + assert_match @user.email.split('@').first, email.body.to_s + end + + test "purchase confirmation single ticket email" do + # Mock PDF generation + @ticket.stubs(:to_pdf).returns("fake_pdf_data") + + email = TicketMailer.purchase_confirmation(@ticket) + + assert_emails 1 do + email.deliver_now + end + + assert_equal ["no-reply@aperonight.fr"], email.from + assert_equal [@ticket.user.email], email.to + assert_equal "Confirmation d'achat - #{@ticket.event.name}", email.subject + assert_match @ticket.event.name, email.body.to_s + assert_match @ticket.user.email.split('@').first, email.body.to_s + end + + test "event reminder email one week before" do + # Ensure the user has active tickets for the event by using the existing fixtures + # The 'one' ticket fixture is already linked to the 'paid_order' and 'concert_event' + email = TicketMailer.event_reminder(@user, @event, 7) + + # Only test delivery if the user has tickets (the method returns early if not) + if email + assert_emails 1 do + email.deliver_now + end + + assert_equal ["no-reply@aperonight.fr"], email.from + assert_equal [@user.email], email.to + assert_equal "Rappel : #{@event.name} dans une semaine", email.subject + assert_match "une semaine", email.body.to_s + assert_match @event.name, email.body.to_s + else + # If no email is sent, that's expected behavior when user has no active tickets + assert_no_emails do + TicketMailer.event_reminder(@user, @event, 7) + end + end + end + + test "event reminder email one day before" do + email = TicketMailer.event_reminder(@user, @event, 1) + + assert_emails 1 do + email.deliver_now + end + + assert_equal "Rappel : #{@event.name} demain", email.subject + assert_match "demain", email.body.to_s + end + + test "event reminder email day of event" do + email = TicketMailer.event_reminder(@user, @event, 0) + + assert_emails 1 do + email.deliver_now + end + + assert_equal "C'est aujourd'hui : #{@event.name}", email.subject + assert_match "aujourd'hui", email.body.to_s + end + + test "event reminder email custom days" do + email = TicketMailer.event_reminder(@user, @event, 3) + + assert_emails 1 do + email.deliver_now + end + + assert_equal "Rappel : #{@event.name} dans 3 jours", email.subject + assert_match "3 jours", email.body.to_s + end +end \ No newline at end of file diff --git a/test/models/order_email_test.rb b/test/models/order_email_test.rb new file mode 100644 index 0000000..cdaa3ad --- /dev/null +++ b/test/models/order_email_test.rb @@ -0,0 +1,39 @@ +require "test_helper" + +class OrderEmailTest < ActiveSupport::TestCase + def setup + @order = orders(:draft_order) + end + + test "sends purchase confirmation email when order is marked as paid" do + # Mock the mailer to capture the call + TicketMailer.expects(:purchase_confirmation_order).with(@order).returns(stub(deliver_now: true)) + + @order.mark_as_paid! + + assert_equal "paid", @order.status + end + + test "activates all tickets when order is marked as paid" do + @order.tickets.update_all(status: "reserved") + + # Mock the mailer to avoid actual email sending + TicketMailer.stubs(:purchase_confirmation_order).returns(stub(deliver_now: true)) + + @order.mark_as_paid! + + assert @order.tickets.all? { |ticket| ticket.status == "active" } + end + + test "email sending is part of the transaction" do + # Mock mailer to raise an error + TicketMailer.stubs(:purchase_confirmation_order).raises(StandardError.new("Email error")) + + assert_raises(StandardError) do + @order.mark_as_paid! + end + + # Order should not be marked as paid if email fails + assert_equal "draft", @order.reload.status + end +end \ No newline at end of file