sql_safety_net 1.1.11 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,142 +2,137 @@ require 'spec_helper'
2
2
 
3
3
  describe SqlSafetyNet::ConnectionAdapter do
4
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
5
+ let(:connection){ SqlSafetyNet::TestModel.connection }
44
6
 
45
- after(:each) do
46
- SqlSafetyNet::QueryAnalysis.clear
7
+ before :each do
8
+ SqlSafetyNet::TestModel.delete_all
9
+ SqlSafetyNet::TestModel.create!(:name => "test", :value => 10)
47
10
  end
48
11
 
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"]
12
+ describe "injection" do
13
+ it "should analyze queries in the select_rows method" do
14
+ connection.should_receive(:analyze_query).with("select name, value from test_models", "SQL", []).and_yield
15
+ connection.select_rows("select name, value from test_models", "SQL").should == [["test", 10]]
55
16
  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
17
+
18
+ it "should analyze queries in the select method" do
19
+ connection.should_receive(:analyze_query).with("select name, value from test_models", "SQL", []).and_yield
20
+ connection.send(:select, "select name, value from test_models", "SQL").should == [{"name"=>"test", "value"=>10}]
63
21
  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
22
  end
74
23
 
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
24
+ describe "analysis" do
25
+ it "should not blow up if there is no current QueryAnalysis" do
26
+ connection.select_rows("select name, value from test_models", "SQL").should == [["test", 10]]
27
+ end
81
28
 
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')
29
+ context "select statements" do
30
+ before :each do
31
+ SqlSafetyNet::TestModel.create!(:name => "foo", :value => 100)
32
+ end
33
+
34
+ it "should analyze select statements" do
35
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
36
+ results = connection.send(:select, "select name, value from test_models order by name")
37
+ results.should == [{"name" => "foo", "value" => 100}, {"name" => "test", "value" => 10}]
38
+ analysis.queries.size.should == 1
39
+ query_info = analysis.queries.first
40
+ query_info.sql.should == "select name, value from test_models order by name"
41
+ query_info.rows.should == 2
42
+ query_info.result_size.should == 12
43
+ query_info.elapsed_time.should > 0
44
+ end
45
+ end
46
+
47
+ # ActiveRecord < 3.1 doesn't have the binds parameter
48
+ if ActiveRecord::VERSION::MAJOR > 3 || (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR >= 1)
49
+ it "should analyze select statements using bind variables" do
50
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
51
+ name_column = SqlSafetyNet::TestModel.columns_hash["name"]
52
+ results = connection.send(:select, "select name, value from test_models where name = ? order by name", "SQL", [[name_column, "foo"]])
53
+ results.should == [{"name" => "foo", "value" => 100}]
54
+ analysis.queries.size.should == 1
55
+ query_info = analysis.queries.first
56
+ query_info.sql.should == 'select name, value from test_models where name = ? order by name [["name", "foo"]]'
57
+ query_info.rows.should == 1
58
+ query_info.result_size.should == 6
59
+ query_info.elapsed_time.should > 0
60
+ end
61
+ end
62
+ end
63
+
64
+ it "should analyze select_rows statements" do
65
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
66
+ results = connection.select_rows("select name, value from test_models order by name")
67
+ results.should == [["foo", 100], ["test", 10]]
68
+ analysis.queries.size.should == 1
69
+ query_info = analysis.queries.first
70
+ query_info.sql.should == "select name, value from test_models order by name"
71
+ query_info.rows.should == 2
72
+ query_info.result_size.should == 12
73
+ query_info.elapsed_time.should > 0
86
74
  end
87
- analysis.selects.should == 2
88
75
  end
76
+ end
89
77
 
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')
78
+ context "non-select statements" do
79
+ it "should not analyze schema statements" do
80
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
81
+ results = connection.select_rows("INSERT INTO test_models (name, value) VALUES ('moo', 1)")
82
+ analysis.queries.should be_empty
94
83
  end
95
- analysis.rows.should == 4
96
84
  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')
85
+
86
+ it "should not analyze explain statements" do
87
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
88
+ results = connection.select_rows("EXPLAIN select * from test_models")
89
+ analysis.queries.should be_empty
101
90
  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
91
  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')
92
+
93
+ it "should not analyze insert statements" do
94
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
95
+ results = connection.select_rows("INSERT INTO test_models (name, value) VALUES ('moo', 1)")
96
+ analysis.queries.should be_empty
112
97
  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
98
  end
