Class: CloverSandboxSimulator::ParallelExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/clover_sandbox_simulator/parallel_executor.rb

Overview

Executes operations across multiple merchants in parallel

Usage:

executor = ParallelExecutor.new

# Run a block for each merchant
results = executor.run_all do |services, merchant|
  charge = services.ecommerce.create_test_charge(amount: 1000)
  refund = services.ecommerce.create_refund(charge_id: charge["id"])
  { charge: charge, refund: refund }
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(merchant_ids: nil) ⇒ ParallelExecutor

Returns a new instance of ParallelExecutor.



21
22
23
24
25
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 21

def initialize(merchant_ids: nil)
  @logger = CloverSandboxSimulator.logger
  @merchants = load_merchants(merchant_ids)
  @results = {}
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



19
20
21
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 19

def logger
  @logger
end

#merchantsObject (readonly)

Returns the value of attribute merchants.



19
20
21
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 19

def merchants
  @merchants
end

#resultsObject (readonly)

Returns the value of attribute results.



19
20
21
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 19

def results
  @results
end

Instance Method Details

#refresh_tokens_allHash

Refresh tokens for all merchants that have refresh tokens

Returns:

  • (Hash)

    Results with new token status



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 109

def refresh_tokens_all
  run_all do |services, merchant|
    config = services.config

    unless config.refresh_token && !config.refresh_token.empty?
      next { skipped: true, reason: "No refresh token" }
    end

    unless config.oauth_enabled?
      next { skipped: true, reason: "OAuth not configured (need APP_ID and APP_SECRET)" }
    end

    tokens = services.oauth.refresh_current_merchant_token
    if tokens
      {
        refreshed: true,
        expires_in: tokens["expires_in"]
      }
    else
      { refreshed: false, error: "Refresh failed" }
    end
  end
end

#run_all(max_threads: 5) {|services, merchant| ... } ⇒ Hash

Run a block for each merchant in parallel

Parameters:

  • max_threads (Integer) (defaults to: 5)

    Maximum concurrent threads (default: 5)

Yields:

  • (services, merchant)

    Block to execute for each merchant

Yield Parameters:

  • services (ServicesManager)

    Services for the merchant

  • merchant (Hash)

    Merchant configuration

Returns:

  • (Hash)

    Results keyed by merchant_id



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 34

def run_all(max_threads: 5, &block)
  return {} if merchants.empty?

  logger.info "Running operations for #{merchants.size} merchants (max #{max_threads} threads)"

  pool = Concurrent::FixedThreadPool.new(max_threads)
  futures = {}

  merchants.each do |merchant|
    merchant_id = merchant[:id]

    futures[merchant_id] = Concurrent::Future.execute(executor: pool) do
      run_for_merchant(merchant, &block)
    end
  end

  # Wait for all futures to complete
  futures.each do |merchant_id, future|
    begin
      @results[merchant_id] = {
        success: true,
        merchant_name: merchants.find { |m| m[:id] == merchant_id }&.dig(:name),
        data: future.value(30) # 30 second timeout per merchant
      }
    rescue StandardError => e
      @results[merchant_id] = {
        success: false,
        merchant_name: merchants.find { |m| m[:id] == merchant_id }&.dig(:name),
        error: e.message
      }
    end
  end

  pool.shutdown
  pool.wait_for_termination(60)

  print_summary
  @results
end

#run_card_transactions_all(transaction_count: 5, refund_percentage: 40) ⇒ Hash

Run multiple card transactions with refunds for each merchant Works without OAuth tokens (Ecommerce API only)

Parameters:

  • transaction_count (Integer) (defaults to: 5)

    Number of transactions per merchant

  • refund_percentage (Integer) (defaults to: 40)

    Percentage of transactions to refund

Returns:

  • (Hash)

    Results with transaction details



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 176

