cleanup code
This commit is contained in:
@@ -5,57 +5,40 @@ describe Enterprise::Billing::V2::CreditManagementService do
|
||||
let(:service) { described_class.new(account: account) }
|
||||
|
||||
before do
|
||||
allow(Enterprise::Billing::ReportUsageJob).to receive(:perform_later)
|
||||
allow(ENV).to receive(:fetch).and_call_original
|
||||
allow(ENV).to receive(:fetch).with('STRIPE_V2_METER_EVENT_NAME', anything).and_return('ai_prompts')
|
||||
allow(ENV).to receive(:[]).and_call_original
|
||||
allow(ENV).to receive(:[]).with('STRIPE_V2_METER_EVENT_NAME').and_return('ai_prompts')
|
||||
allow(ENV).to receive(:[]).with('STRIPE_V2_METER_ID').and_return(nil) # Disable Stripe meter fetching
|
||||
|
||||
# Stub Stripe credit grant creation
|
||||
allow(Stripe::Billing::CreditGrant).to receive(:create).and_return(
|
||||
OpenStruct.new(id: 'cg_test_123')
|
||||
)
|
||||
|
||||
account.update!(
|
||||
custom_attributes: (account.custom_attributes || {}).merge(
|
||||
custom_attributes: {
|
||||
'stripe_billing_version' => 2,
|
||||
'monthly_credits' => 100,
|
||||
'topup_credits' => 50,
|
||||
'stripe_customer_id' => 'cus_test_123'
|
||||
)
|
||||
'stripe_customer_id' => 'cus_test_123',
|
||||
'stripe_meter_event_name' => 'ai_prompts'
|
||||
}
|
||||
)
|
||||
|
||||
# Stub Stripe meter event creation
|
||||
allow(Stripe::Billing::MeterEvent).to receive(:create).and_return(
|
||||
OpenStruct.new(identifier: 'test_event_123')
|
||||
)
|
||||
end
|
||||
|
||||
describe '#credit_balance' do
|
||||
it 'returns current credit balance' do
|
||||
balance = service.credit_balance
|
||||
|
||||
expect(balance[:monthly]).to eq(100)
|
||||
expect(balance[:topup]).to eq(50)
|
||||
expect(balance[:total]).to eq(150)
|
||||
describe '#total_credits' do
|
||||
it 'returns sum of monthly and topup credits' do
|
||||
expect(service.total_credits).to eq(150)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#use_credit' do
|
||||
before do
|
||||
# Stub the Stripe meter event creation
|
||||
allow(Stripe::Billing::MeterEvent).to receive(:create).and_return(
|
||||
OpenStruct.new(identifier: 'test_event_123')
|
||||
)
|
||||
end
|
||||
|
||||
context 'when sufficient monthly credits' do
|
||||
it 'uses monthly credits first' do
|
||||
context 'when sufficient credits' do
|
||||
it 'uses credits and reports to Stripe' do
|
||||
result = service.use_credit(feature: 'ai_test', amount: 10)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(result[:credits_used]).to eq(10)
|
||||
expect(result[:remaining]).to eq(140)
|
||||
|
||||
account.reload
|
||||
expect(account.custom_attributes['monthly_credits']).to eq(90)
|
||||
expect(account.custom_attributes['topup_credits']).to eq(50)
|
||||
expect(account.credit_transactions.order(created_at: :desc).first.metadata['feature']).to eq('ai_test')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,27 +50,46 @@ describe Enterprise::Billing::V2::CreditManagementService do
|
||||
expect(result[:message]).to eq('Insufficient credits')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using mixed credits' do
|
||||
it 'uses monthly first then topup' do
|
||||
result = service.use_credit(feature: 'ai_test', amount: 120)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
account.reload
|
||||
expect(account.custom_attributes['monthly_credits']).to eq(0)
|
||||
expect(account.custom_attributes['topup_credits']).to eq(30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#grant_monthly_credits' do
|
||||
it 'grants new monthly credits and logs transaction metadata' do
|
||||
expect { service.grant_monthly_credits(500, metadata: { source: 'spec' }) }
|
||||
.to change { account.credit_transactions.count }.by(2) # expire + grant
|
||||
describe '#sync_monthly_credits' do
|
||||
it 'updates monthly credits from Stripe' do
|
||||
service.sync_monthly_credits(500)
|
||||
|
||||
account.reload
|
||||
expect(account.custom_attributes['monthly_credits']).to eq(500)
|
||||
expect(account.credit_transactions.order(created_at: :desc).first.metadata['source']).to eq('spec')
|
||||
expect(account.credit_transactions.last.description).to eq('Monthly credits from Stripe')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sync_monthly_expired' do
|
||||
it 'expires monthly credits' do
|
||||
expired = service.sync_monthly_expired
|
||||
|
||||
expect(expired).to eq(100)
|
||||
account.reload
|
||||
expect(account.custom_attributes['monthly_credits']).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_topup_credits' do
|
||||
it 'adds topup credits' do
|
||||
expect { service.add_topup_credits(100, metadata: { source: 'spec' }) }
|
||||
.to change { account.credit_transactions.count }.by(1)
|
||||
service.add_topup_credits(100)
|
||||
|
||||
account.reload
|
||||
expect(account.custom_attributes['topup_credits']).to eq(150)
|
||||
expect(account.credit_transactions.order(created_at: :desc).first.metadata['source']).to eq('spec')
|
||||
expect(account.credit_transactions.last.description).to eq('Topup credits added')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,17 +16,20 @@ describe Enterprise::Billing::V2::UsageAnalyticsService do
|
||||
|
||||
context 'when account has usage' do
|
||||
before do
|
||||
account.update!(custom_attributes: (account.custom_attributes || {}).merge(
|
||||
'stripe_billing_version' => 2,
|
||||
'stripe_customer_id' => 'cus_123'
|
||||
))
|
||||
account.update!(
|
||||
custom_attributes: {
|
||||
'stripe_billing_version' => 2,
|
||||
'monthly_credits' => 100,
|
||||
'topup_credits' => 50
|
||||
}
|
||||
)
|
||||
|
||||
account.credit_transactions.create!(
|
||||
transaction_type: 'use',
|
||||
credit_type: 'monthly',
|
||||
amount: 5,
|
||||
description: 'spec monthly',
|
||||
metadata: {},
|
||||
metadata: { 'feature' => 'ai_captain' },
|
||||
created_at: Time.current
|
||||
)
|
||||
|
||||
@@ -35,13 +38,9 @@ describe Enterprise::Billing::V2::UsageAnalyticsService do
|
||||
credit_type: 'topup',
|
||||
amount: 3,
|
||||
description: 'spec topup',
|
||||
metadata: {},
|
||||
metadata: { 'feature' => 'ai_summary' },
|
||||
created_at: Time.current
|
||||
)
|
||||
|
||||
# Stub the Stripe API call to return nil (which will fallback to local data)
|
||||
allow(ENV).to receive(:[]).and_call_original
|
||||
allow(ENV).to receive(:[]).with('STRIPE_V2_METER_ID').and_return(nil)
|
||||
end
|
||||
|
||||
it 'aggregates usage from credit transactions' do
|
||||
@@ -49,8 +48,29 @@ describe Enterprise::Billing::V2::UsageAnalyticsService do
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(result[:total_usage]).to eq(8)
|
||||
expect(result[:source]).to eq('local')
|
||||
expect(result[:credits_remaining]).to eq(150)
|
||||
expect(result[:usage_by_feature]).to include('ai_captain' => 5, 'ai_summary' => 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#recent_transactions' do
|
||||
it 'returns recent transactions' do
|
||||
account.update!(custom_attributes: { 'stripe_billing_version' => 2 })
|
||||
|
||||
3.times do |i|
|
||||
account.credit_transactions.create!(
|
||||
transaction_type: 'use',
|
||||
amount: i + 1,
|
||||
credit_type: 'monthly',
|
||||
created_at: i.hours.ago
|
||||
)
|
||||
end
|
||||
|
||||
transactions = service.recent_transactions(limit: 2)
|
||||
|
||||
expect(transactions.count).to eq(2)
|
||||
expect(transactions.first.amount).to eq(1) # Most recent
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,39 +3,53 @@ require 'rails_helper'
|
||||
describe Enterprise::Billing::V2::UsageReporterService do
|
||||
let(:account) { create(:account) }
|
||||
let(:service) { described_class.new(account: account) }
|
||||
let(:config) { Rails.application.config.stripe_v2 }
|
||||
let!(:original_config) { config.deep_dup }
|
||||
|
||||
before do
|
||||
account.update!(
|
||||
custom_attributes: (account.custom_attributes || {}).merge(
|
||||
custom_attributes: {
|
||||
'stripe_billing_version' => 2,
|
||||
'stripe_customer_id' => 'cus_test_123'
|
||||
)
|
||||
'stripe_customer_id' => 'cus_test_123',
|
||||
'stripe_meter_event_name' => 'ai_prompts'
|
||||
}
|
||||
)
|
||||
|
||||
config[:meter_id] = 'mtr_test_123'
|
||||
config[:meter_event_name] = 'chat_prompts'
|
||||
end
|
||||
|
||||
after { config.replace(original_config) }
|
||||
|
||||
it 'posts usage events to Stripe meters' do
|
||||
meter_event = OpenStruct.new(identifier: 'me_test_123')
|
||||
allow(Stripe::Billing::MeterEvent).to receive(:create).and_return(meter_event)
|
||||
|
||||
# Stub ENV to return the configured meter event name from config
|
||||
allow(ENV).to receive(:[]).and_call_original
|
||||
allow(ENV).to receive(:[]).with('STRIPE_V2_METER_EVENT_NAME').and_return(nil)
|
||||
|
||||
result = service.report(5, 'ai_test')
|
||||
|
||||
expect(result).to include(success: true, reported_credits: 5)
|
||||
expect(result).to include(success: true, event_id: 'me_test_123')
|
||||
expect(Stripe::Billing::MeterEvent).to have_received(:create) do |params, options|
|
||||
expect(params[:event_name]).to eq('chat_prompts') # Should use the config value
|
||||
expect(params[:event_name]).to eq('ai_prompts')
|
||||
expect(params[:payload][:value]).to eq('5')
|
||||
expect(params[:payload][:stripe_customer_id]).to eq('cus_test_123')
|
||||
expect(options[:stripe_version]).to eq('2025-08-27.preview')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when V2 billing not enabled' do
|
||||
before do
|
||||
account.update!(custom_attributes: { 'stripe_billing_version' => 1 })
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
result = service.report(5, 'ai_test')
|
||||
|
||||
expect(result).to include(success: false, message: 'V2 billing not enabled')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no Stripe customer' do
|
||||
before do
|
||||
account.update!(custom_attributes: { 'stripe_billing_version' => 2 })
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
result = service.report(5, 'ai_test')
|
||||
|
||||
expect(result).to include(success: false, message: 'No Stripe customer')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,76 +7,61 @@ describe Enterprise::Billing::V2::WebhookHandlerService do
|
||||
let(:credit_service) { instance_double(Enterprise::Billing::V2::CreditManagementService) }
|
||||
|
||||
before do
|
||||
account.update!(custom_attributes: (account.custom_attributes || {}).merge('stripe_billing_version' => 2))
|
||||
account.update!(custom_attributes: { 'stripe_billing_version' => 2 })
|
||||
allow(Enterprise::Billing::V2::CreditManagementService).to receive(:new).with(account: account).and_return(credit_service)
|
||||
end
|
||||
|
||||
def build_event(type:, id:, object:)
|
||||
def build_event(type:, object:)
|
||||
data = double('Stripe::Event::Data', object: object)
|
||||
double('Stripe::Event', type: type, id: id, data: data)
|
||||
double('Stripe::Event', type: type, data: data)
|
||||
end
|
||||
|
||||
describe '#process' do
|
||||
context 'when handling monthly credit grant' do
|
||||
it 'grants monthly credits with metadata' do
|
||||
allow(credit_service).to receive(:grant_monthly_credits).and_return(success: true)
|
||||
grant = Struct.new(:id, :amount, :expires_at).new('cg_123', 2000, Time.current)
|
||||
event = build_event(type: 'billing.credit_grant.created', id: 'evt_123', object: grant)
|
||||
it 'syncs monthly credits from Stripe' do
|
||||
allow(credit_service).to receive(:sync_monthly_credits)
|
||||
grant = Struct.new(:amount, :expires_at).new(2000, Time.current)
|
||||
event = build_event(type: 'billing.credit_grant.created', object: grant)
|
||||
|
||||
result = service.process(event)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(credit_service).to have_received(:grant_monthly_credits).with(2000,
|
||||
metadata: hash_including('stripe_event_id' => 'evt_123',
|
||||
'grant_id' => 'cg_123'))
|
||||
expect(credit_service).to have_received(:sync_monthly_credits).with(2000)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when handling topup credit grant' do
|
||||
it 'adds topup credits' do
|
||||
allow(credit_service).to receive(:add_topup_credits).and_return(success: true)
|
||||
grant = Struct.new(:id, :amount, :expires_at).new('cg_456', 500, nil)
|
||||
event = build_event(type: 'billing.credit_grant.created', id: 'evt_456', object: grant)
|
||||
allow(credit_service).to receive(:add_topup_credits)
|
||||
grant = Struct.new(:amount, :expires_at).new(500, nil)
|
||||
event = build_event(type: 'billing.credit_grant.created', object: grant)
|
||||
|
||||
result = service.process(event)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(credit_service).to have_received(:add_topup_credits)
|
||||
.with(500, metadata: hash_including('stripe_event_id' => 'evt_456', 'grant_id' => 'cg_456', 'source' => 'credit_grant'))
|
||||
expect(credit_service).to have_received(:add_topup_credits).with(500)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invoice payment succeeded for a topup' do
|
||||
it 'adds topup credits from invoice metadata' do
|
||||
allow(credit_service).to receive(:add_topup_credits).and_return(success: true)
|
||||
invoice_metadata = ActiveSupport::HashWithIndifferentAccess.new(type: 'topup', credits: '300')
|
||||
invoice = Struct.new(:id, :metadata).new('in_123', invoice_metadata)
|
||||
event = build_event(type: 'invoice.payment_succeeded', id: 'evt_789', object: invoice)
|
||||
context 'when handling credit expiration' do
|
||||
it 'expires monthly credits' do
|
||||
allow(credit_service).to receive(:sync_monthly_expired).and_return(100)
|
||||
event = build_event(type: 'billing.credit_grant.expired', object: {})
|
||||
|
||||
result = service.process(event)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(credit_service).to have_received(:add_topup_credits)
|
||||
.with(300, metadata: hash_including('stripe_event_id' => 'evt_789', 'invoice_id' => 'in_123', 'source' => 'invoice'))
|
||||
expect(credit_service).to have_received(:sync_monthly_expired)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when event already processed' do
|
||||
it 'skips duplicate handling' do
|
||||
account.credit_transactions.create!(
|
||||
transaction_type: 'grant',
|
||||
amount: 100,
|
||||
credit_type: 'monthly',
|
||||
metadata: { 'stripe_event_id' => 'evt_dup' }
|
||||
)
|
||||
allow(credit_service).to receive(:grant_monthly_credits)
|
||||
grant = Struct.new(:id, :amount, :expires_at).new('cg_dup', 200, Time.current)
|
||||
event = build_event(type: 'billing.credit_grant.created', id: 'evt_dup', object: grant)
|
||||
context 'when handling unknown event' do
|
||||
it 'returns success' do
|
||||
event = build_event(type: 'unknown.event', object: {})
|
||||
|
||||
result = service.process(event)
|
||||
|
||||
expect(result[:success]).to be(true)
|
||||
expect(credit_service).not_to have_received(:grant_monthly_credits)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user