ruby_slm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.idea/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +768 -0
- data/Rakefile +16 -0
- data/examples/test_complex_workflow.rb +747 -0
- data/examples/test_parallel_complex_workflow.rb +983 -0
- data/lib/ruby_slm/errors.rb +24 -0
- data/lib/ruby_slm/execution.rb +176 -0
- data/lib/ruby_slm/state.rb +47 -0
- data/lib/ruby_slm/state_machine.rb +140 -0
- data/lib/ruby_slm/states/base.rb +149 -0
- data/lib/ruby_slm/states/choice.rb +144 -0
- data/lib/ruby_slm/states/fail.rb +62 -0
- data/lib/ruby_slm/states/parallel.rb +178 -0
- data/lib/ruby_slm/states/pass.rb +42 -0
- data/lib/ruby_slm/states/succeed.rb +39 -0
- data/lib/ruby_slm/states/task.rb +523 -0
- data/lib/ruby_slm/states/wait.rb +123 -0
- data/lib/ruby_slm/version.rb +5 -0
- data/lib/ruby_slm.rb +50 -0
- data/sig/states_language_machine.rbs +4 -0
- data/test/test_state_machine.rb +52 -0
- metadata +146 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
# ComplexOrderProcessor class from previous step
|
|
8
|
+
class ComplexOrderProcessor
|
|
9
|
+
# Order Classification
|
|
10
|
+
def determine_order_type(input)
|
|
11
|
+
order = input['order'] || {}
|
|
12
|
+
|
|
13
|
+
total = order['total'].to_f
|
|
14
|
+
items = order['items'] || []
|
|
15
|
+
quantity = order['quantity'].to_i
|
|
16
|
+
|
|
17
|
+
# Business logic for order classification
|
|
18
|
+
if total > 500 || order['premium_customer']
|
|
19
|
+
{ "order_type" => "premium", "reason" => "high_value_or_premium_customer" }
|
|
20
|
+
elsif quantity > 10 || total > 1000
|
|
21
|
+
{ "order_type" => "bulk", "reason" => "large_quantity" }
|
|
22
|
+
elsif order['shipping'] && order['shipping']['country'] != 'US'
|
|
23
|
+
{ "order_type" => "international", "reason" => "international_shipping" }
|
|
24
|
+
elsif order['digital_product']
|
|
25
|
+
{ "order_type" => "digital", "reason" => "digital_product" }
|
|
26
|
+
else
|
|
27
|
+
{ "order_type" => "standard", "reason" => "regular_order" }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Premium Order Processing
|
|
32
|
+
def process_premium_order(input)
|
|
33
|
+
order = input['order']
|
|
34
|
+
{
|
|
35
|
+
"premium_processed" => true,
|
|
36
|
+
"order_id" => order['id'],
|
|
37
|
+
"vip_handling" => true,
|
|
38
|
+
"dedicated_support" => true,
|
|
39
|
+
"processing_tier" => "premium"
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Bulk Order Processing
|
|
44
|
+
def check_bulk_inventory(input)
|
|
45
|
+
order = input['order']
|
|
46
|
+
required_quantity = input['required_quantity']
|
|
47
|
+
|
|
48
|
+
# Simulate inventory check
|
|
49
|
+
available = rand(0..1) == 1
|
|
50
|
+
{
|
|
51
|
+
"available" => available,
|
|
52
|
+
"checked_at" => Time.now.to_i,
|
|
53
|
+
"required_quantity" => required_quantity,
|
|
54
|
+
"available_quantity" => available ? required_quantity : 0
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_bulk_order(input)
|
|
59
|
+
order = input['order']
|
|
60
|
+
{
|
|
61
|
+
"bulk_processed" => true,
|
|
62
|
+
"order_id" => order['id'],
|
|
63
|
+
"volume_discount_applied" => true,
|
|
64
|
+
"special_handling" => true
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# International Order Processing
|
|
69
|
+
def process_international_order(input)
|
|
70
|
+
order = input['order']
|
|
71
|
+
country = input['destination_country']
|
|
72
|
+
|
|
73
|
+
{
|
|
74
|
+
"international_processed" => true,
|
|
75
|
+
"order_id" => order['id'],
|
|
76
|
+
"destination_country" => country,
|
|
77
|
+
"export_documentation" => "required",
|
|
78
|
+
"customs_declaration" => "needed"
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def calculate_customs_duty(input)
|
|
83
|
+
order_value = input['order_value'].to_f
|
|
84
|
+
country = input['country']
|
|
85
|
+
|
|
86
|
+
# Simple customs calculation
|
|
87
|
+
duty_rate = case country
|
|
88
|
+
when 'CA', 'MX' then 0.05
|
|
89
|
+
when 'EU' then 0.15
|
|
90
|
+
when 'UK' then 0.12
|
|
91
|
+
else 0.10
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
duty_amount = order_value * duty_rate
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
"duty_calculated" => true,
|
|
98
|
+
"duty_rate" => duty_rate,
|
|
99
|
+
"duty_amount" => duty_amount,
|
|
100
|
+
"total_with_duty" => order_value + duty_amount,
|
|
101
|
+
"currency" => "USD"
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Digital Order Processing
|
|
106
|
+
def process_digital_order(input)
|
|
107
|
+
order = input['order']
|
|
108
|
+
{
|
|
109
|
+
"digital_processed" => true,
|
|
110
|
+
"order_id" => order['id'],
|
|
111
|
+
"product_type" => order['digital_product']['type'],
|
|
112
|
+
"instant_delivery" => true
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def generate_digital_access(input)
|
|
117
|
+
{
|
|
118
|
+
"access_generated" => true,
|
|
119
|
+
"order_id" => input['order_id'],
|
|
120
|
+
"access_codes" => ["CODE-#{SecureRandom.hex(8)}"],
|
|
121
|
+
"download_links" => ["https://download.example.com/#{SecureRandom.hex(4)}"],
|
|
122
|
+
"license_key" => "LIC-#{SecureRandom.hex(12)}"
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Standard Order Processing
|
|
127
|
+
def process_standard_order(input)
|
|
128
|
+
order = input['order']
|
|
129
|
+
{
|
|
130
|
+
"standard_processed" => true,
|
|
131
|
+
"order_id" => order['id'],
|
|
132
|
+
"processing_tier" => "standard"
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Payment Processing
|
|
137
|
+
def process_payment(input)
|
|
138
|
+
amount = input['amount'].to_f
|
|
139
|
+
payment_method = input['payment_method']
|
|
140
|
+
|
|
141
|
+
# Simulate payment processing with occasional failures
|
|
142
|
+
success = amount < 2000 && payment_method != 'expired_card'
|
|
143
|
+
|
|
144
|
+
if success
|
|
145
|
+
{
|
|
146
|
+
"status" => "completed",
|
|
147
|
+
"payment_id" => "pay_#{SecureRandom.hex(8)}",
|
|
148
|
+
"amount_charged" => amount,
|
|
149
|
+
"currency" => input['currency'],
|
|
150
|
+
"processed_at" => Time.now.to_i
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
raise "Payment declined: #{payment_method} cannot process $#{amount}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def wait_for_payment_confirmation(input)
|
|
158
|
+
payment_id = input['payment_id']
|
|
159
|
+
|
|
160
|
+
# Simulate waiting for confirmation
|
|
161
|
+
sleep(0.1) # Reduced for testing
|
|
162
|
+
|
|
163
|
+
{
|
|
164
|
+
"confirmed" => true,
|
|
165
|
+
"payment_id" => payment_id,
|
|
166
|
+
"confirmation_code" => "CONF-#{SecureRandom.hex(6)}",
|
|
167
|
+
"confirmed_at" => Time.now.to_i
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def finalize_payment(input)
|
|
172
|
+
{
|
|
173
|
+
"finalized" => true,
|
|
174
|
+
"payment_id" => input['payment_id'],
|
|
175
|
+
"status" => "completed",
|
|
176
|
+
"finalized_at" => Time.now.to_i
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Inventory Management
|
|
181
|
+
def update_inventory(input)
|
|
182
|
+
order_id = input['order_id']
|
|
183
|
+
items = input['items']
|
|
184
|
+
|
|
185
|
+
# Simulate inventory update with occasional stock issues
|
|
186
|
+
out_of_stock = rand(0..9) == 0 # 10% chance of out of stock
|
|
187
|
+
|
|
188
|
+
if out_of_stock
|
|
189
|
+
raise "OutOfStock - Item unavailable for order #{order_id}"
|
|
190
|
+
else
|
|
191
|
+
{
|
|
192
|
+
"inventory_updated" => true,
|
|
193
|
+
"order_id" => order_id,
|
|
194
|
+
"items_processed" => items.size,
|
|
195
|
+
"updated_at" => Time.now.to_i
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Shipping Methods
|
|
201
|
+
def schedule_express_shipping(input)
|
|
202
|
+
order = input['order']
|
|
203
|
+
{
|
|
204
|
+
"shipping_scheduled" => true,
|
|
205
|
+
"order_id" => order['id'],
|
|
206
|
+
"method" => "express",
|
|
207
|
+
"estimated_days" => 1,
|
|
208
|
+
"tracking_number" => "EXP#{SecureRandom.hex(6).upcase}",
|
|
209
|
+
"priority" => "high"
|
|
210
|
+
}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def schedule_standard_shipping(input)
|
|
214
|
+
order = input['order']
|
|
215
|
+
{
|
|
216
|
+
"shipping_scheduled" => true,
|
|
217
|
+
"order_id" => order['id'],
|
|
218
|
+
"method" => "standard",
|
|
219
|
+
"estimated_days" => 3,
|
|
220
|
+
"tracking_number" => "STD#{SecureRandom.hex(6).upcase}"
|
|
221
|
+
}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def schedule_economy_shipping(input)
|
|
225
|
+
order = input['order']
|
|
226
|
+
{
|
|
227
|
+
"shipping_scheduled" => true,
|
|
228
|
+
"order_id" => order['id'],
|
|
229
|
+
"method" => "economy",
|
|
230
|
+
"estimated_days" => 7,
|
|
231
|
+
"tracking_number" => "ECO#{SecureRandom.hex(6).upcase}"
|
|
232
|
+
}
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Digital Delivery
|
|
236
|
+
def send_digital_delivery(input)
|
|
237
|
+
order = input['order']
|
|
238
|
+
{
|
|
239
|
+
"digital_delivered" => true,
|
|
240
|
+
"order_id" => order['id'],
|
|
241
|
+
"customer_email" => input['customer_email'],
|
|
242
|
+
"delivery_method" => "email",
|
|
243
|
+
"sent_at" => Time.now.to_i
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Notifications
|
|
248
|
+
def send_order_confirmation(input)
|
|
249
|
+
order = input['order']
|
|
250
|
+
{
|
|
251
|
+
"confirmation_sent" => true,
|
|
252
|
+
"order_id" => order['id'],
|
|
253
|
+
"customer_id" => input['customer'],
|
|
254
|
+
"sent_via" => ["email", "sms"],
|
|
255
|
+
"confirmation_id" => "CONF-#{SecureRandom.hex(6)}"
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Error Handling Methods
|
|
260
|
+
def handle_classification_error(input)
|
|
261
|
+
{
|
|
262
|
+
"classification_recovered" => true,
|
|
263
|
+
"order_id" => input['order']['id'],
|
|
264
|
+
"fallback_strategy" => "standard_processing",
|
|
265
|
+
"recovered_at" => Time.now.to_i
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def handle_premium_error(input)
|
|
270
|
+
{
|
|
271
|
+
"premium_recovered" => true,
|
|
272
|
+
"order_id" => input['order']['id'],
|
|
273
|
+
"fallback_strategy" => "standard_processing",
|
|
274
|
+
"recovered_at" => Time.now.to_i
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def handle_bulk_unavailable(input)
|
|
279
|
+
order = input['order']
|
|
280
|
+
{
|
|
281
|
+
"bulk_unavailable_handled" => true,
|
|
282
|
+
"order_id" => order['id'],
|
|
283
|
+
"action" => "offer_alternative",
|
|
284
|
+
"unavailable_items" => input['inventory']['unavailable_items'] || []
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def handle_bulk_error(input)
|
|
289
|
+
{
|
|
290
|
+
"bulk_error_recovered" => true,
|
|
291
|
+
"order_id" => input['order']['id'],
|
|
292
|
+
"fallback_strategy" => "standard_processing",
|
|
293
|
+
"recovered_at" => Time.now.to_i
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def handle_digital_error(input)
|
|
298
|
+
{
|
|
299
|
+
"digital_error_recovered" => true,
|
|
300
|
+
"order_id" => input['order']['id'],
|
|
301
|
+
"fallback_strategy" => "standard_processing",
|
|
302
|
+
"recovered_at" => Time.now.to_i
|
|
303
|
+
}
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def handle_payment_failure(input)
|
|
307
|
+
order = input['order']
|
|
308
|
+
{
|
|
309
|
+
"payment_failure_handled" => true,
|
|
310
|
+
"order_id" => order['id'],
|
|
311
|
+
"attempt" => input['payment_attempt'],
|
|
312
|
+
"next_action" => "notify_customer",
|
|
313
|
+
"handled_at" => Time.now.to_i
|
|
314
|
+
}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def handle_payment_error(input)
|
|
318
|
+
{
|
|
319
|
+
"payment_error_handled" => true,
|
|
320
|
+
"order_id" => input['order']['id'],
|
|
321
|
+
"action" => "escalate_to_support",
|
|
322
|
+
"handled_at" => Time.now.to_i
|
|
323
|
+
}
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def handle_out_of_stock(input)
|
|
327
|
+
order = input['order']
|
|
328
|
+
{
|
|
329
|
+
"out_of_stock_handled" => true,
|
|
330
|
+
"order_id" => order['id'],
|
|
331
|
+
"action" => "notify_customer_and_restock",
|
|
332
|
+
"handled_at" => Time.now.to_i
|
|
333
|
+
}
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def notify_payment_failure(input)
|
|
337
|
+
order = input['order']
|
|
338
|
+
{
|
|
339
|
+
"payment_failure_notified" => true,
|
|
340
|
+
"order_id" => order['id'],
|
|
341
|
+
"customer_notified" => true,
|
|
342
|
+
"notification_method" => "email",
|
|
343
|
+
"notified_at" => Time.now.to_i
|
|
344
|
+
}
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def notify_out_of_stock(input)
|
|
348
|
+
order = input['order']
|
|
349
|
+
{
|
|
350
|
+
"out_of_stock_notified" => true,
|
|
351
|
+
"order_id" => order['id'],
|
|
352
|
+
"customer_notified" => true,
|
|
353
|
+
"notification_method" => "email",
|
|
354
|
+
"notified_at" => Time.now.to_i
|
|
355
|
+
}
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def offer_alternative(input)
|
|
359
|
+
order = input['order']
|
|
360
|
+
{
|
|
361
|
+
"alternative_offered" => true,
|
|
362
|
+
"order_id" => order['id'],
|
|
363
|
+
"alternative_products" => ["similar_item_1", "similar_item_2"],
|
|
364
|
+
"discount_offered" => true,
|
|
365
|
+
"discount_percentage" => 10
|
|
366
|
+
}
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Simple executor that routes to ComplexOrderProcessor methods
|
|
371
|
+
class LocalMethodExecutor
|
|
372
|
+
def initialize
|
|
373
|
+
@processor = ComplexOrderProcessor.new
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def call(resource, input, credentials = nil)
|
|
377
|
+
method_name = resource.sub('method:', '')
|
|
378
|
+
|
|
379
|
+
unless @processor.respond_to?(method_name)
|
|
380
|
+
raise "Method not found: #{method_name}"
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
puts " ๐ Executing: #{method_name}"
|
|
384
|
+
@processor.send(method_name, input)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Simple workflow runner using the actual StatesLanguageMachine
|
|
389
|
+
class WorkflowTester
|
|
390
|
+
def initialize
|
|
391
|
+
@executor = LocalMethodExecutor.new
|
|
392
|
+
@workflow_file = 'complex_workflow.yaml'
|
|
393
|
+
generate_workflow_file
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def generate_workflow_file
|
|
397
|
+
workflow_yaml = <<~YAML
|
|
398
|
+
Comment: Complex E-commerce Order Processing Workflow
|
|
399
|
+
StartAt: ValidateInput
|
|
400
|
+
States:
|
|
401
|
+
ValidateInput:
|
|
402
|
+
Type: Pass
|
|
403
|
+
Parameters:
|
|
404
|
+
order_id.$: $.order.id
|
|
405
|
+
customer_id.$: $.order.customer_id
|
|
406
|
+
total_amount.$: $.order.total
|
|
407
|
+
item_count.$: $.order.items.length
|
|
408
|
+
timestamp: #{Time.now.to_i}
|
|
409
|
+
ResultPath: $.validation_metadata
|
|
410
|
+
Next: CheckOrderType
|
|
411
|
+
|
|
412
|
+
CheckOrderType:
|
|
413
|
+
Type: Task
|
|
414
|
+
Resource: method:determine_order_type
|
|
415
|
+
ResultPath: $.order_type_result
|
|
416
|
+
Next: RouteOrder
|
|
417
|
+
|
|
418
|
+
RouteOrder:
|
|
419
|
+
Type: Choice
|
|
420
|
+
Choices:
|
|
421
|
+
- Variable: $.order_type_result.order_type
|
|
422
|
+
StringEquals: "premium"
|
|
423
|
+
Next: ProcessPremiumOrder
|
|
424
|
+
- Variable: $.order_type_result.order_type
|
|
425
|
+
StringEquals: "bulk"
|
|
426
|
+
Next: CheckBulkInventory
|
|
427
|
+
- Variable: $.order_type_result.order_type
|
|
428
|
+
StringEquals: "international"
|
|
429
|
+
Next: ProcessInternationalOrder
|
|
430
|
+
- Variable: $.order_type_result.order_type
|
|
431
|
+
StringEquals: "digital"
|
|
432
|
+
Next: ProcessDigitalOrder
|
|
433
|
+
Default: ProcessStandardOrder
|
|
434
|
+
|
|
435
|
+
ProcessPremiumOrder:
|
|
436
|
+
Type: Task
|
|
437
|
+
Resource: method:process_premium_order
|
|
438
|
+
ResultPath: $.premium_result
|
|
439
|
+
Next: ProcessPayment
|
|
440
|
+
|
|
441
|
+
CheckBulkInventory:
|
|
442
|
+
Type: Task
|
|
443
|
+
Resource: method:check_bulk_inventory
|
|
444
|
+
Parameters:
|
|
445
|
+
order.$: $.order
|
|
446
|
+
required_quantity.$: $.order.quantity
|
|
447
|
+
ResultPath: $.bulk_inventory_result
|
|
448
|
+
Next: VerifyBulkAvailability
|
|
449
|
+
|
|
450
|
+
VerifyBulkAvailability:
|
|
451
|
+
Type: Choice
|
|
452
|
+
Choices:
|
|
453
|
+
- Variable: $.bulk_inventory_result.available
|
|
454
|
+
BooleanEquals: true
|
|
455
|
+
Next: ProcessBulkOrder
|
|
456
|
+
- Variable: $.bulk_inventory_result.available
|
|
457
|
+
BooleanEquals: false
|
|
458
|
+
Next: HandleBulkUnavailable
|
|
459
|
+
Default: HandleBulkUnavailable
|
|
460
|
+
|
|
461
|
+
ProcessBulkOrder:
|
|
462
|
+
Type: Task
|
|
463
|
+
Resource: method:process_bulk_order
|
|
464
|
+
ResultPath: $.bulk_result
|
|
465
|
+
Next: ProcessPayment
|
|
466
|
+
|
|
467
|
+
ProcessInternationalOrder:
|
|
468
|
+
Type: Task
|
|
469
|
+
Resource: method:process_international_order
|
|
470
|
+
ResultPath: $.international_result
|
|
471
|
+
Next: ProcessPayment
|
|
472
|
+
|
|
473
|
+
ProcessDigitalOrder:
|
|
474
|
+
Type: Task
|
|
475
|
+
Resource: method:process_digital_order
|
|
476
|
+
ResultPath: $.digital_result
|
|
477
|
+
Next: GenerateDigitalAccess
|
|
478
|
+
|
|
479
|
+
GenerateDigitalAccess:
|
|
480
|
+
Type: Task
|
|
481
|
+
Resource: method:generate_digital_access
|
|
482
|
+
ResultPath: $.digital_access_result
|
|
483
|
+
Next: SendDigitalDelivery
|
|
484
|
+
|
|
485
|
+
ProcessStandardOrder:
|
|
486
|
+
Type: Task
|
|
487
|
+
Resource: method:process_standard_order
|
|
488
|
+
ResultPath: $.standard_result
|
|
489
|
+
Next: ProcessPayment
|
|
490
|
+
|
|
491
|
+
ProcessPayment:
|
|
492
|
+
Type: Task
|
|
493
|
+
Resource: method:process_payment
|
|
494
|
+
Parameters:
|
|
495
|
+
order_id.$: $.order.id
|
|
496
|
+
amount.$: $.order.total
|
|
497
|
+
currency: "USD"
|
|
498
|
+
payment_method.$: $.order.payment_method
|
|
499
|
+
customer_id.$: $.order.customer_id
|
|
500
|
+
ResultPath: $.payment_result
|
|
501
|
+
Next: UpdateInventory
|
|
502
|
+
Catch:
|
|
503
|
+
- ErrorEquals: ["States.ALL"]
|
|
504
|
+
Next: HandlePaymentFailure
|
|
505
|
+
ResultPath: $.payment_error
|
|
506
|
+
|
|
507
|
+
HandlePaymentFailure:
|
|
508
|
+
Type: Task
|
|
509
|
+
Resource: method:handle_payment_failure
|
|
510
|
+
Parameters:
|
|
511
|
+
order.$: $.order
|
|
512
|
+
error.$: $.payment_error
|
|
513
|
+
payment_attempt: 1
|
|
514
|
+
ResultPath: $.payment_failure_result
|
|
515
|
+
Next: NotifyPaymentFailure
|
|
516
|
+
|
|
517
|
+
NotifyPaymentFailure:
|
|
518
|
+
Type: Task
|
|
519
|
+
Resource: method:notify_payment_failure
|
|
520
|
+
ResultPath: $.payment_failure_notification
|
|
521
|
+
End: true
|
|
522
|
+
|
|
523
|
+
UpdateInventory:
|
|
524
|
+
Type: Task
|
|
525
|
+
Resource: method:update_inventory
|
|
526
|
+
Parameters:
|
|
527
|
+
order_id.$: $.order.id
|
|
528
|
+
items.$: $.order.items
|
|
529
|
+
action: "decrement"
|
|
530
|
+
ResultPath: $.inventory_result
|
|
531
|
+
Next: HandleShipping
|
|
532
|
+
Catch:
|
|
533
|
+
- ErrorEquals: ["OutOfStock"]
|
|
534
|
+
Next: HandleOutOfStock
|
|
535
|
+
ResultPath: $.inventory_error
|
|
536
|
+
|
|
537
|
+
HandleOutOfStock:
|
|
538
|
+
Type: Task
|
|
539
|
+
Resource: method:handle_out_of_stock
|
|
540
|
+
ResultPath: $.out_of_stock_result
|
|
541
|
+
Next: NotifyOutOfStock
|
|
542
|
+
|
|
543
|
+
NotifyOutOfStock:
|
|
544
|
+
Type: Task
|
|
545
|
+
Resource: method:notify_out_of_stock
|
|
546
|
+
ResultPath: $.out_of_stock_notification
|
|
547
|
+
End: true
|
|
548
|
+
|
|
549
|
+
HandleBulkUnavailable:
|
|
550
|
+
Type: Task
|
|
551
|
+
Resource: method:handle_bulk_unavailable
|
|
552
|
+
ResultPath: $.bulk_unavailable_result
|
|
553
|
+
Next: ProcessStandardOrder
|
|
554
|
+
|
|
555
|
+
HandleShipping:
|
|
556
|
+
Type: Choice
|
|
557
|
+
Choices:
|
|
558
|
+
- Variable: $.order.shipping.required
|
|
559
|
+
BooleanEquals: false
|
|
560
|
+
Next: SendDigitalDelivery
|
|
561
|
+
- Variable: $.order_type_result.order_type
|
|
562
|
+
StringEquals: "premium"
|
|
563
|
+
Next: ScheduleExpressShipping
|
|
564
|
+
- Variable: $.order.total
|
|
565
|
+
NumericGreaterThan: 100
|
|
566
|
+
Next: ScheduleStandardShipping
|
|
567
|
+
Default: ScheduleEconomyShipping
|
|
568
|
+
|
|
569
|
+
ScheduleExpressShipping:
|
|
570
|
+
Type: Task
|
|
571
|
+
Resource: method:schedule_express_shipping
|
|
572
|
+
ResultPath: $.shipping_result
|
|
573
|
+
Next: SendOrderConfirmation
|
|
574
|
+
|
|
575
|
+
ScheduleStandardShipping:
|
|
576
|
+
Type: Task
|
|
577
|
+
Resource: method:schedule_standard_shipping
|
|
578
|
+
ResultPath: $.shipping_result
|
|
579
|
+
Next: SendOrderConfirmation
|
|
580
|
+
|
|
581
|
+
ScheduleEconomyShipping:
|
|
582
|
+
Type: Task
|
|
583
|
+
Resource: method:schedule_economy_shipping
|
|
584
|
+
ResultPath: $.shipping_result
|
|
585
|
+
Next: SendOrderConfirmation
|
|
586
|
+
|
|
587
|
+
SendDigitalDelivery:
|
|
588
|
+
Type: Task
|
|
589
|
+
Resource: method:send_digital_delivery
|
|
590
|
+
ResultPath: $.digital_delivery_result
|
|
591
|
+
Next: SendOrderConfirmation
|
|
592
|
+
|
|
593
|
+
SendOrderConfirmation:
|
|
594
|
+
Type: Task
|
|
595
|
+
Resource: method:send_order_confirmation
|
|
596
|
+
ResultPath: $.confirmation_result
|
|
597
|
+
End: true
|
|
598
|
+
YAML
|
|
599
|
+
|
|
600
|
+
File.write(@workflow_file, workflow_yaml)
|
|
601
|
+
puts "๐ Generated workflow file: #{@workflow_file}"
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def run_test_cases
|
|
605
|
+
test_cases = [
|
|
606
|
+
{
|
|
607
|
+
name: "Premium Order",
|
|
608
|
+
input: {
|
|
609
|
+
"order" => {
|
|
610
|
+
"id" => "ORD-PREM-001",
|
|
611
|
+
"total" => 750.00,
|
|
612
|
+
"customer_id" => "CUST-PREM-001",
|
|
613
|
+
"items" => ["premium_item_1", "premium_item_2"],
|
|
614
|
+
"quantity" => 2,
|
|
615
|
+
"premium_customer" => true,
|
|
616
|
+
"payment_method" => "credit_card",
|
|
617
|
+
"shipping" => { "required" => true, "country" => "US" }
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: "Bulk Order (Available)",
|
|
623
|
+
input: {
|
|
624
|
+
"order" => {
|
|
625
|
+
"id" => "ORD-BULK-001",
|
|
626
|
+
"total" => 1500.00,
|
|
627
|
+
"customer_id" => "CUST-BULK-001",
|
|
628
|
+
"items" => ["bulk_item_1"],
|
|
629
|
+
"quantity" => 25,
|
|
630
|
+
"payment_method" => "credit_card",
|
|
631
|
+
"shipping" => { "required" => true, "country" => "US" }
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: "Digital Order",
|
|
637
|
+
input: {
|
|
638
|
+
"order" => {
|
|
639
|
+
"id" => "ORD-DIG-001",
|
|
640
|
+
"total" => 49.99,
|
|
641
|
+
"customer_id" => "CUST-DIG-001",
|
|
642
|
+
"items" => ["ebook"],
|
|
643
|
+
"quantity" => 1,
|
|
644
|
+
"payment_method" => "paypal",
|
|
645
|
+
"shipping" => { "required" => false },
|
|
646
|
+
"digital_product" => { "type" => "ebook" },
|
|
647
|
+
"customer_email" => "customer@example.com"
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: "Standard Order",
|
|
653
|
+
input: {
|
|
654
|
+
"order" => {
|
|
655
|
+
"id" => "ORD-STD-001",
|
|
656
|
+
"total" => 89.99,
|
|
657
|
+
"customer_id" => "CUST-STD-001",
|
|
658
|
+
"items" => ["standard_item_1", "standard_item_2"],
|
|
659
|
+
"quantity" => 3,
|
|
660
|
+
"payment_method" => "credit_card",
|
|
661
|
+
"shipping" => { "required" => true, "country" => "US" }
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: "Payment Failure Order",
|
|
667
|
+
input: {
|
|
668
|
+
"order" => {
|
|
669
|
+
"id" => "ORD-FAIL-001",
|
|
670
|
+
"total" => 2500.00, # High amount that will fail payment
|
|
671
|
+
"customer_id" => "CUST-FAIL-001",
|
|
672
|
+
"items" => ["expensive_item"],
|
|
673
|
+
"quantity" => 1,
|
|
674
|
+
"payment_method" => "expired_card", # This will cause failure
|
|
675
|
+
"shipping" => { "required" => true, "country" => "US" }
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
]
|
|
680
|
+
|
|
681
|
+
test_cases.each do |test_case|
|
|
682
|
+
puts "\n" + "="*60
|
|
683
|
+
puts "๐งช Testing: #{test_case[:name]}"
|
|
684
|
+
puts "="*60
|
|
685
|
+
|
|
686
|
+
begin
|
|
687
|
+
# Load the state machine from YAML
|
|
688
|
+
state_machine = StatesLanguageMachine.from_yaml_file(@workflow_file)
|
|
689
|
+
|
|
690
|
+
# Start execution
|
|
691
|
+
execution = state_machine.start_execution(test_case[:input], "test-#{test_case[:input]['order']['id']}")
|
|
692
|
+
|
|
693
|
+
# Set the executor in context
|
|
694
|
+
execution.context[:task_executor] = @executor
|
|
695
|
+
|
|
696
|
+
# Run the workflow
|
|
697
|
+
execution.run_all
|
|
698
|
+
|
|
699
|
+
# Display results
|
|
700
|
+
puts "โ
Workflow completed successfully!"
|
|
701
|
+
puts "๐ Final Status: #{execution.status}"
|
|
702
|
+
puts "๐ฃ๏ธ Execution Path: #{execution.history.map { |h| h[:state_name] }.join(' โ ')}"
|
|
703
|
+
puts "โฑ๏ธ Execution Time: #{execution.execution_time.round(4)} seconds"
|
|
704
|
+
|
|
705
|
+
if execution.output
|
|
706
|
+
puts "๐ฆ Output Keys: #{execution.output.keys.join(', ')}"
|
|
707
|
+
# Show some key results
|
|
708
|
+
execution.output.each do |key, value|
|
|
709
|
+
if key =~ /result|status|confirmation/
|
|
710
|
+
puts " - #{key}: #{value.is_a?(Hash) ? value.keys.join(',') : value}"
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
rescue => e
|
|
716
|
+
puts "โ Workflow failed!"
|
|
717
|
+
puts "๐ฅ Error: #{e.message}"
|
|
718
|
+
if execution
|
|
719
|
+
puts "๐ Final Status: #{execution.status}"
|
|
720
|
+
puts "๐ Failed at: #{execution.history.last[:state_name]}" if execution.history.any?
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
puts "\n"
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Main execution
|
|
730
|
+
if __FILE__ == $0
|
|
731
|
+
begin
|
|
732
|
+
require 'ruby_slm'
|
|
733
|
+
|
|
734
|
+
puts "๐ Starting Complex Workflow Test"
|
|
735
|
+
puts "This tests Pass, Task, Choice, Succeed, and Fail states with local methods"
|
|
736
|
+
puts ""
|
|
737
|
+
|
|
738
|
+
tester = WorkflowTester.new
|
|
739
|
+
tester.run_test_cases
|
|
740
|
+
|
|
741
|
+
puts "๐ All tests completed!"
|
|
742
|
+
|
|
743
|
+
rescue LoadError
|
|
744
|
+
puts "โ Error: Could not load ruby_slm gem"
|
|
745
|
+
puts "Make sure the gem is installed and in your load path"
|
|
746
|
+
end
|
|
747
|
+
end
|