sql_safety_net 1.1.11

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,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)