telegem 3.3.1 → 3.4.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.
data/docs/testing.md ADDED
@@ -0,0 +1,612 @@
1
+ # Testing
2
+
3
+ Comprehensive testing ensures your Telegem bot works correctly and handles edge cases properly.
4
+
5
+ ## Test Setup
6
+
7
+ ### RSpec Configuration
8
+
9
+ ```ruby
10
+ # spec/spec_helper.rb
11
+ require 'telegem'
12
+ require 'rspec'
13
+ require 'webmock/rspec'
14
+
15
+ # Disable real HTTP requests
16
+ WebMock.disable_net_connect!(allow_localhost: true)
17
+
18
+ RSpec.configure do |config|
19
+ config.before(:each) do
20
+ WebMock.reset!
21
+ end
22
+ end
23
+
24
+ # Test helpers
25
+ module TestHelpers
26
+ def create_mock_update(type, **attrs)
27
+ # Create mock Telegram update
28
+ end
29
+
30
+ def simulate_message(bot, text, from: nil, chat: nil)
31
+ # Simulate incoming message
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include TestHelpers
37
+ end
38
+ ```
39
+
40
+ ### Test Bot Factory
41
+
42
+ ```ruby
43
+ # spec/support/bot_factory.rb
44
+ module BotFactory
45
+ def create_test_bot(token: 'test_token', **options)
46
+ bot = Telegem.new(token: token, **options)
47
+
48
+ # Mock API calls
49
+ allow(bot.api).to receive(:call).and_return({'ok' => true})
50
+
51
+ # Use memory store for tests
52
+ bot.session_store = Telegem::Session::MemoryStore.new
53
+
54
+ bot
55
+ end
56
+ end
57
+ ```
58
+
59
+ ## Unit Testing
60
+
61
+ ### Handler Testing
62
+
63
+ ```ruby
64
+ # spec/bot_handlers_spec.rb
65
+ RSpec.describe 'Bot Handlers' do
66
+ let(:bot) { create_test_bot }
67
+
68
+ describe '/start command' do
69
+ it 'responds with welcome message' do
70
+ expect(bot.api).to receive(:call).with('sendMessage', hash_including(text: /Welcome/))
71
+
72
+ simulate_message(bot, '/start')
73
+ end
74
+ end
75
+
76
+ describe 'hears pattern' do
77
+ it 'matches hello messages' do
78
+ expect(bot.api).to receive(:call).with('sendMessage', hash_including(text: 'Hi there!'))
79
+
80
+ simulate_message(bot, 'hello world')
81
+ end
82
+ end
83
+ end
84
+ ```
85
+
86
+ ### Context Testing
87
+
88
+ ```ruby
89
+ # spec/context_spec.rb
90
+ RSpec.describe Telegem::Context do
91
+ let(:bot) { create_test_bot }
92
+ let(:update) { create_mock_update(:message, text: 'test') }
93
+ let(:ctx) { Telegem::Context.new(bot, update) }
94
+
95
+ describe '#reply' do
96
+ it 'sends message to chat' do
97
+ expect(bot.api).to receive(:call).with('sendMessage',
98
+ chat_id: ctx.chat.id,
99
+ text: 'Hello'
100
+ )
101
+
102
+ ctx.reply('Hello')
103
+ end
104
+ end
105
+
106
+ describe '#session' do
107
+ it 'stores and retrieves data' do
108
+ ctx.session[:user_id] = 123
109
+ expect(ctx.session[:user_id]).to eq(123)
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### API Client Testing
116
+
117
+ ```ruby
118
+ # spec/api_client_spec.rb
119
+ RSpec.describe Telegem::API::Client do
120
+ let(:client) { Telegem::API::Client.new('test_token') }
121
+
122
+ describe '#call' do
123
+ it 'makes successful API call' do
124
+ stub_request(:post, /api\.telegram\.org/)
125
+ .to_return(body: {'ok' => true, 'result' => {}}.to_json)
126
+
127
+ result = client.call('getMe')
128
+ expect(result['ok']).to be true
129
+ end
130
+
131
+ it 'handles API errors' do
132
+ stub_request(:post, /api\.telegram\.org/)
133
+ .to_return(status: 400, body: {'ok' => false, 'description' => 'Bad Request'}.to_json)
134
+
135
+ expect { client.call('invalidMethod') }.to raise_error(Telegem::API::APIError)
136
+ end
137
+ end
138
+ end
139
+ ```
140
+
141
+ ## Integration Testing
142
+
143
+ ### Full Bot Testing
144
+
145
+ ```ruby
146
+ # spec/integration/bot_integration_spec.rb
147
+ RSpec.describe 'Bot Integration' do
148
+ let(:bot) { create_test_bot }
149
+
150
+ before do
151
+ bot.command('echo') do |ctx|
152
+ ctx.reply(ctx.text)
153
+ end
154
+
155
+ bot.hears(/^hello/) do |ctx|
156
+ ctx.reply('Hi there!')
157
+ end
158
+ end
159
+
160
+ it 'handles command' do
161
+ simulate_message(bot, '/echo test message')
162
+
163
+ expect(last_api_call).to include(
164
+ method: 'sendMessage',
165
+ text: 'test message'
166
+ )
167
+ end
168
+
169
+ it 'handles hears pattern' do
170
+ simulate_message(bot, 'hello world')
171
+
172
+ expect(last_api_call).to include(
173
+ method: 'sendMessage',
174
+ text: 'Hi there!'
175
+ )
176
+ end
177
+ end
178
+ ```
179
+
180
+ ### Middleware Testing
181
+
182
+ ```ruby
183
+ # spec/middleware_spec.rb
184
+ RSpec.describe 'Middleware Chain' do
185
+ let(:bot) { create_test_bot }
186
+
187
+ it 'executes middleware in order' do
188
+ execution_order = []
189
+
190
+ bot.use(lambda do |ctx, next_middleware|
191
+ execution_order << :first
192
+ next_middleware.call(ctx)
193
+ execution_order << :first_after
194
+ end)
195
+
196
+ bot.use(lambda do |ctx, next_middleware|
197
+ execution_order << :second
198
+ next_middleware.call(ctx)
199
+ execution_order << :second_after
200
+ end)
201
+
202
+ bot.command('test') do |ctx|
203
+ execution_order << :handler
204
+ ctx.reply('done')
205
+ end
206
+
207
+ simulate_message(bot, '/test')
208
+
209
+ expect(execution_order).to eq([
210
+ :first, :second, :handler, :second_after, :first_after
211
+ ])
212
+ end
213
+ end
214
+ ```
215
+
216
+ ## Scene Testing
217
+
218
+ ### Scene Flow Testing
219
+
220
+ ```ruby
221
+ # spec/scenes_spec.rb
222
+ RSpec.describe 'User Scenes' do
223
+ let(:bot) { create_test_bot }
224
+
225
+ before do
226
+ bot.scene('registration') do |scene|
227
+ scene.step('name') do |ctx|
228
+ ctx.reply('Enter your name:')
229
+ ctx.session[:name] = ctx.text
230
+ scene.next_step
231
+ end
232
+
233
+ scene.step('age') do |ctx|
234
+ ctx.reply('Enter your age:')
235
+ ctx.session[:age] = ctx.text.to_i
236
+ scene.complete
237
+ end
238
+ end
239
+ end
240
+
241
+ it 'completes registration scene' do
242
+ # Start scene
243
+ simulate_message(bot, '/register')
244
+
245
+ # Enter name
246
+ simulate_message(bot, 'John Doe')
247
+
248
+ # Enter age
249
+ simulate_message(bot, '25')
250
+
251
+ # Verify completion
252
+ expect(user_session[:name]).to eq('John Doe')
253
+ expect(user_session[:age]).to eq(25)
254
+ expect(user_session[:current_scene]).to be_nil
255
+ end
256
+ end
257
+ ```
258
+
259
+ ## Plugin Testing
260
+
261
+ ### FileExtract Plugin Testing
262
+
263
+ ```ruby
264
+ # spec/plugins/file_extract_spec.rb
265
+ RSpec.describe Telegem::Plugins::FileExtract do
266
+ let(:bot) { create_test_bot }
267
+ let(:file_id) { 'test_file_id' }
268
+
269
+ describe '#extract' do
270
+ it 'extracts PDF content' do
271
+ # Mock file download
272
+ allow(bot.api).to receive(:download_file).and_return(pdf_content)
273
+
274
+ extractor = Telegem::Plugins::FileExtract.new(bot, file_id)
275
+ result = extractor.extract
276
+
277
+ expect(result[:success]).to be true
278
+ expect(result[:type]).to eq(:pdf)
279
+ expect(result[:content]).to include('extracted text')
280
+ end
281
+
282
+ it 'handles extraction errors' do
283
+ allow(bot.api).to receive(:download_file).and_raise(StandardError)
284
+
285
+ extractor = Telegem::Plugins::FileExtract.new(bot, file_id)
286
+ result = extractor.extract
287
+
288
+ expect(result[:success]).to be false
289
+ expect(result[:error]).to be_present
290
+ end
291
+ end
292
+ end
293
+ ```
294
+
295
+ ## Mock Data
296
+
297
+ ### Mock Update Creation
298
+
299
+ ```ruby
300
+ # spec/support/mock_updates.rb
301
+ def create_mock_update(type, **attrs)
302
+ base_update = {
303
+ update_id: rand(1000000),
304
+ message: nil,
305
+ edited_message: nil,
306
+ channel_post: nil,
307
+ edited_channel_post: nil,
308
+ inline_query: nil,
309
+ chosen_inline_result: nil,
310
+ callback_query: nil,
311
+ shipping_query: nil,
312
+ pre_checkout_query: nil
313
+ }
314
+
315
+ case type
316
+ when :message
317
+ base_update[:message] = create_mock_message(**attrs)
318
+ when :callback_query
319
+ base_update[:callback_query] = create_mock_callback_query(**attrs)
320
+ end
321
+
322
+ base_update
323
+ end
324
+
325
+ def create_mock_message(**attrs)
326
+ {
327
+ message_id: rand(100000),
328
+ from: create_mock_user,
329
+ chat: create_mock_chat,
330
+ date: Time.now.to_i,
331
+ text: attrs[:text] || 'test message',
332
+ entities: attrs[:entities] || []
333
+ }.merge(attrs)
334
+ end
335
+
336
+ def create_mock_user(**attrs)
337
+ {
338
+ id: rand(1000000000),
339
+ is_bot: false,
340
+ first_name: 'Test',
341
+ last_name: 'User',
342
+ username: 'testuser'
343
+ }.merge(attrs)
344
+ end
345
+
346
+ def create_mock_chat(**attrs)
347
+ {
348
+ id: rand(1000000000),
349
+ type: 'private',
350
+ title: nil,
351
+ username: nil,
352
+ first_name: 'Test',
353
+ last_name: 'User'
354
+ }.merge(attrs)
355
+ end
356
+ ```
357
+
358
+ ### API Response Stubbing
359
+
360
+ ```ruby
361
+ # spec/support/api_stubs.rb
362
+ def stub_telegram_api(method, response = {}, status = 200)
363
+ stub_request(:post, "https://api.telegram.org/bot#{@token}/#{method}")
364
+ .to_return(
365
+ status: status,
366
+ body: response.to_json,
367
+ headers: {'Content-Type' => 'application/json'}
368
+ )
369
+ end
370
+
371
+ def stub_successful_send_message(text = nil)
372
+ stub_telegram_api('sendMessage', {
373
+ 'ok' => true,
374
+ 'result' => {
375
+ 'message_id' => rand(100000),
376
+ 'from' => {'id' => 123456, 'is_bot' => true, 'first_name' => 'TestBot'},
377
+ 'chat' => {'id' => 789, 'type' => 'private'},
378
+ 'date' => Time.now.to_i,
379
+ 'text' => text || 'Test response'
380
+ }
381
+ })
382
+ end
383
+ ```
384
+
385
+ ## Test Helpers
386
+
387
+ ### Message Simulation
388
+
389
+ ```ruby
390
+ # spec/support/message_simulation.rb
391
+ def simulate_message(bot, text, from: nil, chat: nil, **attrs)
392
+ update = create_mock_update(:message,
393
+ text: text,
394
+ from: from || create_mock_user,
395
+ chat: chat || create_mock_chat,
396
+ **attrs
397
+ )
398
+
399
+ # Process update through bot
400
+ bot.process_update(update)
401
+ end
402
+
403
+ def simulate_callback(bot, data, from: nil, message: nil, **attrs)
404
+ callback_query = create_mock_callback_query(
405
+ data: data,
406
+ from: from || create_mock_user,
407
+ message: message || create_mock_message,
408
+ **attrs
409
+ )
410
+
411
+ update = create_mock_update(:callback_query, callback_query: callback_query)
412
+ bot.process_update(update)
413
+ end
414
+
415
+ def last_api_call
416
+ WebMock::RequestRegistry.instance.requested_signatures.last
417
+ end
418
+ ```
419
+
420
+ ## Asynchronous Testing
421
+
422
+ ### Async Handler Testing
423
+
424
+ ```ruby
425
+ # spec/async_handlers_spec.rb
426
+ RSpec.describe 'Async Handlers' do
427
+ let(:bot) { create_test_bot }
428
+
429
+ it 'handles async operations' do
430
+ responses = []
431
+
432
+ bot.command('async') do |ctx|
433
+ Async do
434
+ sleep 0.1
435
+ responses << 'async_done'
436
+ ctx.reply('Async complete')
437
+ end
438
+ responses << 'handler_done'
439
+ end
440
+
441
+ simulate_message(bot, '/async')
442
+
443
+ # Wait for async operation
444
+ sleep 0.2
445
+
446
+ expect(responses).to eq(['handler_done', 'async_done'])
447
+ expect(last_api_call[:body]).to include('Async complete')
448
+ end
449
+ end
450
+ ```
451
+
452
+ ## Performance Testing
453
+
454
+ ### Load Testing
455
+
456
+ ```ruby
457
+ # spec/performance/load_spec.rb
458
+ RSpec.describe 'Bot Performance' do
459
+ let(:bot) { create_test_bot }
460
+
461
+ it 'handles multiple concurrent requests' do
462
+ bot.command('test') do |ctx|
463
+ ctx.reply("Response #{ctx.message.message_id}")
464
+ end
465
+
466
+ # Simulate concurrent requests
467
+ threads = 10.times.map do |i|
468
+ Thread.new do
469
+ simulate_message(bot, "/test #{i}")
470
+ end
471
+ end
472
+
473
+ threads.each(&:join)
474
+
475
+ # Verify all requests processed
476
+ expect(WebMock::RequestRegistry.instance.requested_signatures.size).to eq(10)
477
+ end
478
+ end
479
+ ```
480
+
481
+ ### Benchmark Testing
482
+
483
+ ```ruby
484
+ # spec/performance/benchmark_spec.rb
485
+ require 'benchmark'
486
+
487
+ RSpec.describe 'Bot Benchmarks' do
488
+ let(:bot) { create_test_bot }
489
+
490
+ it 'processes messages quickly' do
491
+ bot.command('bench') do |ctx|
492
+ # Simple response
493
+ ctx.reply('OK')
494
+ end
495
+
496
+ time = Benchmark.realtime do
497
+ 100.times { simulate_message(bot, '/bench') }
498
+ end
499
+
500
+ avg_time = time / 100
501
+ expect(avg_time).to be < 0.01 # Less than 10ms per message
502
+ end
503
+ end
504
+ ```
505
+
506
+ ## Continuous Integration
507
+
508
+ ### GitHub Actions
509
+
510
+ ```yaml
511
+ # .github/workflows/test.yml
512
+ name: Tests
513
+
514
+ on: [push, pull_request]
515
+
516
+ jobs:
517
+ test:
518
+ runs-on: ubuntu-latest
519
+
520
+ steps:
521
+ - uses: actions/checkout@v3
522
+ - name: Set up Ruby
523
+ uses: ruby/setup-ruby@v1
524
+ with:
525
+ ruby-version: 3.1
526
+ bundler-cache: true
527
+
528
+ - name: Run tests
529
+ run: bundle exec rspec
530
+
531
+ - name: Upload coverage
532
+ uses: codecov/codecov-action@v3
533
+ with:
534
+ file: ./coverage/coverage.xml
535
+ ```
536
+
537
+ ### Code Coverage
538
+
539
+ ```ruby
540
+ # spec/spec_helper.rb
541
+ require 'simplecov'
542
+
543
+ SimpleCov.start do
544
+ add_filter '/spec/'
545
+ minimum_coverage 90
546
+ end
547
+ ```
548
+
549
+ ## Test Organization
550
+
551
+ ### Directory Structure
552
+
553
+ ```
554
+ spec/
555
+ ├── spec_helper.rb
556
+ ├── support/
557
+ │ ├── bot_factory.rb
558
+ │ ├── mock_updates.rb
559
+ │ ├── api_stubs.rb
560
+ │ └── message_simulation.rb
561
+ ├── unit/
562
+ │ ├── api_client_spec.rb
563
+ │ ├── context_spec.rb
564
+ │ └── middleware_spec.rb
565
+ ├── integration/
566
+ │ ├── bot_integration_spec.rb
567
+ │ └── scenes_spec.rb
568
+ ├── plugins/
569
+ │ ├── file_extract_spec.rb
570
+ │ └── translate_spec.rb
571
+ ├── performance/
572
+ │ ├── load_spec.rb
573
+ │ └── benchmark_spec.rb
574
+ └── features/
575
+ └── end_to_end_spec.rb
576
+ ```
577
+
578
+ ### Test Naming Conventions
579
+
580
+ ```ruby
581
+ # Good
582
+ describe '#reply' do
583
+ context 'when chat is private' do
584
+ it 'sends message to user' do
585
+ # test
586
+ end
587
+ end
588
+ end
589
+
590
+ # Bad
591
+ describe 'reply method' do
592
+ it 'test reply' do
593
+ # test
594
+ end
595
+ end
596
+ ```
597
+
598
+ ## Best Practices
599
+
600
+ 1. **Test behavior, not implementation**
601
+ 2. **Use descriptive test names**
602
+ 3. **Keep tests isolated and independent**
603
+ 4. **Mock external dependencies**
604
+ 5. **Test edge cases and error conditions**
605
+ 6. **Use factories for test data**
606
+ 7. **Maintain high code coverage**
607
+ 8. **Run tests in CI/CD pipeline**
608
+ 9. **Test async operations properly**
609
+ 10. **Document complex test scenarios**
610
+
611
+ Comprehensive testing ensures your bot is reliable, maintainable, and ready for production deployment.</content>
612
+ <parameter name="filePath">/home/slick/telegem/docs/testing.md