def run_card_transactions_all(transaction_count: 5, refund_percentage: 40)
  run_all(max_threads: 3) do |services, merchant|
    next { skipped: true, reason: "Ecommerce not configured" } unless services.ecommerce_available?

    results = {
      charges: [],
      refunds: [],
      total_charged: 0,
      total_refunded: 0
    }

    # Create transactions
    transaction_count.times do |i|
      # Random amount between $5 and $75
      amount = rand(500..7500)

      # Small delay to avoid rate limits
      sleep(0.5) if i > 0

      charge = services.ecommerce.create_test_charge(amount: amount)
      next unless charge&.dig("id")

      results[:charges] << {
        id: charge["id"],
        amount: charge["amount"]
      }
      results[:total_charged] += charge["amount"]
    end

    # Refund some transactions
    refund_count = (results[:charges].size * refund_percentage / 100.0).ceil
    charges_to_refund = results[:charges].sample(refund_count)

    charges_to_refund.each_with_index do |charge, i|
      sleep(0.5) if i > 0

      # 60% full refunds, 40% partial
      if rand < 0.6
        refund = services.ecommerce.create_refund(charge_id: charge[:id])
      else
        partial_amount = (charge[:amount] * rand(25..75) / 100.0).round
        refund = services.ecommerce.create_refund(charge_id: charge[:id], amount: partial_amount)
      end

      next unless refund&.dig("id")

      results[:refunds] << {
        id: refund["id"],
        charge_id: charge[:id],
        amount: refund["amount"]
      }
      results[:total_refunded] += refund["amount"]
    end

    results
  end
end

#run_day_all(multiplier: 0.5, refund_percentage: 10) ⇒ Hash

Run a full day simulation for all merchants Requires valid OAuth tokens in .env.json for Platform API

Parameters:

  • multiplier (Float) (defaults to: 0.5)

    Order count multiplier (default 0.5 for faster runs)

  • refund_percentage (Integer) (defaults to: 10)

    Percentage of orders to refund (default 10)

Returns:

  • (Hash)

    Results with order counts and stats



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 139

def run_day_all(multiplier: 0.5, refund_percentage: 10)
  run_all(max_threads: 2) do |services, merchant|
    config = services.config

    unless config.platform_enabled?
      next { skipped: true, reason: "OAuth token required (Platform API)" }
    end

    # Run full day simulation
    generator = Generators::OrderGenerator.new(
      services: services,
      refund_percentage: refund_percentage
    )

    orders = generator.generate_realistic_day(
      date: Date.today,
      multiplier: multiplier
    )

    stats = generator.stats

    {
      orders: orders.size,
      revenue: stats[:revenue],
      tips: stats[:tips],
      tax: stats[:tax],
      refunds: stats[:refunds]
    }
  end
end

#run_for(merchant_ids) {|services, merchant| ... } ⇒ Hash

Run for specific merchant IDs only

Parameters:

  • merchant_ids (Array<String>)

    List of merchant IDs

Yields:

  • (services, merchant)

    Block to execute

Returns:

  • (Hash)

    Results



79
80
81
82
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 79

def run_for(merchant_ids, &block)
  @merchants = load_merchants(merchant_ids)
  run_all(&block)
end

#test_ecommerce_all(amount: 1000) ⇒ Hash

Run Ecommerce test (charge + refund) for all merchants

Parameters:

  • amount (Integer) (defaults to: 1000)

    Charge amount in cents

Returns:

  • (Hash)

    Results with charge and refund details



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/clover_sandbox_simulator/parallel_executor.rb', line 88

def test_ecommerce_all(amount: 1000)
  run_all do |services, merchant|
    next { skipped: true, reason: "Ecommerce not configured" } unless services.ecommerce_available?

    charge = services.ecommerce.create_test_charge(amount: amount)
    next { error: "Charge failed" } unless charge&.dig("id")

    refund = services.ecommerce.create_refund(charge_id: charge["id"])

    {
      charge_id: charge["id"],
      charge_amount: charge["amount"],
      refund_id: refund&.dig("id"),
      refund_amount: refund&.dig("amount")
    }
  end
end