- Add comprehensive promotion code methods to Order model - Implement Stripe invoice integration for promotion code discounts - Display promotion codes on invoice with proper discount breakdown - Fix and enhance all unit tests for promotion code functionality - Add discount calculation with capping to prevent negative totals - Ensure promotion codes work across entire order lifecycle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
270 lines
7.5 KiB
Ruby
270 lines
7.5 KiB
Ruby
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,
|
|
user: @user,
|
|
event: @event
|
|
)
|
|
|
|
assert promotion_code.valid?
|
|
assert_equal "DISCOUNT10", promotion_code.code
|
|
assert_equal 1000, promotion_code.discount_amount_cents
|
|
assert promotion_code.active?
|
|
end
|
|
|
|
# Test validation for required fields
|
|
def test_validation_for_required_fields
|
|
promotion_code = PromotionCode.new
|
|
refute promotion_code.valid?
|
|
assert_not_nil promotion_code.errors[:code]
|
|
end
|
|
|
|
# Test unique code validation
|
|
def test_unique_code_validation
|
|
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, 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, 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
|
|
end
|
|
|
|
# Test expired scope
|
|
def test_expired_scope
|
|
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
|
|
end
|
|
|
|
# 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, 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
|