118
99
 
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')
100
+ it "should not analyze update statements" do
101
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
102
+ results = connection.select_rows("UPDATE test_models SET value = 1 WHERE value = 10")
103
+ analysis.queries.should be_empty
124
104
  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
105
  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')
106
+
107
+ it "should not analyze delete statements" do
108
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
109
+ results = connection.select_rows("DELETE FROM test_models WHERE id = 1")
110
+ analysis.queries.should be_empty
134
111
  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
112
  end
140
113
  end
141
114
  end
142
-
115
+
116
+ describe "ActiveRecord finders" do
117
+ it "should analyze the queries" do
118
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
119
+ model = SqlSafetyNet::TestModel.all.first
120
+ SqlSafetyNet::TestModel.find(model.id)
121
+ analysis.total_queries.should == 2
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "explain plan analysis" do
127
+ it "should do further analysis on queries when the adapter support query plan analysis" do
128
+ connection.should_receive(:respond_to?).with(:sql_safety_net_analyze_query_plan).and_return(true)
129
+ connection.should_receive(:sql_safety_net_analyze_query_plan).with("SELECT * from test_models", []).and_return(["table scan"])
130
+
131
+ SqlSafetyNet::QueryAnalysis.capture do |analysis|
132
+ model = connection.select_all("SELECT * from test_models")
133
+ analysis.queries.first.alerts.should == ["table scan"]
134
+ end
135
+ end
136
+ end
137
+
143
138
  end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::ExplainPlan::Mysql do
