super8 0.2.1

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.
@@ -0,0 +1,76 @@
1
+ require "super8"
2
+ require "fakefs/spec_helpers"
3
+
4
+ RSpec.describe Super8::Cassette do
5
+ include FakeFS::SpecHelpers
6
+
7
+ let(:cassette_dir) { "spec/super8_cassettes" }
8
+
9
+ before { Super8.config.cassette_directory = cassette_dir }
10
+
11
+ describe "#path" do
12
+ it "joins cassette_directory with the cassette name" do
13
+ cassette = described_class.new("my_cassette")
14
+ expect(cassette.path).to eq("spec/super8_cassettes/my_cassette")
15
+ end
16
+ end
17
+
18
+ describe "#exists?" do
19
+ it "returns false when directory does not exist" do
20
+ cassette = described_class.new("missing")
21
+ expect(cassette.exists?).to be false
22
+ end
23
+
24
+ it "returns true when directory exists" do
25
+ FileUtils.mkdir_p("#{cassette_dir}/existing")
26
+ cassette = described_class.new("existing")
27
+ expect(cassette.exists?).to be true
28
+ end
29
+ end
30
+
31
+ describe "#save" do
32
+ it "creates the cassette directory" do
33
+ cassette = described_class.new("new_cassette")
34
+ cassette.save
35
+ expect(Dir.exist?("#{cassette_dir}/new_cassette")).to be true
36
+ end
37
+
38
+ it "creates parent directories as needed" do
39
+ Super8.config.cassette_directory = "deep/nested/cassettes"
40
+ cassette = described_class.new("test")
41
+ cassette.save
42
+ expect(Dir.exist?("deep/nested/cassettes/test")).to be true
43
+ end
44
+
45
+ it "creates metadata.yml" do
46
+ cassette = described_class.new("test_metadata")
47
+ cassette.save
48
+ expect(File.exist?("#{cassette_dir}/test_metadata/metadata.yml")).to be true
49
+ end
50
+ end
51
+
52
+ describe "#load" do
53
+ it "raises CassetteNotFoundError when cassette does not exist" do
54
+ cassette = described_class.new("missing")
55
+ expect { cassette.load }.to raise_error(
56
+ Super8::CassetteNotFoundError,
57
+ /Cassette not found:.*missing/
58
+ )
59
+ end
60
+
61
+ it "loads cassettes and allows playback" do
62
+ FileUtils.mkdir_p("#{cassette_dir}/test_cassette")
63
+ commands = [
64
+ {"method" => "connect", "connection_id" => "a", "dsn" => "test"},
65
+ {"method" => "run", "connection_id" => "a", "statement_id" => 1}
66
+ ]
67
+ File.write("#{cassette_dir}/test_cassette/commands.yml", commands.to_yaml)
68
+
69
+ cassette = described_class.new("test_cassette")
70
+ cassette.load
71
+
72
+ expect(cassette.next_command["method"]).to eq("connect")
73
+ expect(cassette.next_command["method"]).to eq("run")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ require "super8/config"
2
+
3
+ RSpec.describe Super8::Config do
4
+ subject(:config) { described_class.new }
5
+
6
+ describe "defaults" do
7
+ it "sets cassette_directory to spec/super8_cassettes" do
8
+ expect(config.cassette_directory).to eq("spec/super8_cassettes")
9
+ end
10
+
11
+ it "sets record_mode to :once" do
12
+ expect(config.record_mode).to eq(:once)
13
+ end
14
+ end
15
+
16
+ describe "accessors" do
17
+ it "allows setting cassette_directory" do
18
+ config.cassette_directory = "other/path"
19
+ expect(config.cassette_directory).to eq("other/path")
20
+ end
21
+
22
+ it "allows setting record_mode" do
23
+ config.record_mode = :none
24
+ expect(config.record_mode).to eq(:none)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,534 @@
1
+ require "spec_helper"
2
+ require "fakefs/spec_helpers"
3
+
4
+ RSpec.describe "ODBC interception" do # rubocop:disable RSpec/DescribeClass
5
+ include FakeFS::SpecHelpers
6
+
7
+ let(:cassette_name) { "test_cassette" }
8
+ let(:cassette_dir) { Super8.config.cassette_directory }
9
+ let(:cassette_path) { File.join(cassette_dir, cassette_name) }
10
+ let(:fake_db) { instance_double(ODBC::Database) }
11
+ let(:fake_statement) { instance_double(ODBC::Statement) }
12
+
13
+ # Store original method to test restoration
14
+ let(:original_connect) { ODBC.method(:connect) }
15
+
16
+ before do
17
+ allow(fake_db).to receive(:run)
18
+ allow(ODBC).to receive(:connect) do |_dsn, &block|
19
+ block&.call(fake_db)
20
+ end
21
+ end
22
+
23
+ describe "ODBC.connect" do
24
+ it "records connect command to commands.yml" do
25
+ Super8.use_cassette(cassette_name) do
26
+ ODBC.connect("retalix") {} # rubocop:disable Lint/EmptyBlock
27
+ end
28
+
29
+ commands_file = File.join(cassette_path, "commands.yml")
30
+ commands = YAML.load_file(commands_file)
31
+ connect_command = commands.first
32
+
33
+ aggregate_failures do
34
+ expect(connect_command["method"]).to eq("connect")
35
+ expect(connect_command["connection_id"]).to eq("a")
36
+ expect(connect_command["dsn"]).to eq("retalix")
37
+ end
38
+ end
39
+
40
+ it "restores original ODBC.connect after use_cassette block" do
41
+ method_during_block = nil
42
+
43
+ Super8.use_cassette(cassette_name) do
44
+ method_during_block = ODBC.method(:connect)
45
+ ODBC.connect("retalix") {} # rubocop:disable Lint/EmptyBlock
46
+ end
47
+
48
+ # The method should have been replaced during the block
49
+ expect(method_during_block).not_to eq(original_connect)
50
+
51
+ # And restored after
52
+ expect(ODBC.method(:connect)).to eq(original_connect)
53
+ end
54
+ end
55
+
56
+ describe "Database#run" do
57
+ it "records SQL queries to commands.yml" do # rubocop:disable RSpec/ExampleLength
58
+ sql = "SELECT ID, NAME FROM USERS WHERE STATUS = 'A'"
59
+ allow(fake_db).to receive(:run).with(sql).and_return(fake_statement)
60
+
61
+ Super8.use_cassette(cassette_name) do
62
+ ODBC.connect("retalix") do |db|
63
+ db.run(sql)
64
+ end
65
+ end
66
+
67
+ commands_file = File.join(cassette_path, "commands.yml")
68
+ expect(File.exist?(commands_file)).to be true
69
+
70
+ commands = YAML.load_file(commands_file)
71
+ expect(commands).to be_an(Array)
72
+ expect(commands.length).to eq(2) # connect + run
73
+
74
+ run_command = commands[1]
75
+
76
+ aggregate_failures do
77
+ expect(run_command["method"]).to eq("run")
78
+ expect(run_command["connection_id"]).to eq("a")
79
+ expect(run_command["sql"]).to eq(sql)
80
+ expect(run_command["params"]).to eq([])
81
+ expect(run_command["statement_id"]).to eq(1)
82
+ end
83
+ end
84
+
85
+ it "records multiple queries with sequential statement IDs" do # rubocop:disable RSpec/ExampleLength
86
+ sql1 = "SELECT * FROM CUSTOMERS"
87
+ sql2 = "SELECT * FROM ORDERS"
88
+ allow(fake_db).to receive(:run).with(sql1).and_return(fake_statement)
89
+ allow(fake_db).to receive(:run).with(sql2).and_return(fake_statement)
90
+
91
+ Super8.use_cassette(cassette_name) do
92
+ ODBC.connect("retalix") do |db|
93
+ db.run(sql1)
94
+ db.run(sql2)
95
+ end
96
+ end
97
+
98
+ commands_file = File.join(cassette_path, "commands.yml")
99
+ commands = YAML.load_file(commands_file)
100
+
101
+ run_commands = commands.select { |cmd| cmd["method"] == "run" }
102
+ expect(run_commands.length).to eq(2)
103
+ expect(run_commands[0]["statement_id"]).to eq(1)
104
+ expect(run_commands[1]["statement_id"]).to eq(2)
105
+ end
106
+
107
+ it "records commands in array order" do # rubocop:disable RSpec/ExampleLength
108
+ sql = "SELECT COUNT(*) FROM PRODUCTS"
109
+ allow(fake_db).to receive(:run).with(sql).and_return(fake_statement)
110
+
111
+ Super8.use_cassette(cassette_name) do
112
+ ODBC.connect("retalix") do |db|
113
+ db.run(sql)
114
+ end
115
+ end
116
+
117
+ commands_file = File.join(cassette_path, "commands.yml")
118
+ commands = YAML.load_file(commands_file)
119
+
120
+ expect(commands).to be_an(Array)
121
+ expect(commands.length).to eq(2) # connect + run
122
+ expect(commands[0]["method"]).to eq("connect")
123
+ expect(commands[1]["method"]).to eq("run")
124
+ end
125
+ end
126
+
127
+ describe "Statement#columns" do
128
+ let(:columns_hash) { {"ID" => "Integer", "NAME" => "String"} }
129
+ let(:columns_array) { [{"name" => "ID", "type" => 4}, {"name" => "NAME", "type" => 1}] }
130
+
131
+ before do
132
+ allow(fake_db).to receive(:run).and_return(fake_statement)
133
+ end
134
+
135
+ it "records columns call with as_ary=false and returns original result" do # rubocop:disable RSpec/ExampleLength
136
+ allow(fake_statement).to receive(:columns).with(false).and_return(columns_hash)
137
+
138
+ result = nil
139
+ Super8.use_cassette(cassette_name) do
140
+ ODBC.connect("retalix") do |db|
141
+ statement = db.run("SELECT * FROM USERS")
142
+ result = statement.columns
143
+ end
144
+ end
145
+
146
+ expect(result).to eq(columns_hash)
147
+
148
+ commands_file = File.join(cassette_path, "commands.yml")
149
+ commands = YAML.load_file(commands_file)
150
+
151
+ columns_command = commands.find { |cmd| cmd["method"] == "columns" }
152
+ expect(columns_command).not_to be_nil
153
+
154
+ aggregate_failures do
155
+ expect(columns_command["method"]).to eq("columns")
156
+ expect(columns_command["connection_id"]).to eq("a")
157
+ expect(columns_command["statement_id"]).to eq(1)
158
+ expect(columns_command["as_ary"]).to be false
159
+ expect(columns_command["result"]).to eq(columns_hash)
160
+ end
161
+ end
162
+
163
+ it "records columns call with as_ary=true and returns original result" do # rubocop:disable RSpec/ExampleLength
164
+ allow(fake_statement).to receive(:columns).with(true).and_return(columns_array)
165
+
166
+ result = nil
167
+ Super8.use_cassette(cassette_name) do
168
+ ODBC.connect("retalix") do |db|
169
+ statement = db.run("SELECT * FROM USERS")
170
+ result = statement.columns(true)
171
+ end
172
+ end
173
+
174
+ expect(result).to eq(columns_array)
175
+
176
+ commands_file = File.join(cassette_path, "commands.yml")
177
+ commands = YAML.load_file(commands_file)
178
+
179
+ columns_command = commands.find { |cmd| cmd["method"] == "columns" }
180
+ expect(columns_command).not_to be_nil
181
+
182
+ aggregate_failures do
183
+ expect(columns_command["method"]).to eq("columns")
184
+ expect(columns_command["connection_id"]).to eq("a")
185
+ expect(columns_command["statement_id"]).to eq(1)
186
+ expect(columns_command["as_ary"]).to be true
187
+ expect(columns_command["result"]).to eq(columns_array)
188
+ end
189
+ end
190
+
191
+ it "records multiple columns calls in sequence" do # rubocop:disable RSpec/ExampleLength
192
+ allow(fake_statement).to receive(:columns).with(false).and_return(columns_hash)
193
+ allow(fake_statement).to receive(:columns).with(true).and_return(columns_array)
194
+
195
+ Super8.use_cassette(cassette_name) do
196
+ ODBC.connect("retalix") do |db|
197
+ statement = db.run("SELECT * FROM USERS")
198
+ statement.columns
199
+ statement.columns(true)
200
+ end
201
+ end
202
+
203
+ commands_file = File.join(cassette_path, "commands.yml")
204
+ commands = YAML.load_file(commands_file)
205
+
206
+ columns_commands = commands.select { |cmd| cmd["method"] == "columns" }
207
+
208
+ aggregate_failures do
209
+ expect(columns_commands.length).to eq(2)
210
+ expect(columns_commands[0]["as_ary"]).to be false
211
+ expect(columns_commands[0]["result"]).to eq(columns_hash)
212
+ expect(columns_commands[1]["as_ary"]).to be true
213
+ expect(columns_commands[1]["result"]).to eq(columns_array)
214
+ end
215
+ end
216
+ end
217
+
218
+ # TODO: break these specs up meaningfully
219
+ describe "Statement#fetch_all" do
220
+ let(:row_data) { [["001", "Alice"], ["002", "Bob"], ["003", "Charlie"]] }
221
+
222
+ it "records fetch_all call and saves row data to CSV file" do # rubocop:disable RSpec/ExampleLength
223
+ allow(fake_db).to receive(:run).with("SELECT id, name FROM users").and_return(fake_statement)
224
+ allow(fake_statement).to receive(:fetch_all).and_return(row_data)
225
+
226
+ Super8.use_cassette(cassette_name) do
227
+ ODBC.connect("retalix") do |db|
228
+ statement = db.run("SELECT id, name FROM users")
229
+ result = statement.fetch_all
230
+ expect(result).to eq(row_data) # Should return original result
231
+ end
232
+ end
233
+
234
+ commands_file = File.join(cassette_path, "commands.yml")
235
+ commands = YAML.load_file(commands_file)
236
+
237
+ # Check command log entry
238
+ fetch_command = commands.find { |cmd| cmd["method"] == "fetch_all" }
239
+
240
+ aggregate_failures do
241
+ expect(fetch_command).not_to be_nil
242
+ expect(fetch_command["method"]).to eq("fetch_all")
243
+ expect(fetch_command["connection_id"]).to eq("a")
244
+ expect(fetch_command["statement_id"]).to eq(1)
245
+ expect(fetch_command["rows_file"]).to eq("a_1_fetch_all.csv")
246
+ end
247
+
248
+ # Check CSV file was created with correct data
249
+ csv_file = File.join(cassette_path, "a_1_fetch_all.csv")
250
+ expect(File.exist?(csv_file)).to be true
251
+
252
+ csv_content = CSV.read(csv_file)
253
+ expect(csv_content).to eq(row_data)
254
+ end
255
+
256
+ it "handles empty result sets" do # rubocop:disable RSpec/ExampleLength
257
+ allow(fake_db).to receive(:run).with("SELECT * FROM empty_table").and_return(fake_statement)
258
+ allow(fake_statement).to receive(:fetch_all).and_return([])
259
+
260
+ Super8.use_cassette(cassette_name) do
261
+ ODBC.connect("retalix") do |db|
262
+ statement = db.run("SELECT * FROM empty_table")
263
+ result = statement.fetch_all
264
+ expect(result).to eq([])
265
+ end
266
+ end
267
+
268
+ commands_file = File.join(cassette_path, "commands.yml")
269
+ commands = YAML.load_file(commands_file)
270
+
271
+ fetch_command = commands.find { |cmd| cmd["method"] == "fetch_all" }
272
+ expect(fetch_command["rows_file"]).to eq("a_1_fetch_all.csv")
273
+
274
+ # CSV file should exist but be empty
275
+ csv_file = File.join(cassette_path, "a_1_fetch_all.csv")
276
+ expect(File.exist?(csv_file)).to be true
277
+ expect(CSV.read(csv_file)).to eq([])
278
+ end
279
+
280
+ it "normalizes nil results to empty arrays" do # rubocop:disable RSpec/ExampleLength
281
+ query = "SELECT * FROM truly_empty_table"
282
+ allow(fake_db).to receive(:run).with(query).and_return(fake_statement)
283
+ allow(fake_statement).to receive(:fetch_all).and_return(nil)
284
+
285
+ Super8.use_cassette(cassette_name) do
286
+ ODBC.connect("retalix") do |db|
287
+ statement = db.run(query)
288
+ result = statement.fetch_all
289
+ expect(result).to be_nil # Should still return original nil
290
+ end
291
+ end
292
+
293
+ # But in the cassette, it should be stored as empty array
294
+ csv_file = File.join(cassette_path, "a_1_fetch_all.csv")
295
+ expect(File.exist?(csv_file)).to be true
296
+ expect(CSV.read(csv_file)).to eq([]) # Stored as empty array
297
+ end
298
+
299
+ describe "multiple fetch_all calls with sequential file naming" do
300
+ let(:row_data1) { [["001", "Alice"]] }
301
+ let(:row_data2) { [["002", "Bob"], ["003", "Charlie"]] }
302
+
303
+ before do
304
+ fake_statement1 = instance_double(ODBC::Statement)
305
+ fake_statement2 = instance_double(ODBC::Statement)
306
+ allow(fake_statement1).to receive(:fetch_all).and_return(row_data1)
307
+ allow(fake_statement2).to receive(:fetch_all).and_return(row_data2)
308
+ allow(fake_db).to receive(:run).with("SELECT * FROM users").and_return(fake_statement1)
309
+ allow(fake_db).to receive(:run).with("SELECT * FROM orders").and_return(fake_statement2)
310
+
311
+ Super8.use_cassette(cassette_name) do
312
+ ODBC.connect("retalix") do |db|
313
+ stmt1 = db.run("SELECT * FROM users")
314
+ stmt1.fetch_all
315
+ stmt2 = db.run("SELECT * FROM orders")
316
+ stmt2.fetch_all
317
+ end
318
+ end
319
+ end
320
+
321
+ it "creates CSV files with connection and statement IDs" do
322
+ expect(File.exist?(File.join(cassette_path, "a_1_fetch_all.csv"))).to be true
323
+ expect(File.exist?(File.join(cassette_path, "a_2_fetch_all.csv"))).to be true
324
+ end
325
+
326
+ it "writes correct data to CSV files" do
327
+ expect(CSV.read(File.join(cassette_path, "a_1_fetch_all.csv"))).to eq(row_data1)
328
+ expect(CSV.read(File.join(cassette_path, "a_2_fetch_all.csv"))).to eq(row_data2)
329
+ end
330
+
331
+ it "records commands with correct file references" do
332
+ commands = YAML.load_file(File.join(cassette_path, "commands.yml"))
333
+ fetch_commands = commands.select { |cmd| cmd["method"] == "fetch_all" }
334
+ expect(fetch_commands[0]["rows_file"]).to eq("a_1_fetch_all.csv")
335
+ expect(fetch_commands[1]["rows_file"]).to eq("a_2_fetch_all.csv")
336
+ end
337
+ end
338
+ end
339
+
340
+ describe "Statement#drop" do
341
+ let(:fake_statement_result) { "un-deserializable test double" }
342
+
343
+ before do
344
+ allow(fake_db).to receive(:run).and_return(fake_statement)
345
+ allow(fake_statement).to receive(:drop).and_return(fake_statement_result)
346
+ end
347
+
348
+ it "records drop call and returns original result" do # rubocop:disable RSpec/ExampleLength
349
+ result = nil
350
+ Super8.use_cassette(cassette_name) do
351
+ ODBC.connect("retalix") do |db|
352
+ statement = db.run("SELECT * FROM users")
353
+ result = statement.drop
354
+ end
355
+ end
356
+
357
+ # Should return the original result
358
+ expect(result).to eq(fake_statement_result)
359
+
360
+ # Should record the drop command
361
+ commands_file = File.join(cassette_path, "commands.yml")
362
+ commands = YAML.load_file(commands_file)
363
+
364
+ drop_command = commands.find { |cmd| cmd["method"] == "drop" }
365
+ expect(drop_command).not_to be_nil
366
+
367
+ aggregate_failures do
368
+ expect(drop_command["method"]).to eq("drop")
369
+ expect(drop_command["connection_id"]).to eq("a")
370
+ expect(drop_command["statement_id"]).to eq(1)
371
+ expect(drop_command).not_to have_key("result")
372
+ end
373
+ end
374
+ end
375
+
376
+ describe "Statement#cancel" do
377
+ let(:fake_cancel_result) { "cancel_result" }
378
+
379
+ before do
380
+ allow(fake_db).to receive(:run).and_return(fake_statement)
381
+ allow(fake_statement).to receive(:cancel).and_return(fake_cancel_result)
382
+ end
383
+
384
+ it "records cancel call and returns original result" do # rubocop:disable RSpec/ExampleLength
385
+ result = nil
386
+ Super8.use_cassette(cassette_name) do
387
+ ODBC.connect("retalix") do |db|
388
+ statement = db.run("SELECT * FROM users")
389
+ result = statement.cancel
390
+ end
391
+ end
392
+
393
+ # Should return the original result
394
+ expect(result).to eq(fake_cancel_result)
395
+
396
+ # Should record the cancel command
397
+ commands_file = File.join(cassette_path, "commands.yml")
398
+ commands = YAML.load_file(commands_file)
399
+
400
+ cancel_command = commands.find { |cmd| cmd["method"] == "cancel" }
401
+ expect(cancel_command).not_to be_nil
402
+
403
+ aggregate_failures do
404
+ expect(cancel_command["method"]).to eq("cancel")
405
+ expect(cancel_command["connection_id"]).to eq("a")
406
+ expect(cancel_command["statement_id"]).to eq(1)
407
+ expect(cancel_command).not_to have_key("result")
408
+ end
409
+ end
410
+ end
411
+
412
+ describe "Statement#close" do
413
+ let(:fake_close_result) { "close_result" }
414
+
415
+ before do
416
+ allow(fake_db).to receive(:run).and_return(fake_statement)
417
+ allow(fake_statement).to receive(:close).and_return(fake_close_result)
418
+ end
419
+
420
+ it "records close call and returns original result" do # rubocop:disable RSpec/ExampleLength
421
+ result = nil
422
+ Super8.use_cassette(cassette_name) do
423
+ ODBC.connect("retalix") do |db|
424
+ statement = db.run("SELECT * FROM users")
425
+ result = statement.close
426
+ end
427
+ end
428
+
429
+ # Should return the original result
430
+ expect(result).to eq(fake_close_result)
431
+
432
+ # Should record the close command
433
+ commands_file = File.join(cassette_path, "commands.yml")
434
+ commands = YAML.load_file(commands_file)
435
+
436
+ close_command = commands.find { |cmd| cmd["method"] == "close" }
437
+ expect(close_command).not_to be_nil
438
+
439
+ aggregate_failures do
440
+ expect(close_command["method"]).to eq("close")
441
+ expect(close_command["connection_id"]).to eq("a")
442
+ expect(close_command["statement_id"]).to eq(1)
443
+ expect(close_command).not_to have_key("result")
444
+ end
445
+ end
446
+ end
447
+
448
+ describe "multiple connections" do
449
+ let(:fake_db1) { instance_double(ODBC::Database) }
450
+ let(:fake_db2) { instance_double(ODBC::Database) }
451
+ let(:fake_stmt1) { instance_double(ODBC::Statement) }
452
+ let(:fake_stmt2) { instance_double(ODBC::Statement) }
453
+
454
+ before do
455
+ allow(ODBC).to receive(:connect).with("dsn_one") do |&block|
456
+ block&.call(fake_db1)
457
+ end
458
+ allow(ODBC).to receive(:connect).with("dsn_two") do |&block|
459
+ block&.call(fake_db2)
460
+ end
461
+ allow(fake_db1).to receive(:run).and_return(fake_stmt1)
462
+ allow(fake_db2).to receive(:run).and_return(fake_stmt2)
463
+ allow(fake_stmt1).to receive(:fetch_all).and_return([["conn1_data"]])
464
+ allow(fake_stmt2).to receive(:fetch_all).and_return([["conn2_data"]])
465
+ end
466
+
467
+ it "assigns different connection IDs to each connection" do
468
+ Super8.use_cassette(cassette_name) do
469
+ ODBC.connect("dsn_one") do |db1|
470
+ db1.run("SELECT * FROM table1")
471
+ end
472
+ ODBC.connect("dsn_two") do |db2|
473
+ db2.run("SELECT * FROM table2")
474
+ end
475
+ end
476
+
477
+ commands = YAML.load_file(File.join(cassette_path, "commands.yml"))
478
+ connect_commands = commands.select { |cmd| cmd["method"] == "connect" }
479
+
480
+ expect(connect_commands[0]["connection_id"]).to eq("a")
481
+ expect(connect_commands[1]["connection_id"]).to eq("b")
482
+ end
483
+
484
+ it "uses per-connection statement IDs" do # rubocop:disable RSpec/ExampleLength
485
+ Super8.use_cassette(cassette_name) do
486
+ ODBC.connect("dsn_one") do |db1|
487
+ db1.run("SELECT * FROM table1")
488
+ db1.run("SELECT * FROM table2")
489
+ end
490
+ ODBC.connect("dsn_two") do |db2|
491
+ db2.run("SELECT * FROM table3")
492
+ end
493
+ end
494
+
495
+ commands = YAML.load_file(File.join(cassette_path, "commands.yml"))
496
+ run_commands = commands.select { |cmd| cmd["method"] == "run" }
497
+
498
+ # Connection a has statements 1 and 2
499
+ aggregate_failures do
500
+ expect(run_commands[0]["connection_id"]).to eq("a")
501
+ expect(run_commands[0]["statement_id"]).to eq(1)
502
+ expect(run_commands[1]["connection_id"]).to eq("a")
503
+ expect(run_commands[1]["statement_id"]).to eq(2)
504
+ end
505
+
506
+ # Connection b has statement 1 (independent namespace)
507
+ aggregate_failures do
508
+ expect(run_commands[2]["connection_id"]).to eq("b")
509
+ expect(run_commands[2]["statement_id"]).to eq(1)
510
+ end
511
+ end
512
+
513
+ it "uses connection-specific file names" do # rubocop:disable RSpec/ExampleLength
514
+ Super8.use_cassette(cassette_name) do
515
+ ODBC.connect("dsn_one") do |db1|
516
+ stmt = db1.run("SELECT * FROM table1")
517
+ stmt.fetch_all
518
+ end
519
+ ODBC.connect("dsn_two") do |db2|
520
+ stmt = db2.run("SELECT * FROM table2")
521
+ stmt.fetch_all
522
+ end
523
+ end
524
+
525
+ # Check files use correct connection prefixes
526
+ expect(File.exist?(File.join(cassette_path, "a_1_fetch_all.csv"))).to be true
527
+ expect(File.exist?(File.join(cassette_path, "b_1_fetch_all.csv"))).to be true
528
+
529
+ # Check file contents
530
+ expect(CSV.read(File.join(cassette_path, "a_1_fetch_all.csv"))).to eq([["conn1_data"]])
531
+ expect(CSV.read(File.join(cassette_path, "b_1_fetch_all.csv"))).to eq([["conn2_data"]])
532
+ end
533
+ end
534
+ end