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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +96 -0
- data/lib/super8/cassette.rb +119 -0
- data/lib/super8/config.rb +12 -0
- data/lib/super8/diff_helper.rb +39 -0
- data/lib/super8/errors.rb +10 -0
- data/lib/super8/playback_database_wrapper.rb +57 -0
- data/lib/super8/playback_statement_wrapper.rb +80 -0
- data/lib/super8/recording_database_wrapper.rb +37 -0
- data/lib/super8/recording_statement_wrapper.rb +64 -0
- data/lib/super8/version.rb +5 -0
- data/lib/super8.rb +100 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/super8/cassette_spec.rb +76 -0
- data/spec/super8/config_spec.rb +27 -0
- data/spec/super8/odbc_interception_spec.rb +534 -0
- data/spec/super8/playback_spec.rb +127 -0
- data/spec/super8_spec.rb +35 -0
- metadata +146 -0
|
@@ -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
|