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.
- checksums.yaml +4 -4
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +19 -1
- data/README.md +147 -0
- data/bin/telegem-ssl +71 -25
- data/contributing.md +375 -0
- data/docs/api.md +663 -0
- data/docs/bot.md +332 -0
- data/docs/changelog.md +182 -0
- data/docs/context.md +554 -0
- data/docs/core_concepts.md +218 -0
- data/docs/deployment.md +702 -0
- data/docs/error_handling.md +435 -0
- data/docs/examples.md +752 -0
- data/docs/getting_started.md +151 -0
- data/docs/handlers.md +580 -0
- data/docs/keyboards.md +446 -0
- data/docs/middleware.md +536 -0
- data/docs/plugins.md +384 -0
- data/docs/scenes.md +517 -0
- data/docs/sessions.md +544 -0
- data/docs/testing.md +612 -0
- data/docs/troubleshooting.md +574 -0
- data/docs/types.md +538 -0
- data/docs/webhooks.md +456 -0
- data/lib/api/client.rb +38 -10
- data/lib/api/types.rb +129 -106
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +3 -3
- data/lib/webhook/server.rb +5 -3
- metadata +41 -29
- data/Contributing.md +0 -161
- data/Readme.md +0 -298
- data/bin/telegem-init +0 -32
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
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
|