sql_safety_net 1.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::Configuration do
4
+
5
+ before :each do
6
+ @config = SqlSafetyNet::Configuration.new
7
+ end
8
+
9
+ it "should be able to set debug" do
10
+ @config.debug?.should == false
11
+ @config.debug = true
12
+ @config.debug?.should == true
13
+ @config.debug = false
14
+ @config.debug?.should == false
15
+ end
16
+
17
+ it "should be able to set header" do
18
+ @config.header?.should == false
19
+ @config.header = true
20
+ @config.header?.should == true
21
+ @config.header = false
22
+ @config.header?.should == false
23
+ end
24
+
25
+ it "should be able to set table_scan_limit" do
26
+ @config.table_scan_limit = 500
27
+ @config.table_scan_limit.should == 500
28
+ end
29
+
30
+ it "should be able to set temporary_table_limit" do
31
+ @config.temporary_table_limit = 50
32
+ @config.temporary_table_limit.should == 50
33
+ end
34
+
35
+ it "should be able to set filesort_limit" do
36
+ @config.filesort_limit = 600
37
+ @config.filesort_limit.should == 600
38
+ end
39
+
40
+ it "should be able to set examine_rows_limit" do
41
+ @config.examine_rows_limit = 1000
42
+ @config.examine_rows_limit.should == 1000
43
+ end
44
+
45
+ it "should be able to set return_rows_limit" do
46
+ @config.return_rows_limit = 1000
47
+ @config.return_rows_limit.should == 1000
48
+ end
49
+
50
+ it "should be able to set query_limit" do
51
+ @config.query_limit = 20
52
+ @config.query_limit.should == 20
53
+ end
54
+
55
+ it "should be able to set time_limit" do
56
+ @config.time_limit = 200
57
+ @config.time_limit.should == 200
58
+ end
59
+
60
+ it "should be able to set always_show" do
61
+ @config.always_show = true
62
+ @config.always_show?.should == true
63
+ @config.always_show = nil
64
+ @config.always_show?.should == false
65
+ end
66
+
67
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::ConnectionAdapter do
4
+
5
+ class SqlSafetyNet::TestConnectionAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
6
+ class Model
7
+ attr_accessor :id, :name
8
+ def initialize (attrs)
9
+ @id = attrs["id"]
10
+ @name = attrs["name"]
11
+ end
12
+ end
13
+
14
+ def columns (table_name, name = nil)
15
+ select_rows("GET columns")
16
+ ["id", "name"]
17
+ end
18
+
19
+ def select_rows (sql, name = nil, binds = [])
20
+ return [{"id" => 1, "name" => "foo"}, {"id" => 2, "name" => "bar"}]
21
+ end
22
+
23
+ protected
24
+
25
+ def analyze_query (sql, name, *args)
26
+ if sql.match(/table scan/i)
27
+ {:flags => ["table scan"]}
28
+ end
29
+ end
30
+
31
+ def select (sql, name = nil, binds = [])
32
+ select_rows(sql, name).collect do |row|
33
+ Model.new(row)
34
+ end
35
+ end
36
+
37
+ SqlSafetyNet.config.enable_on(self)
38
+ end
39
+
40
+ before(:each) do
41
+ SqlSafetyNet.config.debug = true
42
+ SqlSafetyNet::QueryAnalysis.clear
43
+ end
44
+
45
+ after(:each) do
46
+ SqlSafetyNet::QueryAnalysis.clear
47
+ end
48
+
49
+ let(:connection){ SqlSafetyNet::TestConnectionAdapter.new(:connection) }
50
+
51
+ it "should not analyze the SQL select in the columns method" do
52
+ connection.should_receive(:columns_without_sql_safety_net).with("table", "columns").and_return(["col1", "col2"])
53
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
54
+ connection.columns("table", "columns").should == ["col1", "col2"]
55
+ end
56
+ analysis.selects.should == 0
57
+ end
58
+
59
+ it "should not analyze the SQL select in the active? method" do
60
+ connection.should_receive(:active_without_sql_safety_net?).and_return(true)
61
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
62
+ connection.active?.should == true
63
+ end
64
+ analysis.selects.should == 0
65
+ end
66
+
67
+ it "should determine if a SQL statement is a select statement" do
68
+ connection.select_statement?("SELECT * FROM TABLE").should == true
69
+ connection.select_statement?(" \n SELECT * FROM TABLE").should == true
70
+ connection.select_statement?("Select * From Table").should == true
71
+ connection.select_statement?("select * from table").should == true
72
+ connection.select_statement?("EXECUTE SELECT * FROM TABLE").should == false
73
+ end
74
+
75
+ [:select, :select_rows].each do |select_method|
76
+ context select_method do
77
+ it "should proxy the select method to the underlying adapter" do
78
+ connection.should_receive("#{select_method}_without_sql_safety_net").with('Select sql', 'name').and_return([:row1, :row2])
79
+ connection.send(select_method, 'Select sql', 'name').should == [:row1, :row2]
80
+ end
81
+
82
+ it "should count selects" do
83
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
84
+ connection.send(select_method, 'Select * from table')
85
+ connection.send(select_method, 'Select * from table where whatever')
86
+ end
87
+ analysis.selects.should == 2
88
+ end
89
+
90
+ it "should count rows returned" do
91
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
92
+ connection.send(select_method, 'Select * from table')
93
+ connection.send(select_method, 'Select * from table where whatever')
94
+ end
95
+ analysis.rows.should == 4
96
+ end
97
+
98
+ it "should analyze select statements and keep track of bad queries" do
99
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
100
+ connection.send(select_method, 'Select * from table doing table scan')
101
+ end
102
+ analysis.non_flagged_queries.size.should == 0
103
+ analysis.flagged_queries.size.should == 1
104
+ analysis.flagged_queries.first[:sql].should == 'Select * from table doing table scan'
105
+ analysis.flagged_queries.first[:rows].should == 2
106
+ analysis.flagged_queries.first[:flags].should == ['table scan']
107
+ end
108
+
109
+ it "should analyze select statements and keep track of good queries" do
110
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
111
+ connection.send(select_method, 'Select * from table')
112
+ end
113
+ analysis.flagged_queries.size.should == 0
114
+ analysis.non_flagged_queries.size.should == 1
115
+ analysis.non_flagged_queries.first[:sql].should == 'Select * from table'
116
+ analysis.non_flagged_queries.first[:rows].should == 2
117
+ end
118
+
119
+ it "should flag queries that exceed the configured time limit" do
120
+ now = Time.now
121
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
122
+ Time.stub(:now).and_return(now, now + 100)
123
+ connection.send(select_method, 'Select * from table')
124
+ end
125
+ analysis.flagged_queries.size.should == 1
126
+ analysis.non_flagged_queries.size.should == 0
127
+ analysis.flagged_queries.first[:flags].should == ["query time exceeded #{SqlSafetyNet.config.time_limit} ms"]
128
+ end
129
+
130
+ it "should not analyze queries if debug mode disabled" do
131
+ SqlSafetyNet.config.debug = false
132
+ analysis = SqlSafetyNet::QueryAnalysis.analyze do
133
+ connection.send(select_method, 'SELECT * from table with table scan')
134
+ end
135
+ analysis.selects.should == 1
136
+ analysis.rows.should == 2
137
+ analysis.flagged_queries.size.should == 0
138
+ analysis.non_flagged_queries.size.should == 0
139
+ end
140
+ end
141
+ end
142
+
143
+ end
@@ -0,0 +1,14 @@
1
+ # Copy this file to database.yml to run the test with connection settings for you environment.
2
+ # You must create the database in both mysql and postgres.
3
+
4
+ mysql:
5
+ adapter: mysql
6
+ username: root
7
+ password: ""
8
+ database: sql_safety_net_test
9
+
10
+ postgresql:
11
+ adapter: postgresql
12
+ username: postgres
13
+ password: postgres
14
+ database: sql_safety_net_test
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::ConnectionAdapter::MysqlAdapter do
4
+
5
+ before(:each) do
6
+ @connection = SqlSafetyNet::MysqlTestModel.connection
7
+ end
8
+
9
+ it "should use query plan analysis to flag queries" do
10
+ query_plan = [{'type' => 'ALL', 'rows' => 200}]
11
+ @connection.should_receive(:select_without_sql_safety_net).with('EXPLAIN Select sql', 'EXPLAIN', []).and_return(query_plan)
12
+ @connection.should_receive(:analyze_query_plan).with(query_plan).and_return(["bad query"])
13
+ @connection.analyze_query('Select sql', 'name', []).should == {:query_plan => query_plan, :flags => ["bad query"]}
14
+ end
15
+
16
+ it "should only analyze query plans for select statements" do
17
+ @connection.should_not_receive(:select_without_sql_safety_net)
18
+ @connection.should_not_receive(:analyze_query_plan)
19
+ @connection.analyze_query('Execute sql', 'name', []).should == nil
20
+ end
21
+
22
+ it "should translate query plans into flags" do
23
+ @connection.send(:analyze_query_plan, [{'type' => 'ALL', 'rows' => 500}]).should == ["table scan", "no index used", "no indexes possible"]
24
+ @connection.send(:analyze_query_plan, [{'Extra' => 'using temporary table; using filesort', 'rows' => 200, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["uses temporary table for 200 rows", "uses filesort for 200 rows"]
25
+ @connection.send(:analyze_query_plan, [{'select_type' => 'dependent subquery', 'rows' => 20, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["dependent subquery"]
26
+ @connection.send(:analyze_query_plan, [{'select_type' => 'uncacheable subquery', 'rows' => 20, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["uncacheable subquery"]
27
+ @connection.send(:analyze_query_plan, [{'rows' => 20000, 'key' => 'index', 'possible_keys' => 'index'}]).should == ["examines 20000 rows"]
28
+ @connection.send(:analyze_query_plan, [{'select_type' => 'SIMPLE', 'rows' => 1, 'key' => 'index', 'possible_keys' => 'index'}]).should == []
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::ConnectionAdapter::PostgreSQLAdapter do
4
+
5
+ before(:each) do
6
+ @connection = SqlSafetyNet::PostgresqlTestModel.connection
7
+ end
8
+
9
+ it "should use query plan analysis to flag queries" do
10
+ query_plan = [{"QUERY PLAN"=>"Limit (cost=0.00..0.06 rows=1 width=335)"}, {"QUERY PLAN"=>" -> Seq Scan on records (cost=0.00..12.20 rows=220 width=335)"}]
11
+ @connection.should_receive(:select_without_sql_safety_net).with('EXPLAIN Select sql', 'EXPLAIN', []).and_return(query_plan)
12
+ @connection.should_receive(:analyze_query_plan).with(query_plan).and_return(["bad query"])
13
+ @connection.analyze_query('Select sql', 'name', []).should == {:query_plan => query_plan, :flags => ["bad query"]}
14
+ end
15
+
16
+ it "should only analyze query plans for select statements" do
17
+ @connection.should_not_receive(:select_without_sql_safety_net)
18
+ @connection.should_not_receive(:analyze_query_plan)
19
+ @connection.analyze_query('Execute sql', 'name', []).should == nil
20
+ end
21
+
22
+ it "should not flag a small table scan" do
23
+ query_plan = [{"QUERY PLAN"=>"Seq Scan on records (cost=0.00..12.20 rows=10 width=335)"}]
24
+ @connection.send(:analyze_query_plan, query_plan).should == []
25
+ end
26
+
27
+ it "should flag a table scan" do
28
+ query_plan = [{"QUERY PLAN"=>"Seq Scan on records (cost=0.00..12.20 rows=1000000 width=335)"}]
29
+ @connection.send(:analyze_query_plan, query_plan).should == ["table scan"]
30
+ end
31
+
32
+ it "should flag too many rows returned" do
33
+ query_plan = [{"QUERY PLAN"=>"Index Scan using records_pkey on records (cost=0.00..8.27 rows=1000000 width=336)"}]
34
+ @connection.send(:analyze_query_plan, query_plan).should == ["examines 1000000 rows"]
35
+ end
36
+
37
+ it "should apply a limit to the rows returned" do
38
+ query_plan = [{"QUERY PLAN"=>"Limit (cost=0.00..0.06 rows=1 width=335)"}, {"QUERY PLAN"=>" -> Seq Scan on records (cost=0.00..12.20 rows=1000000 width=335)"}]
39
+ @connection.send(:analyze_query_plan, query_plan).should == []
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::QueryAnalysis do
4
+
5
+ let(:analysis){ SqlSafetyNet::QueryAnalysis.new }
6
+
7
+ it "should add a flagged query" do
8
+ analysis.add_query("sql", "test", 10, 0.01, :query_plan => "this sucks", :flags => ["write better sql"])
9
+ analysis.non_flagged_queries.should be_empty
10
+ query = analysis.flagged_queries.first
11
+ query[:sql].should == "sql"
12
+ query[:name].should == "test"
13
+ query[:rows].should == 10
14
+ query[:elapsed_time].should == 0.01
15
+ query[:query_plan].should == "this sucks"
16
+ query[:flags].should == ["write better sql"]
17
+ query[:cached].should == false
18
+ end
19
+
20
+ it "should add a non-flagged query" do
21
+ analysis.add_query("sql", "test", 10, 0.01, nil)
22
+ analysis.flagged_queries.should be_empty
23
+ query = analysis.non_flagged_queries.first
24
+ query[:sql].should == "sql"
25
+ query[:name].should == "test"
26
+ query[:rows].should == 10
27
+ query[:elapsed_time].should == 0.01
28
+ query[:cached].should == false
29
+ end
30
+
31
+ it "should determine if any query is flagged" do
32
+ analysis.add_query("sql", "test", 10, 0.01, :query_plan => "this sucks", :flags => ["write better sql"])
33
+ analysis.add_query("sql", "test", 10, 0.01, nil)
34
+ analysis.should be_flagged
35
+ analysis.flagged_queries?.should == true
36
+ end
37
+
38
+ it "should determine if the rows selected is flagged" do
39
+ analysis.rows = 1000000
40
+ analysis.should be_flagged
41
+ analysis.too_many_rows?.should == true
42
+ end
43
+
44
+ it "should determine if the number of selects is flagged" do
45
+ analysis.selects = 100000
46
+ analysis.should be_flagged
47
+ analysis.too_many_selects?.should == true
48
+ end
49
+
50
+ it "should set the analysis object within a block" do
51
+ SqlSafetyNet::QueryAnalysis.analyze do
52
+ SqlSafetyNet::QueryAnalysis.current.should be_a(SqlSafetyNet::QueryAnalysis)
53
+ end
54
+ SqlSafetyNet::QueryAnalysis.current.should == nil
55
+ end
56
+
57
+ it "should determine if a query is happening in a cache block" do
58
+ cache = ActiveSupport::Cache::MemoryStore.new
59
+ analysis = nil
60
+ SqlSafetyNet::QueryAnalysis.analyze do
61
+ val = cache.fetch("key") do
62
+ analysis = SqlSafetyNet::QueryAnalysis.current
63
+ analysis.add_query("sql", "test", 10, 0.01, nil)
64
+ "woot"
65
+ end
66
+ val.should == "woot"
67
+ end
68
+ query = analysis.non_flagged_queries.first
69
+ query[:cached].should == true
70
+ end
71
+ end
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::RackHandler do
4
+
5
+ class SqlSafetyNet::TestApp
6
+ attr_accessor :response, :block
7
+
8
+ def initialize
9
+ @response = Rack::Response.new
10
+ end
11
+
12
+ def call (env)
13
+ @block.call(env) if @block
14
+ @response["Content-Type"] = env["response_content_type"] if env["response_content_type"]
15
+ @response.finish
16
+ end
17
+ end
18
+
19
+ before(:each) do
20
+ SqlSafetyNet.config.debug = true
21
+ SqlSafetyNet::QueryAnalysis.clear
22
+ end
23
+
24
+ let(:logger) do
25
+ logger = Object.new
26
+ def logger.warn (message)
27
+ # noop
28
+ end
29
+ logger
30
+ end
31
+
32
+ let(:app){ SqlSafetyNet::TestApp.new }
33
+
34
+ let(:handler){ SqlSafetyNet::RackHandler.new(app, logger) }
35
+
36
+ let(:env) do
37
+ {
38
+ "rack.url_scheme" => "http",
39
+ "PATH_INFO" => "/test",
40
+ "SERVER_PORT" => 80,
41
+ "HTTP_HOST" => "example.com",
42
+ "REQUEST_METHOD" => "GET"
43
+ }
44
+ end
45
+
46
+ it "should append the bad queries to the HTML when debug is enabled and always_show is false" do
47
+ SqlSafetyNet.config.always_show = false
48
+
49
+ app.block = lambda do |env|
50
+ SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
51
+ end
52
+ app.response.write("Monkeys are neat.")
53
+
54
+ r = handler.call(env)
55
+ response = Rack::Response.new(r[2], r[0], r[1])
56
+
57
+ response.body.join("").should include('<div id="sql_safety_net_warning"')
58
+ response.body.join("").should include("Monkeys are neat.")
59
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
60
+ end
61
+
62
+ it "should not append the bad queries to the HTML when there are none and always_show is false" do
63
+ SqlSafetyNet.config.always_show = false
64
+
65
+ app.response.write("Monkeys are neat.")
66
+
67
+ r = handler.call(env)
68
+ response = Rack::Response.new(r[2], r[0], r[1])
69
+
70
+ response.body.join("").should == "Monkeys are neat."
71
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=0"
72
+ end
73
+
74
+ it "should append the queries to the HTML when there are none and always_show is true" do
75
+ SqlSafetyNet.config.always_show = true
76
+
77
+ app.response.write("Monkeys are neat.")
78
+
79
+ r = handler.call(env)
80
+ response = Rack::Response.new(r[2], r[0], r[1])
81
+
82
+ response.body.join("").should include('<div id="sql_safety_net_warning"')
83
+ response.body.join("").should include("Monkeys are neat.")
84
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=0"
85
+ end
86
+
87
+ it "should append the bad queries to XML when debug is enabled" do
88
+ SqlSafetyNet.config.always_show = false
89
+
90
+ app.block = lambda do |env|
91
+ SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
92
+ end
93
+ app.response.write("<woot>Monkeys are neat.</woot>")
94
+
95
+ r = handler.call(env.merge("response_content_type" => "application/xml"))
96
+ response = Rack::Response.new(r[2], r[0], r[1])
97
+
98
+ response.body.join("").should_not include('<div id="sql_safety_net_warning"')
99
+ response.body.join("").should include("<!-- SqlSafetyNet")
100
+ response.body.join("").should include("<woot>Monkeys are neat.</woot>")
101
+ Hash.from_xml(response.body.join('')).should == {"woot" => "Monkeys are neat."}
102
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
103
+ end
104
+
105
+ it "should not append the bad queries to the HTML or add a response header if debug is not enabled" do
106
+ SqlSafetyNet.config.debug = false
107
+
108
+ app.block = lambda do |env|
109
+ SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
110
+ end
111
+ app.response.write("Monkeys are neat.")
112
+
113
+ r = handler.call(env)
114
+ response = Rack::Response.new(r[2], r[0], r[1])
115
+
116
+ response.body.join("").should == "Monkeys are neat."
117
+ response["X-SqlSafetyNet"].should == nil
118
+ end
119
+
120
+ it "should not append the bad queries to the HTML if not text/html" do
121
+ app.block = lambda do |env|
122
+ SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
123
+ end
124
+ app.response.write("Monkeys are neat.")
125
+ app.response["Content-Type"] = "text/plain"
126
+
127
+ r = handler.call(env)
128
+ response = Rack::Response.new(r[2], r[0], r[1])
129
+
130
+ response.body.join("").should == "Monkeys are neat."
131
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
132
+ end
133
+
134
+ it "should not append the bad queries to the HTML if Ajax request" do
135
+ app.block = lambda do |env|
136
+ SqlSafetyNet::QueryAnalysis.current.flagged_queries << {:sql => 'sql', :query_plan => 'bad plan', :flags => ['bad query'], :rows => 4, :elapsed_time => 0.1}
137
+ end
138
+ app.response.write("Monkeys are neat.")
139
+ app.response["Content-Type"] = "text/plain"
140
+
141
+ r = handler.call(env.merge("HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"))
142
+ response = Rack::Response.new(r[2], r[0], r[1])
143
+
144
+ response.body.join("").should == "Monkeys are neat."
145
+ response["X-SqlSafetyNet"].should == "selects=0; rows=0; elapsed_time=0; flagged_queries=1"
146
+ end
147
+
148
+ it "should log too many selects even if debug is not enabled" do
149
+ SqlSafetyNet.config.debug = false
150
+ app.block = lambda do |env|
151
+ SqlSafetyNet::QueryAnalysis.current.selects = 1000
152
+ end
153
+ logger.should_receive(:warn).with("Excess database usage: request generated 1000 queries and returned 0 rows [GET http://example.com/test]")
154
+ r = handler.call(env)
155
+ end
156
+
157
+ it "should log too many rows even if debug is not enabled" do
158
+ SqlSafetyNet.config.debug = false
159
+ SqlSafetyNet.config.header = false
160
+ app.block = lambda do |env|
161
+ SqlSafetyNet::QueryAnalysis.current.rows = 10000
162
+ end
163
+ app.response.write("Monkeys are neat.")
164
+
165
+ logger.should_receive(:warn).with("Excess database usage: request generated 0 queries and returned 10000 rows [GET http://example.com/test]")
166
+ r = handler.call(env)
167
+ response = Rack::Response.new(r[2], r[0], r[1])
168
+
169
+ response.body.join("").should == "Monkeys are neat."
170
+ response["X-SqlSafetyNet"].should == nil
171
+ end
172
+
173
+ it "should log too many rows even if debug is not enabled" do
174
+ SqlSafetyNet.config.debug = false
175
+ SqlSafetyNet.config.header = true
176
+ app.block = lambda do |env|
177
+ SqlSafetyNet::QueryAnalysis.current.rows = 10
178
+ end
179
+ app.response.write("Monkeys are neat.")
180
+ r = handler.call(env)
181
+ response = Rack::Response.new(r[2], r[0], r[1])
182
+
183
+ response.body.join("").should == "Monkeys are neat."
184
+ response["X-SqlSafetyNet"].should == "selects=0; rows=10; elapsed_time=0; flagged_queries=0"
185
+ end
186
+
187
+ it "should not log warnings if there are none" do
188
+ SqlSafetyNet.config.debug = false
189
+ logger.should_not_receive(:warn)
190
+ r = handler.call(env)
191
+ end
192
+
193
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+
3
+ active_record_version = ENV['ACTIVE_RECORD_VERSION'] || ">=2.2.2"
4
+ gem 'rails', active_record_version
5
+ gem 'activerecord', active_record_version
6
+ gem 'activesupport', active_record_version
7
+ require 'active_support/all'
8
+ require 'active_record'
9
+ puts "Testing against #{ActiveRecord::VERSION::STRING}"
10
+
11
+ require 'mysql'
12
+ require 'pg'
13
+
14
+ begin
15
+ require 'simplecov'
16
+ SimpleCov.start do
17
+ add_filter "/spec/"
18
+ end
19
+ rescue LoadError
20
+ # simplecov not installed
21
+ end
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'sql_safety_net'))
24
+
25
+ module SqlSafetyNet
26
+ class TestModel < ActiveRecord::Base
27
+ def self.create_tables
28
+ connection.create_table(table_name) do |t|
29
+ t.string :name
30
+ end unless table_exists?
31
+ end
32
+
33
+ def self.drop_tables
34
+ connection.drop_table(table_name)
35
+ end
36
+
37
+ def self.database_config
38
+ database_yml = File.expand_path(File.join(File.dirname(__FILE__), 'database.yml'))
39
+ raise "You must create a database.yml file in the spec directory (see example_database.yml)" unless File.exist?(database_yml)
40
+ YAML.load_file(database_yml)
41
+ end
42
+ end
43
+
44
+ class MysqlTestModel < TestModel
45
+ establish_connection(database_config['mysql'])
46
+ end
47
+
48
+ class PostgresqlTestModel < TestModel
49
+ establish_connection(database_config['postgresql'])
50
+ end
51
+
52
+ SqlSafetyNet.config.enable_on(SqlSafetyNet::MysqlTestModel.connection.class)
53
+ SqlSafetyNet.config.enable_on(SqlSafetyNet::PostgresqlTestModel.connection.class)
54
+ end
55
+
56
+ ActiveSupport::Cache::Store.send(:include, SqlSafetyNet::CacheStore)