feat/promotion-code #5
@@ -9,7 +9,7 @@ class Promoter::PromotionCodesController < ApplicationController
|
||||
@promotion_codes = @event.promotion_codes.includes(:user)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# GET /promoter/events/:event_id/promotion_codes/new
|
||||
# Show form to create a new promotion code
|
||||
def new
|
||||
|
||||
@@ -96,7 +96,7 @@ class Order < ApplicationRecord
|
||||
discount_total = promotion_codes.sum(:discount_amount_cents)
|
||||
|
||||
# Ensure total doesn't go below zero
|
||||
final_total = [ticket_total - discount_total, 0].max
|
||||
final_total = [ ticket_total - discount_total, 0 ].max
|
||||
update!(total_amount_cents: final_total)
|
||||
end
|
||||
|
||||
@@ -110,9 +110,9 @@ class Order < ApplicationRecord
|
||||
subtotal_amount_cents / 100.0
|
||||
end
|
||||
|
||||
# Total discount amount from all promotion codes
|
||||
# Total discount amount from all promotion codes (capped at subtotal)
|
||||
def discount_amount_cents
|
||||
promotion_codes.sum(:discount_amount_cents)
|
||||
[ promotion_codes.sum(:discount_amount_cents), subtotal_amount_cents ].min
|
||||
end
|
||||
|
||||
# Discount amount in euros
|
||||
|
||||
@@ -166,6 +166,23 @@ class StripeInvoiceService
|
||||
})
|
||||
end
|
||||
|
||||
# Add promotion code discounts as negative line items
|
||||
@order.promotion_codes.each do |promo_code|
|
||||
Stripe::InvoiceItem.create({
|
||||
customer: customer.id,
|
||||
invoice: invoice.id,
|
||||
amount: -promo_code.discount_amount_cents, # Negative amount for discount
|
||||
currency: "eur",
|
||||
description: "Réduction promotionnelle (Code: #{promo_code.code})",
|
||||
metadata: {
|
||||
promotion_code_id: promo_code.id,
|
||||
promotion_code: promo_code.code,
|
||||
discount_amount_cents: promo_code.discount_amount_cents,
|
||||
type: "promotion_discount"
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
# No service fee on customer invoice; platform fee deducted from promoter payout
|
||||
end
|
||||
|
||||
|
||||
@@ -121,13 +121,56 @@
|
||||
<% end %>
|
||||
</tbody>
|
||||
<tfoot class="bg-gray-50">
|
||||
<!-- Subtotal -->
|
||||
<tr>
|
||||
<th colspan="3" scope="col" class="px-6 py-3 text-right text-sm font-medium text-gray-900 uppercase tracking-wider">Total</th>
|
||||
<th scope="col" class="px-6 py-3 text-right text-lg font-bold text-gray-900"><%= "%.2f" % @order.total_amount_euros %>€</th>
|
||||
<td colspan="3" scope="col" class="px-6 py-3 text-right text-sm font-medium text-gray-600">Sous-total</td>
|
||||
<td scope="col" class="px-6 py-3 text-right text-sm font-medium text-gray-600"><%= "%.2f" % @order.subtotal_amount_euros %>€</td>
|
||||
</tr>
|
||||
|
||||
<!-- Promotion Code Discounts -->
|
||||
<% if @order.promotion_codes.any? %>
|
||||
<% @order.promotion_codes.each do |promo_code| %>
|
||||
<tr>
|
||||
<td colspan="3" scope="col" class="px-6 py-3 text-right text-sm font-medium text-green-600">
|
||||
Réduction (Code: <%= promo_code.code %>)
|
||||
</td>
|
||||
<td scope="col" class="px-6 py-3 text-right text-sm font-semibold text-green-600">-<%= "%.2f" % promo_code.discount_amount_euros %>€</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<!-- Total -->
|
||||
<tr class="border-t-2 border-gray-300">
|
||||
<td colspan="3" scope="col" class="px-6 py-3 text-right text-lg font-bold text-gray-900 uppercase tracking-wider">Total</td>
|
||||
<td scope="col" class="px-6 py-3 text-right text-lg font-bold text-gray-900">
|
||||
<% if @order.total_amount_cents == 0 %>
|
||||
GRATUIT
|
||||
<% else %>
|
||||
<%= "%.2f" % @order.total_amount_euros %>€
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Promotion Code Summary -->
|
||||
<% if @order.promotion_codes.any? %>
|
||||
<div class="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<h4 class="text-sm font-semibold text-green-900 mb-2 flex items-center">
|
||||
<i data-lucide="tag" class="w-4 h-4 mr-2"></i>
|
||||
Codes promotionnels appliqués
|
||||
</h4>
|
||||
<div class="text-xs text-green-700">
|
||||
<% @order.promotion_codes.each do |promo_code| %>
|
||||
<div class="flex items-center justify-between">
|
||||
<span><%= promo_code.code %></span>
|
||||
<span class="font-semibold">-<%= "%.2f" % promo_code.discount_amount_euros %>€</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Payment Information -->
|
||||
|
||||
@@ -6,32 +6,57 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest
|
||||
# Setup test data
|
||||
def setup
|
||||
@user = users(:one)
|
||||
@event = events(:one)
|
||||
@order = orders(:one)
|
||||
@event = events(:concert_event)
|
||||
@order = orders(:draft_order)
|
||||
sign_in @user
|
||||
end
|
||||
|
||||
# Test applying a valid promotion code
|
||||
def test_apply_valid_promotion_code
|
||||
# Create ticket type and 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: 2000,
|
||||
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"
|
||||
)
|
||||
|
||||
# Recalculate the order total
|
||||
@order.calculate_total!
|
||||
|
||||
promotion_code = PromotionCode.create(
|
||||
code: "TESTDISCOUNT",
|
||||
discount_amount_cents: 1000, # €10.00
|
||||
discount_amount_cents: 500, # €5.00
|
||||
expires_at: 1.month.from_now,
|
||||
active: true
|
||||
active: true,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
get checkout_order_path(@order), params: { promotion_code: "TESTDISCOUNT" }
|
||||
assert_response :success
|
||||
assert_not_nil flash[:notice]
|
||||
assert_match /Code promotionnel appliqué: TESTDISCOUNT/, flash[:notice]
|
||||
assert_not_nil flash.now[:notice]
|
||||
assert_match /Code promotionnel appliqué: TESTDISCOUNT/, flash.now[:notice]
|
||||
end
|
||||
|
||||
# Test applying an invalid promotion code
|
||||
def test_apply_invalid_promotion_code
|
||||
get checkout_order_path(@order), params: { promotion_code: "INVALIDCODE" }
|
||||
assert_response :success
|
||||
assert_not_nil flash[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash[:alert]
|
||||
assert_not_nil flash.now[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash.now[:alert]
|
||||
end
|
||||
|
||||
# Test applying an expired promotion code
|
||||
@@ -40,13 +65,15 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest
|
||||
code: "EXPIREDCODE",
|
||||
discount_amount_cents: 1000,
|
||||
expires_at: 1.day.ago,
|
||||
active: true
|
||||
active: true,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
get checkout_order_path(@order), params: { promotion_code: "EXPIREDCODE" }
|
||||
assert_response :success
|
||||
assert_not_nil flash[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash[:alert]
|
||||
assert_not_nil flash.now[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash.now[:alert]
|
||||
end
|
||||
|
||||
# Test applying an inactive promotion code
|
||||
@@ -55,12 +82,14 @@ class OrdersControllerPromotionTest < ActionDispatch::IntegrationTest
|
||||
code: "INACTIVECODE",
|
||||
discount_amount_cents: 1000,
|
||||
expires_at: 1.month.from_now,
|
||||
active: false
|
||||
active: false,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
get checkout_order_path(@order), params: { promotion_code: "INACTIVECODE" }
|
||||
assert_response :success
|
||||
assert_not_nil flash[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash[:alert]
|
||||
assert_not_nil flash.now[:alert]
|
||||
assert_equal "Code promotionnel invalide", flash.now[:alert]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -582,6 +582,243 @@ class OrderTest < ActiveSupport::TestCase
|
||||
assert_equal 95.0, order.promoter_payout_euros
|
||||
end
|
||||
|
||||
# === Promotion Code Tests ===
|
||||
|
||||
test "subtotal_amount_cents should calculate total without discounts" 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"
|
||||
)
|
||||
|
||||
# Create promotion code
|
||||
promotion_code = PromotionCode.create!(
|
||||
code: "TESTCODE",
|
||||
discount_amount_cents: 500,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
order.promotion_codes << promotion_code
|
||||
order.calculate_total!
|
||||
|
||||
assert_equal 3000, order.subtotal_amount_cents # 2 tickets * 1500 cents
|
||||
assert_equal 2500, order.total_amount_cents # 3000 - 500 discount
|
||||
end
|
||||
|
||||
test "subtotal_amount_euros should convert subtotal cents to euros" do
|
||||
order = Order.new(total_amount_cents: 2500)
|
||||
def order.subtotal_amount_cents; 3000; end
|
||||
assert_equal 30.0, order.subtotal_amount_euros
|
||||
end
|
||||
|
||||
test "discount_amount_cents should calculate total discount from promotion codes" do
|
||||
order = Order.create!(
|
||||
user: @user, event: @event, total_amount_cents: 0,
|
||||
status: "draft", payment_attempts: 0
|
||||
)
|
||||
|
||||
# Create ticket type and tickets for subtotal
|
||||
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.create!(
|
||||
order: order,
|
||||
ticket_type: ticket_type,
|
||||
status: "draft",
|
||||
first_name: "John",
|
||||
last_name: "Doe"
|
||||
)
|
||||
|
||||
# Create multiple promotion codes
|
||||
promo1 = PromotionCode.create!(
|
||||
code: "PROMO1",
|
||||
discount_amount_cents: 300,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
promo2 = PromotionCode.create!(
|
||||
code: "PROMO2",
|
||||
discount_amount_cents: 700,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
order.promotion_codes << [ promo1, promo2 ]
|
||||
order.calculate_total!
|
||||
|
||||
assert_equal 1000, order.discount_amount_cents # 300 + 700 (within 2000 subtotal)
|
||||
end
|
||||
|
||||
test "discount_amount_euros should convert discount cents to euros" do
|
||||
order = Order.new(total_amount_cents: 2000)
|
||||
def order.discount_amount_cents; 1000; end
|
||||
assert_equal 10.0, order.discount_amount_euros
|
||||
end
|
||||
|
||||
test "calculate_total! should apply promotion code discounts" 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: 2000,
|
||||
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"
|
||||
)
|
||||
|
||||
# Create promotion code
|
||||
promotion_code = PromotionCode.create!(
|
||||
code: "TESTCODE",
|
||||
discount_amount_cents: 500,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
order.promotion_codes << promotion_code
|
||||
order.calculate_total!
|
||||
|
||||
assert_equal 2000, order.subtotal_amount_cents
|
||||
assert_equal 500, order.discount_amount_cents
|
||||
assert_equal 1500, order.total_amount_cents
|
||||
end
|
||||
|
||||
test "calculate_total! should handle zero total after promotion codes" 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: 500,
|
||||
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"
|
||||
)
|
||||
|
||||
# Create promotion code that covers the entire amount
|
||||
promotion_code = PromotionCode.create!(
|
||||
code: "FULLDISCOUNT",
|
||||
discount_amount_cents: 500,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
order.promotion_codes << promotion_code
|
||||
order.calculate_total!
|
||||
|
||||
assert_equal 500, order.subtotal_amount_cents
|
||||
assert_equal 500, order.discount_amount_cents
|
||||
assert_equal 0, order.total_amount_cents
|
||||
assert order.free?
|
||||
end
|
||||
|
||||
test "calculate_total! should not allow negative totals with promotion codes" 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: 300,
|
||||
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"
|
||||
)
|
||||
|
||||
# Create promotion code that exceeds the ticket amount
|
||||
promotion_code = PromotionCode.create!(
|
||||
code: "TOOMUCH",
|
||||
discount_amount_cents: 1000,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
order.promotion_codes << promotion_code
|
||||
order.calculate_total!
|
||||
|
||||
assert_equal 300, order.subtotal_amount_cents
|
||||
assert_equal 300, order.discount_amount_cents # Capped at subtotal
|
||||
assert_equal 0, order.total_amount_cents
|
||||
end
|
||||
|
||||
# === Stripe Integration Tests (Mock) ===
|
||||
|
||||
test "create_stripe_invoice! should return nil for non-paid orders" do
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
require "test_helper"
|
||||
|
||||
class PromotionCodeTest < 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
|
||||
|
||||
# Test valid promotion code creation
|
||||
def test_valid_promotion_code
|
||||
promotion_code = PromotionCode.create(
|
||||
code: "DISCOUNT10",
|
||||
discount_amount_cents: 1000, # €10.00
|
||||
expires_at: 1.month.from_now,
|
||||
active: true
|
||||
active: true,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
assert promotion_code.valid?
|
||||
@@ -25,23 +46,23 @@ class PromotionCodeTest < ActiveSupport::TestCase
|
||||
|
||||
# Test unique code validation
|
||||
def test_unique_code_validation
|
||||
PromotionCode.create(code: "UNIQUE123", discount_amount_cents: 500)
|
||||
duplicate_code = PromotionCode.new(code: "UNIQUE123", discount_amount_cents: 500)
|
||||
PromotionCode.create(code: "UNIQUE123", discount_amount_cents: 500, user: @user, event: @event)
|
||||
duplicate_code = PromotionCode.new(code: "UNIQUE123", discount_amount_cents: 500, user: @user, event: @event)
|
||||
refute duplicate_code.valid?
|
||||
assert_not_nil duplicate_code.errors[:code]
|
||||
end
|
||||
|
||||
# Test discount amount validation
|
||||
def test_discount_amount_validation
|
||||
promotion_code = PromotionCode.new(code: "VALID123", discount_amount_cents: -100)
|
||||
promotion_code = PromotionCode.new(code: "VALID123", discount_amount_cents: -100, user: @user, event: @event)
|
||||
refute promotion_code.valid?
|
||||
assert_not_nil promotion_code.errors[:discount_amount_cents]
|
||||
end
|
||||
|
||||
# Test active scope
|
||||
def test_active_scope
|
||||
active_code = PromotionCode.create(code: "ACTIVE123", discount_amount_cents: 500, active: true)
|
||||
inactive_code = PromotionCode.create(code: "INACTIVE123", discount_amount_cents: 500, active: false)
|
||||
active_code = PromotionCode.create(code: "ACTIVE123", discount_amount_cents: 500, active: true, user: @user, event: @event)
|
||||
inactive_code = PromotionCode.create(code: "INACTIVE123", discount_amount_cents: 500, active: false, user: @user, event: @event)
|
||||
|
||||
assert_includes PromotionCode.active, active_code
|
||||
refute_includes PromotionCode.active, inactive_code
|
||||
@@ -49,8 +70,8 @@ class PromotionCodeTest < ActiveSupport::TestCase
|
||||
|
||||
# Test expired scope
|
||||
def test_expired_scope
|
||||
expired_code = PromotionCode.create(code: "EXPIRED123", discount_amount_cents: 500, expires_at: 1.day.ago)
|
||||
future_code = PromotionCode.create(code: "FUTURE123", discount_amount_cents: 500, expires_at: 1.month.from_now)
|
||||
expired_code = PromotionCode.create(code: "EXPIRED123", discount_amount_cents: 500, expires_at: 1.day.ago, user: @user, event: @event)
|
||||
future_code = PromotionCode.create(code: "FUTURE123", discount_amount_cents: 500, expires_at: 1.month.from_now, user: @user, event: @event)
|
||||
|
||||
assert_includes PromotionCode.expired, expired_code
|
||||
refute_includes PromotionCode.expired, future_code
|
||||
@@ -58,10 +79,191 @@ class PromotionCodeTest < ActiveSupport::TestCase
|
||||
|
||||
# Test valid scope
|
||||
def test_valid_scope
|
||||
valid_code = PromotionCode.create(code: "VALID123", discount_amount_cents: 500, active: true, expires_at: 1.month.from_now)
|
||||
invalid_code = PromotionCode.create(code: "INVALID123", discount_amount_cents: 500, active: false, expires_at: 1.day.ago)
|
||||
valid_code = PromotionCode.create(code: "VALID123", discount_amount_cents: 500, active: true, expires_at: 1.month.from_now, user: @user, event: @event)
|
||||
invalid_code = PromotionCode.create(code: "INVALID123", discount_amount_cents: 500, active: false, expires_at: 1.day.ago, user: @user, event: @event)
|
||||
|
||||
assert_includes PromotionCode.valid, valid_code
|
||||
refute_includes PromotionCode.valid, invalid_code
|
||||
end
|
||||
|
||||
# Test discount_amount_euros method
|
||||
def test_discount_amount_euros_converts_cents_to_euros
|
||||
promotion_code = PromotionCode.new(discount_amount_cents: 1000)
|
||||
assert_equal 10.0, promotion_code.discount_amount_euros
|
||||
|
||||
promotion_code = PromotionCode.new(discount_amount_cents: 550)
|
||||
assert_equal 5.5, promotion_code.discount_amount_euros
|
||||
end
|
||||
|
||||
# Test active? method
|
||||
def test_active_method
|
||||
# Active and not expired
|
||||
active_code = PromotionCode.create(
|
||||
code: "ACTIVE1",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.month.from_now,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert active_code.active?
|
||||
|
||||
# Active but expired
|
||||
expired_active_code = PromotionCode.create(
|
||||
code: "ACTIVE2",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.day.ago,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not expired_active_code.active?
|
||||
|
||||
# Inactive but not expired
|
||||
inactive_code = PromotionCode.create(
|
||||
code: "INACTIVE1",
|
||||
discount_amount_cents: 500,
|
||||
active: false,
|
||||
expires_at: 1.month.from_now,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not inactive_code.active?
|
||||
|
||||
# Active with no expiration
|
||||
no_expiry_code = PromotionCode.create(
|
||||
code: "NOEXPIRY",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: nil,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert no_expiry_code.active?
|
||||
end
|
||||
|
||||
# Test expired? method
|
||||
def test_expired_method
|
||||
# Expired code
|
||||
expired_code = PromotionCode.create(
|
||||
code: "EXPIRED1",
|
||||
discount_amount_cents: 500,
|
||||
expires_at: 1.day.ago,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert expired_code.expired?
|
||||
|
||||
# Future code
|
||||
future_code = PromotionCode.create(
|
||||
code: "FUTURE1",
|
||||
discount_amount_cents: 500,
|
||||
expires_at: 1.month.from_now,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not future_code.expired?
|
||||
|
||||
# No expiration
|
||||
no_expiry_code = PromotionCode.create(
|
||||
code: "NOEXPIRY1",
|
||||
discount_amount_cents: 500,
|
||||
expires_at: nil,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not no_expiry_code.expired?
|
||||
end
|
||||
|
||||
# Test can_be_used? method
|
||||
def test_can_be_used_method
|
||||
# Can be used: active, not expired, under usage limit
|
||||
usable_code = PromotionCode.create(
|
||||
code: "USABLE1",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.month.from_now,
|
||||
usage_limit: 10,
|
||||
uses_count: 0,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert usable_code.can_be_used?
|
||||
|
||||
# Cannot be used: inactive
|
||||
inactive_code = PromotionCode.create(
|
||||
code: "INACTIVE2",
|
||||
discount_amount_cents: 500,
|
||||
active: false,
|
||||
expires_at: 1.month.from_now,
|
||||
usage_limit: 10,
|
||||
uses_count: 0,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not inactive_code.can_be_used?
|
||||
|
||||
# Cannot be used: expired
|
||||
expired_code = PromotionCode.create(
|
||||
code: "EXPIRED2",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.day.ago,
|
||||
usage_limit: 10,
|
||||
uses_count: 0,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not expired_code.can_be_used?
|
||||
|
||||
# Cannot be used: at usage limit
|
||||
limit_reached_code = PromotionCode.create(
|
||||
code: "LIMIT1",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.month.from_now,
|
||||
usage_limit: 5,
|
||||
uses_count: 5,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert_not limit_reached_code.can_be_used?
|
||||
|
||||
# Can be used: no usage limit
|
||||
no_limit_code = PromotionCode.create(
|
||||
code: "NOLIMIT1",
|
||||
discount_amount_cents: 500,
|
||||
active: true,
|
||||
expires_at: 1.month.from_now,
|
||||
usage_limit: nil,
|
||||
uses_count: 100,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
assert no_limit_code.can_be_used?
|
||||
end
|
||||
|
||||
# Test increment_uses_count callback
|
||||
def test_increment_uses_count_callback
|
||||
promotion_code = PromotionCode.create(
|
||||
code: "INCREMENT1",
|
||||
discount_amount_cents: 500,
|
||||
uses_count: 0,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
assert_equal 0, promotion_code.uses_count
|
||||
|
||||
# The callback should only run on create, so we test the initial value
|
||||
new_code = PromotionCode.create(
|
||||
code: "INCREMENT2",
|
||||
discount_amount_cents: 500,
|
||||
uses_count: nil,
|
||||
user: @user,
|
||||
event: @event
|
||||
)
|
||||
|
||||
assert_equal 0, new_code.uses_count
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user