4
+
5
+ class MockMysqlConnection; end
6
+ SqlSafetyNet::ExplainPlan.enable_on_connection_adapter!(MockMysqlConnection, :mysql)
7
+
8
+ let(:connection){ MockMysqlConnection.new }
9
+ let(:sql){ "SELECT * FROM *" }
10
+
11
+ it "should detect table scans" do
12
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"type" => "ALL", "rows" => 100}])
13
+
14
+ SqlSafetyNet.override_config do |config|
15
+ config.table_scan_limit = 99
16
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
17
+ alerts.should include("table scan on 100 rows")
18
+
19
+ config.table_scan_limit = 100
20
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
21
+ alerts.should be_empty
22
+ end
23
+ end
24
+
25
+ it "should detect no indexes being used" do
26
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"key" => nil, "rows" => 100}])
27
+
28
+ SqlSafetyNet.override_config do |config|
29
+ config.table_scan_limit = 99
30
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
31
+ alerts.should include("no index used")
32
+
33
+ config.table_scan_limit = 100
34
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
35
+ alerts.should_not include("no index used")
36
+ end
37
+ end
38
+
39
+ it "should detect no indexes possible" do
40
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"possible_keys" => nil, "rows" => 100}])
41
+
42
+ SqlSafetyNet.override_config do |config|
43
+ config.table_scan_limit = 99
44
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
45
+ alerts.should include("no index possible")
46
+
47
+ config.table_scan_limit = 100
48
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
49
+ alerts.should_not include("no index possible")
50
+ end
51
+ end
52
+
53
+ it "should detect dependent subqueries" do
54
+ connection.should_receive(:select).with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"select_type" => "dependent", "rows" => 100}])
55
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
56
+ alerts.should include("dependent subquery")
57
+ end
58
+
59
+ it "should detect uncacheable subqueries" do
60
+ connection.should_receive(:select).with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"select_type" => "uncacheable", "rows" => 100}])
61
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
62
+ alerts.should include("uncacheable subquery")
63
+ end
64
+
65
+ it "should detect full scan on null key" do
66
+ connection.should_receive(:select).with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"Extra" => "full scan on null key", "rows" => 100}])
67
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
68
+ alerts.should include("full scan on null key")
69
+ end
70
+
71
+ it "should detect excess temporary table usage" do
72
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"Extra" => "using temporary", "rows" => 100}])
73
+
74
+ SqlSafetyNet.override_config do |config|
75
+ config.temporary_table_limit = 99
76
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
77
+ alerts.should include("uses temporary table for 100 rows")
78
+
79
+ config.temporary_table_limit = 100
80
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
81
+ alerts.should_not include("uses temporary table for 100 rows")
82
+ end
83
+ end
84
+
85
+ it "should detect excess filesort usage" do
86
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"Extra" => "filesort", "rows" => 100}])
87
+
88
+ SqlSafetyNet.override_config do |config|
89
+ config.filesort_limit = 99
90
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
91
+ alerts.should include("uses filesort for 100 rows")
92
+
93
+ config.filesort_limit = 100
94
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
95
+ alerts.should_not include("uses filesort for 100 rows")
96
+ end
97
+ end
98
+
99
+ it "should detect examining too many rows" do
100
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", "EXPLAIN", []).and_return([{"rows" => 100}])
101
+
102
+ SqlSafetyNet.override_config do |config|
103
+ config.examined_rows_limit = 99
104
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
105
+ alerts.should include("examined 100 rows")
106
+
107
+ config.examined_rows_limit = 100
108
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
109
+ alerts.should be_empty
110
+ end
111
+ end
112
+
113
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::ExplainPlan::Postgresql do
4
+ class PostgresqlConnection; end
5
+ SqlSafetyNet::ExplainPlan.enable_on_connection_adapter!(PostgresqlConnection, :postgresql)
6
+
7
+ let(:connection){ PostgresqlConnection.new }
8
+ let(:sql){ "SELECT * FROM *" }
9
+
10
+ it "should flag excessive table scans" do
11
+ query_plan = [{"QUERY PLAN"=>"Seq Scan on records (cost=0.00..12.20 rows=100 width=335)"}]
12
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", 'EXPLAIN', []).and_return(query_plan)
13
+ SqlSafetyNet.override_config do |config|
14
+ config.table_scan_limit = 99
15
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
16
+ alerts.should include("table scan on ~100 rows")
17
+
18
+ config.table_scan_limit = 100
19
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
20
+ alerts.should be_empty
21
+ end
22
+ end
23
+
24
+ it "should flag too many rows examined" do
25
+ query_plan = [{"QUERY PLAN"=>"Index Scan using records_pkey on records (cost=0.00..8.27 rows=100 width=336)"}]
26
+ connection.should_receive(:select).twice.with("EXPLAIN #{sql}", 'EXPLAIN', []).and_return(query_plan)
27
+ SqlSafetyNet.override_config do |config|
28
+ config.examined_rows_limit = 99
29
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
30
+ alerts.should include("examined ~100 rows")
31
+
32
+ config.examined_rows_limit = 100
33
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
34
+ alerts.should be_empty
35
+ end
36
+ end
37
+
38
+ it "should apply a limit to the rows returned" do
39
+ 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)"}]
40
+ connection.should_receive(:select).with("EXPLAIN #{sql}", 'EXPLAIN', []).and_return(query_plan)
41
+ alerts = connection.sql_safety_net_analyze_query_plan(sql, [])
42
+ alerts.should be_empty
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::Formatter do
4
+
5
+ let(:query_info){ SqlSafetyNet::QueryInfo.new("SELECT * FROM *", :elapsed_time => 0.1, :rows => 1, :result_size => 500) }
6
+ let(:analysis){ SqlSafetyNet::QueryAnalysis.new }
7
+
8
+ it "should convert an analysis to valid XHTML" do
9
+ analysis << query_info
10
+ formatter = SqlSafetyNet::Formatter.new(analysis)
11
+ html = formatter.to_html
12
+ html.should match(/<div [^>]*id="_sql_safety_net_"/)
13
+ parsed = ActiveSupport::XmlMini.parse(html)
14
+ parsed["div"]["id"].should == "_sql_safety_net_"
15
+ end
16
+
17
+ it "should convert an analysis to a string" do
18
+ analysis << query_info
19
+ formatter = SqlSafetyNet::Formatter.new(analysis)
20
+ text = formatter.to_s
21
+ text.should include("1 query, 1 row")
22
+ end
23
+
24
+ it "should give a summary of an analysis" do
25
+ analysis << query_info
26
+ formatter = SqlSafetyNet::Formatter.new(analysis)
27
+ formatter.summary.should == "1 query, 1 row, 0.5K, 100ms"
28
+ end
29
+
30
+ it "should pluralize words in the summary" do
31
+ analysis << query_info
32
+ analysis << query_info
33
+ formatter = SqlSafetyNet::Formatter.new(analysis)
34
+ formatter.summary.should == "2 queries, 2 rows, 1.0K, 200ms"
35
+ end
36
+
37
+ it "should creat a CSS style for the HTML div" do
38
+ formatter = SqlSafetyNet::Formatter.new(analysis)
39
+ style = formatter.div_style("width" => "200px", "left" => "10px", "text-decoration" => "underline", "font-weight" => nil)
40
+ style.should include("left:10px;")
41
+ style.should include("top:5px;")
42
+ style.should include("width:200px")
43
+ style.should include("text-decoration:underline")
44
+ style.should_not include("font-weight")
45
+ end
46
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe SqlSafetyNet::Middleware do
4
+
5
+ before :all do
6
+ SqlSafetyNet::TestModel.delete_all
7
+ SqlSafetyNet::TestModel.create(:name => "test")
8
+ end
9
+
10
+ let(:app){ lambda{|env| [env[:status] || 200, {"Content-Type" => env[:type] || "text/plain"}, ["<body>Hello</body>"]]} }
11
+ let(:app_with_query){ lambda{|env| SqlSafetyNet::TestModel.first; [env[:status] || 200, {"Content-Type" => env[:type] || "text/plain"}, ["<body>Hello</body>"]]} }
12
+
13
+ it "should return the original response code" do
14
+ middleware = SqlSafetyNet::Middleware.new(app)
15
+ response = middleware.call(:status => 301)
16
+ response = Rack::Response.new(response[2], response[0], response[1])
17
+ response.status.should == 301
18
+ end
19
+
20
+ it "should not return the X-SqlSafetyNet header if not queries occurred" do
21
+ middleware = SqlSafetyNet::Middleware.new(app)
22
+ response = middleware.call(:status => 200)
23
+ response = Rack::Response.new(response[2], response[0], response[1])
24
+ response["X-SqlSafetyNet"].should == nil
25
+ end
26
+
27
+ it "should return the original response headers plus X-SqlSafetyNet" do
28
+ middleware = SqlSafetyNet::Middleware.new(app_with_query)
29
+ response = middleware.call(:status => 200)
30
+ response = Rack::Response.new(response[2], response[0], response[1])
31
+ response["X-SqlSafetyNet"].should include("1 query, 1 row")
32
+ end
33
+
34
+ it "should return the original body if no queries performed" do
35
+ middleware = SqlSafetyNet::Middleware.new(app)
36
+ response = middleware.call(:type => "text/html")
37
+ response = Rack::Response.new(response[2], response[0], response[1])
38
+ body = response.body.join("")
39
+ body.should == "<body>Hello</body>"
40
+ end
41
+
42
+ it "should return the original body plus the sql analysis if HTML and not Ajax and always show is on" do
43
+ SqlSafetyNet.override_config do |config|
44
+ config.always_show = true
45
+ middleware = SqlSafetyNet::Middleware.new(app_with_query)
46
+ response = middleware.call(:type => "text/html; charset=UTF-8")
47
+ response = Rack::Response.new(response[2], response[0], response[1])
48
+ body = response.body.join("")
49
+ body.should include("<body>Hello</body>")
50
+ body.should include("_sql_safety_net_")
51
+ end
52
+ end
53
+
54
+ it "should return the original body plus the sql analysis if XHTML and not Ajax and the queries are flagged" do
55
+ SqlSafetyNet.override_config do |config|
56
+ config.query_limit = 0
57
+ middleware = SqlSafetyNet::Middleware.new(app_with_query)
58
+ response = middleware.call(:type => "text/xhtml; charset=UTF-8")
59
+ response = Rack::Response.new(response[2], response[0], response[1])
60
+ body = response.body.join("")
61
+ body.should include("<body>Hello</body>")
62
+ body.should include("_sql_safety_net_")
63
+ end
64
+ end
65
+
66
+ it "should return the original body only if always_on is false and no flagged query" do
67
+ middleware = SqlSafetyNet::Middleware.new(app_with_query)
68
+ response = middleware.call(:type => "text/html; charset=UTF-8")
69
+ response = Rack::Response.new(response[2], response[0], response[1])
70
+ body = response.body.join("")
71
+ body.should == "<body>Hello</body>"
72
+ end
73
+
74
+ it "should return the original body only if Ajax" do
75
+ middleware = SqlSafetyNet::Middleware.new(app)
76
+ response = middleware.call(:type => "text/html", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
77
+ response = Rack::Response.new(response[2], response[0], response[1])
78
+ body = response.body.join("")
79
+ body.should == "<body>Hello</body>"
80
+ end
81
+
82
+ it "should return the original body only if not HTML" do
83
+ middleware = SqlSafetyNet::Middleware.new(app)
84
+ response = middleware.call(:type => "text/plain")
85
+ response = Rack::Response.new(response[2], response[0], response[1])
86
+ body = response.body.join("")
87
+ body.should == "<body>Hello</body>"
88
+ end
89
+
90
+ end