cleanup code

This commit is contained in:
Tanmay Sharma
2025-10-11 10:22:46 +04:00
parent d6f5c627eb
commit efe6e64dcd
10 changed files with 228 additions and 707 deletions
@@ -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