fluoride-analyzer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/fluoride-analyzer.rb +14 -0
- data/lib/fluoride-analyzer/config.rb +43 -0
- data/lib/fluoride-analyzer/exception-result.rb +5 -0
- data/lib/fluoride-analyzer/exchange-result.rb +5 -0
- data/lib/fluoride-analyzer/group-collapser.rb +51 -0
- data/lib/fluoride-analyzer/group-context.rb +86 -0
- data/lib/fluoride-analyzer/parser.rb +261 -0
- data/lib/fluoride-analyzer/pattern-collapser.rb +30 -0
- data/lib/fluoride-analyzer/pattern-context.rb +34 -0
- data/lib/fluoride-analyzer/rails.rb +1 -0
- data/lib/fluoride-analyzer/rails/railtie.rb +14 -0
- data/lib/fluoride-analyzer/request-processor.rb +7 -0
- data/lib/fluoride-analyzer/request-templater.rb +26 -0
- data/lib/fluoride-analyzer/request.rb +11 -0
- data/lib/fluoride-analyzer/result-collection.rb +5 -0
- data/lib/fluoride-analyzer/tasks.rake +46 -0
- data/spec-help/gem-test-suite.rb +17 -0
- data/spec/railtie_spec.rb +27 -0
- data/spec/result-parser.rb +110 -0
- data/spec/result-templater.rb +67 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 29f99f8f8b407486d27622bb63af1b8bd604d87f
|
4
|
+
data.tar.gz: b29dacf810a25eb37374ac88532f2c09217cdd65
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6c30d82f0adbc6051ee083d9062233bd8a632832b343647acad1c97cd7a82fbb1308a11a4959be511ba29d648c859f84dd2964ffa05c8992d34bdabd8b14c93
|
7
|
+
data.tar.gz: 7c618c9bf042e2319e0ccb95a0fb00338a82a0daa4088827b0573df89748f0b7f0340f2cba3bf44051158a0ea1dbcc7bbf67869bf469ea428121b50b58a2430f
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Fluoride
|
2
|
+
module Analyzer
|
3
|
+
class ConfigurationError < ::StandardError
|
4
|
+
end
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'fluoride-analyzer/config'
|
9
|
+
require 'fluoride-analyzer/request'
|
10
|
+
require 'fluoride-analyzer/request-processor'
|
11
|
+
require 'fluoride-analyzer/exchange-result'
|
12
|
+
require 'fluoride-analyzer/exception-result'
|
13
|
+
require 'fluoride-analyzer/result-collection'
|
14
|
+
require 'fluoride-analyzer/rails' if defined?(Rails)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Fluoride::Analyzer
|
2
|
+
class Config
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
DEFAULT_CONFIG_PATH = '.fluoride.yml'
|
6
|
+
DEFAULT_EXCLUDE_PATHS = [ %r[^/images], %r[^/stylesheets], %r[^/javascripts], %r[^/system], ]
|
7
|
+
DEFAULT_EXCLUDE_MIME_TYPES = [ %r[image], %r[text/css], %r[javascript], %r[shockwave] ]
|
8
|
+
|
9
|
+
attr_accessor :exclude_path_patterns,
|
10
|
+
:exclude_mime_types,
|
11
|
+
:match_on_required_params,
|
12
|
+
:exclude_match_params,
|
13
|
+
:limit_count
|
14
|
+
|
15
|
+
|
16
|
+
def self.load(config_path = DEFAULT_CONFIG_PATH)
|
17
|
+
yaml_config = YAML::load(File.open(config_path))
|
18
|
+
config = self.new(yaml_config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(yaml_config)
|
22
|
+
self.exclude_path_patterns = DEFAULT_EXCLUDE_PATHS
|
23
|
+
+ yaml_config['exclude_path_patterns']
|
24
|
+
- yaml_config['include_path_patterns']
|
25
|
+
|
26
|
+
self.exclude_mime_types = DEFAULT_EXCLUDE_MIME_TYPES
|
27
|
+
+ yaml_config['exclude_mime_types']
|
28
|
+
- yaml_config['include_mime_types']
|
29
|
+
|
30
|
+
|
31
|
+
if yaml_config.has_key?('match_on_required_params')
|
32
|
+
self.match_on_required_params = yaml_config['match_on_required_params']
|
33
|
+
else
|
34
|
+
self.match_on_required_params = false
|
35
|
+
end
|
36
|
+
|
37
|
+
self.exclude_match_params = yaml_config['exclude_match_params'] || []
|
38
|
+
self.include_match_params = yaml_config['include_match_params'] || []
|
39
|
+
self.limit_count = yaml_config['limit_count']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'fluoride-analyzer/group-context'
|
2
|
+
|
3
|
+
module Fluoride::Analyzer
|
4
|
+
class GroupCollapser
|
5
|
+
def initialize(pattern, letname_map, method, status, requests_list)
|
6
|
+
@pattern = pattern
|
7
|
+
@letname_map = letname_map
|
8
|
+
@request_list = requests_list
|
9
|
+
@method, @status = method, status
|
10
|
+
end
|
11
|
+
attr_reader :method, :status, :letname_map
|
12
|
+
|
13
|
+
def stet_list
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
def requests
|
18
|
+
@request_list
|
19
|
+
end
|
20
|
+
|
21
|
+
def reduce_path(path, request)
|
22
|
+
path = @pattern.dup
|
23
|
+
request['path_params'].each_pair do |name, value|
|
24
|
+
next if stet_list.include?(name)
|
25
|
+
target_name = letname_map.fetch(name, name)
|
26
|
+
path.sub!(/:#{name}/,"\#{#{target_name}}")
|
27
|
+
end
|
28
|
+
path
|
29
|
+
end
|
30
|
+
|
31
|
+
def reduce_query(query, request)
|
32
|
+
""
|
33
|
+
#none captured (yet?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def each_group_context
|
37
|
+
path_tuples = requests.map do |request|
|
38
|
+
[request['path'], request['query_params'], request]
|
39
|
+
end
|
40
|
+
|
41
|
+
reduced_hash = Hash.new{|h,k| h[k] = []}
|
42
|
+
path_tuples.each do |path, query, request|
|
43
|
+
reduced_hash[ [ reduce_path(path, request), reduce_query(query, request) ] ] << request
|
44
|
+
end
|
45
|
+
|
46
|
+
reduced_hash.each do |(path, query_params), requests|
|
47
|
+
yield GroupContext.new(method, status, requests, path, query_params)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'fluoride-analyzer'
|
2
|
+
|
3
|
+
module Fluoride::Analyzer
|
4
|
+
class GroupContext
|
5
|
+
def initialize(method, status, requests, path, query_params)
|
6
|
+
@method, @status_code, @requests, @path, @query_params = method, status, requests, path, query_params
|
7
|
+
end
|
8
|
+
attr_reader :method, :status_code
|
9
|
+
|
10
|
+
def test_method
|
11
|
+
@method.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def status_description
|
15
|
+
case @status_code.to_i
|
16
|
+
when 300..399
|
17
|
+
"Redirect"
|
18
|
+
else
|
19
|
+
"OK"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_count
|
24
|
+
@requests.inject(0) do |sum, request|
|
25
|
+
sum + request['sources'].keys.length
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def example_source
|
30
|
+
request['sources'].keys.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def example_path
|
34
|
+
request['path']
|
35
|
+
end
|
36
|
+
|
37
|
+
def request_spec_description
|
38
|
+
"#@method #{spec_request_path}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def should_what
|
42
|
+
case @status_code.to_i
|
43
|
+
when 300..399
|
44
|
+
"redirect"
|
45
|
+
else
|
46
|
+
"succeed"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def redirect_path
|
51
|
+
request['redirect_location'].sub(%r[^https?://#{request['host']}], '')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_result
|
55
|
+
case @status_code.to_i
|
56
|
+
when 300..399
|
57
|
+
["response.should redirect_to(\"#{redirect_path}\")"]
|
58
|
+
else
|
59
|
+
["response.should be_success", "response.status.should == 200"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_request(indent)
|
64
|
+
indent = " " * indent
|
65
|
+
test_request = "#{test_method} \"#{spec_request_path}\""
|
66
|
+
if request.has_key? 'post_params'
|
67
|
+
params = request['post_params'].pretty_inspect.split("\n")
|
68
|
+
params = ([params[0]] + params[1..-1].map do |line|
|
69
|
+
indent + line
|
70
|
+
end).join("\n")
|
71
|
+
test_request += ", #{params}"
|
72
|
+
end
|
73
|
+
test_request
|
74
|
+
end
|
75
|
+
|
76
|
+
def spec_request_path
|
77
|
+
"#{@path}#{@query_params}"
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def request
|
83
|
+
@requests.first
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'fluoride-analyzer'
|
2
|
+
require 'rack'
|
3
|
+
require 'yaml'
|
4
|
+
#Also: Rails and ActionDispatch::Request
|
5
|
+
|
6
|
+
module Fluoride::Analyzer
|
7
|
+
class Parser
|
8
|
+
attr_accessor :files, :limit, :target_path
|
9
|
+
attr_reader :exceptions, :results, :counts
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@exceptions = []
|
13
|
+
@counts = {
|
14
|
+
:excluded => 0,
|
15
|
+
:unrecognized => 0,
|
16
|
+
:no_matching_route => 0,
|
17
|
+
:exceptions => 0
|
18
|
+
}
|
19
|
+
@results = Hash.new do|results, pattern| #PATH PATTERN
|
20
|
+
results[pattern] = Hash.new do |by_method, method| #METHOD
|
21
|
+
by_method[method] = Hash.new do |records, status| #STATUS CODE
|
22
|
+
records[status] = Hash.new do |record, key| #formatted record
|
23
|
+
record[key] = []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@limit = -1
|
29
|
+
end
|
30
|
+
|
31
|
+
def excluded_content_types
|
32
|
+
[ %r[image], %r[text/css], %r[javascript], %r[shockwave] ]
|
33
|
+
end
|
34
|
+
|
35
|
+
def excluded_paths
|
36
|
+
[ %r[^/images], %r[\.ttf\z], %r[\.woff\z] ]
|
37
|
+
end
|
38
|
+
|
39
|
+
def exclude?(record)
|
40
|
+
return true if record['type'] == 'exception_raised'
|
41
|
+
|
42
|
+
if excluded_content_types.any?{ |reg| reg =~ record['response']['headers']['Content-Type'] }
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
if excluded_paths.any?{|req| req =~ record['request']['path']}
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
return false
|
49
|
+
rescue
|
50
|
+
warn "Exception raised while filtering record: #{record.inspect}"
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
|
54
|
+
def format_record(record, path_params)
|
55
|
+
{
|
56
|
+
'path' => record['request']['path'],
|
57
|
+
'query_params' => record['request']['query_params'],
|
58
|
+
'content-type' => record['request']['content_type'],
|
59
|
+
'path_params' => path_params,
|
60
|
+
'redirect_location' => record['response']['headers']['Location'],
|
61
|
+
'host' => record['request']['host']
|
62
|
+
#'accept' => record['request']['accept']
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def formatted_results
|
67
|
+
formatted = {}
|
68
|
+
results.each do |pattern, methods|
|
69
|
+
frmt_methods = formatted[pattern] = {}
|
70
|
+
methods.each do |method, status|
|
71
|
+
frmt_statuses = frmt_methods[method] = {}
|
72
|
+
status.each do |code, requests|
|
73
|
+
frmt_statuses[code] = requests.keys.map do |req_hash|
|
74
|
+
hash = req_hash.dup
|
75
|
+
hash['sources'] = Hash[requests[req_hash]]
|
76
|
+
hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
formatted
|
82
|
+
end
|
83
|
+
|
84
|
+
def post_params(record)
|
85
|
+
return {} unless %w{POST PUT}.include?(record['request']['method'])
|
86
|
+
unless record['request']['content_type'].nil? or
|
87
|
+
%r[^application/x-www-form-urlencoded.*] =~ record['request']['content_type']
|
88
|
+
return {}
|
89
|
+
end
|
90
|
+
|
91
|
+
form_hash = Rack::Utils.parse_nested_query(record['request']['body'])
|
92
|
+
|
93
|
+
{ 'post_params' => form_hash }
|
94
|
+
end
|
95
|
+
|
96
|
+
RoutePattern = Struct.new(:route, :matches, :params, :path_spec, :segment_keys)
|
97
|
+
|
98
|
+
class Patterner
|
99
|
+
def self.for(rails_routes)
|
100
|
+
if rails_routes.respond_to? :recognize_path
|
101
|
+
Rails4.new(rails_routes)
|
102
|
+
else
|
103
|
+
Rails3.new(rails_routes)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def base_env
|
108
|
+
@base_env ||= {
|
109
|
+
"HTTP_REFERER" => '',
|
110
|
+
"HTTP_COOKIE" => '',
|
111
|
+
"HTTP_AUTHORIZATION" => '',
|
112
|
+
"REQUEST_METHOD" => "GET",
|
113
|
+
'HTTP_HOST' => '',
|
114
|
+
'SERVER_NAME' => '',
|
115
|
+
'SERVER_ADDR' => '',
|
116
|
+
'SERVER_PORT' => '80',
|
117
|
+
"SCRIPT_NAME" => '',
|
118
|
+
"QUERY_STRING" => '',
|
119
|
+
'rack.input' => '' #body
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def initialize(rails_routes)
|
124
|
+
@rails_routes = rails_routes
|
125
|
+
end
|
126
|
+
attr_reader :rails_routes
|
127
|
+
|
128
|
+
def build_request(result_env)
|
129
|
+
ActionDispatch::Request.new(base_env.merge(request_env))
|
130
|
+
end
|
131
|
+
|
132
|
+
def route_map
|
133
|
+
@route_map ||=
|
134
|
+
begin
|
135
|
+
ad_routes_array = rails_routes.routes
|
136
|
+
rack_routes_array = rails_routes.set.instance_eval{ @routes }
|
137
|
+
Hash[ rack_routes_array.zip(ad_routes_array) ]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def route_set
|
142
|
+
@route_set ||=
|
143
|
+
begin
|
144
|
+
set = rails_routes.set
|
145
|
+
set
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Rails3 < Patterner
|
150
|
+
def build(env)
|
151
|
+
req = build_request(env)
|
152
|
+
route, matches, params = route_set.recognize(req)
|
153
|
+
|
154
|
+
path_spec = :unrecognized
|
155
|
+
segment_keys = {}
|
156
|
+
|
157
|
+
if route_map.has_key?(route)
|
158
|
+
rails_route = route_map[route]
|
159
|
+
path_spec = rails_route.path
|
160
|
+
segment_keys = rails_route.segment_keys
|
161
|
+
end
|
162
|
+
|
163
|
+
RoutePattern.new(route, matches, params, path_spec, segment_keys)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Rails4 < Patterner
|
168
|
+
def route_set
|
169
|
+
@route_set ||=
|
170
|
+
begin
|
171
|
+
set = Rails.application.routes.router
|
172
|
+
set
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def build_request(request_env)
|
177
|
+
ActionDispatch::Request.new(base_env.merge(request_env))
|
178
|
+
end
|
179
|
+
|
180
|
+
def build(env)
|
181
|
+
req = build_request(env)
|
182
|
+
pattern = nil
|
183
|
+
route_set.recognize(req) do |route, matches, params|
|
184
|
+
rails_route = route_map[route]
|
185
|
+
|
186
|
+
path_spec = :unrecognized
|
187
|
+
segment_keys = {}
|
188
|
+
|
189
|
+
if route_map.has_key?(route)
|
190
|
+
rails_route = route_map[route]
|
191
|
+
path_spec = rails_route.path.spec.to_s
|
192
|
+
segment_keys = rails_route.segment_keys
|
193
|
+
end
|
194
|
+
|
195
|
+
pattern = RoutePattern.new(route, matches, params, path_spec, segment_keys)
|
196
|
+
end
|
197
|
+
if pattern.nil?
|
198
|
+
pattern = RoutePattern.new(nil,nil,nil,nil,nil)
|
199
|
+
end
|
200
|
+
pattern
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def patterner
|
206
|
+
@patterner ||= Patterner.for(Rails.application.routes)
|
207
|
+
end
|
208
|
+
|
209
|
+
def collect_record(file, record, index)
|
210
|
+
if exclude?(record)
|
211
|
+
@counts[:excluded] += 1
|
212
|
+
return
|
213
|
+
end
|
214
|
+
|
215
|
+
request_env = {
|
216
|
+
"REQUEST_METHOD" => record['request']['method'].to_s.upcase,
|
217
|
+
"PATH_INFO" => record['request']['path'],
|
218
|
+
}
|
219
|
+
unless request_env['REQUEST_METHOD'] == "GET"
|
220
|
+
request_env['rack.input'] = StringIO.new(record['request']['body'])
|
221
|
+
end
|
222
|
+
|
223
|
+
pattern = patterner.build(request_env)
|
224
|
+
|
225
|
+
route = pattern.route
|
226
|
+
|
227
|
+
if route.nil?
|
228
|
+
@counts[:unrecognized] += 1
|
229
|
+
return
|
230
|
+
end
|
231
|
+
|
232
|
+
if pattern.path_spec == :unrecognized
|
233
|
+
@counts[:no_matching_route] += 1
|
234
|
+
warn "Unrecognized route: #{record['request']['path'].inspect}"
|
235
|
+
else
|
236
|
+
route_path = pattern.path_spec
|
237
|
+
path_params = Hash[pattern.segment_keys.map do |key|
|
238
|
+
[key, params[key]]
|
239
|
+
end]
|
240
|
+
end
|
241
|
+
|
242
|
+
formatted_record = format_record(record, path_params).merge(post_params(record))
|
243
|
+
|
244
|
+
self.results[route_path][record['request']['method']][record['response']['status']][formatted_record] << [file, index]
|
245
|
+
end
|
246
|
+
|
247
|
+
def parse_stream(file, string)
|
248
|
+
stream = YAML.load_stream(string)
|
249
|
+
stream = stream.documents if stream.respond_to? :documents
|
250
|
+
|
251
|
+
stream.each_with_index do |record, index|
|
252
|
+
collect_record(file, record, index)
|
253
|
+
end
|
254
|
+
rescue ArgumentError => ex
|
255
|
+
@exceptions << [ex, file]
|
256
|
+
rescue Exception => ex
|
257
|
+
@exceptions << [ex, file]
|
258
|
+
raise
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Fluoride::Analyzer
|
2
|
+
class PatternCollapser
|
3
|
+
def initialize(pattern, methods_hash)
|
4
|
+
@pattern = pattern
|
5
|
+
@methods_hash = methods_hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def erase_list
|
9
|
+
%w{ format }
|
10
|
+
end
|
11
|
+
|
12
|
+
def pattern
|
13
|
+
erase_list.inject(@pattern) do |pattern, erase|
|
14
|
+
pattern.sub(/\(.:#{erase}\)/,'')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def param_letname_map
|
19
|
+
{ :id => :model_id }
|
20
|
+
end
|
21
|
+
|
22
|
+
def params_fields
|
23
|
+
@methods_hash.values.first.values.first.first['path_params'].keys.reject do |key|
|
24
|
+
key == :format
|
25
|
+
end.map do |name|
|
26
|
+
param_letname_map.fetch(name, name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fluoride-analyzer/group-collapser'
|
2
|
+
require 'fluoride-analyzer/pattern-collapser'
|
3
|
+
|
4
|
+
module Fluoride::Analyzer
|
5
|
+
class PatternContext
|
6
|
+
def initialize(pattern, methods_hash)
|
7
|
+
@pattern, @methods_hash = pattern, methods_hash
|
8
|
+
@collapser = PatternCollapser.new(pattern, methods_hash)
|
9
|
+
end
|
10
|
+
attr_reader :pattern
|
11
|
+
|
12
|
+
def each_status_group
|
13
|
+
@methods_hash.each_pair do |method, hash|
|
14
|
+
hash.each_pair do |status, requests|
|
15
|
+
GroupCollapser.new(@collapser.pattern, @collapser.param_letname_map, method, status, requests).each_group_context do |context|
|
16
|
+
yield context
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def param_fields
|
23
|
+
@collapser.params_fields
|
24
|
+
end
|
25
|
+
|
26
|
+
def filename
|
27
|
+
@collapser.pattern.gsub(%r{[:/().]+},'_').gsub(/^_|_$/, '') + "_spec.rb"
|
28
|
+
end
|
29
|
+
|
30
|
+
def context_binding
|
31
|
+
binding
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'fluoride-analyzer/rails/railtie'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fluoride-analyzer/pattern-context'
|
3
|
+
|
4
|
+
module Fluoride::Analyzer
|
5
|
+
class RequestTemplater
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
end
|
9
|
+
attr_accessor :template, :template_string, :target_dir, :results
|
10
|
+
|
11
|
+
def template
|
12
|
+
@template ||= ERB.new(template_string, nil, '<>')
|
13
|
+
end
|
14
|
+
|
15
|
+
def go
|
16
|
+
results.each_pair do |pattern, statuses|
|
17
|
+
context = PatternContext.new(pattern, statuses)
|
18
|
+
|
19
|
+
path = File.join(context.filename)
|
20
|
+
contents = template.result(context.context_binding)
|
21
|
+
|
22
|
+
yield(path, contents)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'fluoride-analyzer/request-templater'
|
2
|
+
|
3
|
+
FLUORIDE_PATH = 'fluoride-collector'
|
4
|
+
LIMIT = 2000
|
5
|
+
|
6
|
+
directory 'spec/requests'
|
7
|
+
|
8
|
+
task :parse_fluoride => 'paths.yml'
|
9
|
+
|
10
|
+
task :rebuild_request_specs => [:clobber_request_specs, :template_request_specs]
|
11
|
+
|
12
|
+
task :clobber_request_specs do
|
13
|
+
sh("rm -rf spec/requests/*")
|
14
|
+
end
|
15
|
+
|
16
|
+
file 'results.yml' => FileList[ "#{FLUORIDE_PATH}/*.yml" ] do |task|
|
17
|
+
Rake::Task[:environment].invoke
|
18
|
+
parser = Fluoride::Analyzer::Parser.new
|
19
|
+
|
20
|
+
task.prerequisites.find_all do |prereq|
|
21
|
+
next unless File.file?(prereq) && __FILE__ != prereq
|
22
|
+
parser.parse_stream(File.read(prereq))
|
23
|
+
end
|
24
|
+
|
25
|
+
#parser.limit = LIMIT #XXX Uncomment to limit number of files parsed
|
26
|
+
parser.target_path = task.name
|
27
|
+
|
28
|
+
File.open(task.name, "w") do |target_file|
|
29
|
+
target_file.write(YAML.dump(parser.formatted_results))
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "Found #{parser.formatted_results.keys.length} unique requests"
|
33
|
+
end
|
34
|
+
|
35
|
+
task :template_request_specs => ['spec/requests', 'results.yml'] do
|
36
|
+
templater = Fluoride::Analyzer::RequestTemplater.new
|
37
|
+
|
38
|
+
templater.template_string = File::read(File::expand_path("../../../templates/request_spec.erb", __FILE__))
|
39
|
+
templater.results = YAML.load(File.read('results.yml'))
|
40
|
+
templater.go do |filename, contents|
|
41
|
+
path = File.join("spec/requests", filename)
|
42
|
+
File.open(path, "w") do |spec_file|
|
43
|
+
spec_file.write(contents)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
puts Dir::pwd
|
2
|
+
require 'test/unit'
|
3
|
+
begin
|
4
|
+
require 'spec'
|
5
|
+
rescue LoadError
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
class RSpecTest < Test::Unit::TestCase
|
10
|
+
def test_that_rspec_is_available
|
11
|
+
assert_nothing_raised("\n\n * RSpec isn't available - please run: gem install rspec *\n\n"){ ::Spec }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_that_specs_pass
|
15
|
+
assert(system(*%w{spec -f e -p **/*.rb spec}),"\n\n * Specs failed *\n\n")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "action_controller/railtie"
|
2
|
+
require 'fluoride-analyzer/rails'
|
3
|
+
|
4
|
+
describe Fluoride::Analyzer::Railtie do
|
5
|
+
|
6
|
+
def config(app)
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
let :rails_application do
|
11
|
+
Class.new(::Rails::Application).tap do |app|
|
12
|
+
app.configure do
|
13
|
+
config.active_support.deprecation = :stderr
|
14
|
+
config.eager_load = false
|
15
|
+
end
|
16
|
+
config(app)
|
17
|
+
app.initialize!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
after :each do
|
22
|
+
Rails.application = nil #because Rails has ideas of it's own, silly thing
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should add rake tasks"
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'action_controller/railtie'
|
2
|
+
require 'fluoride-analyzer/parser'
|
3
|
+
|
4
|
+
describe Fluoride::Analyzer::Parser do
|
5
|
+
def config(app)
|
6
|
+
app.routes.draw do
|
7
|
+
get "/", :to => 'root#index', :as => :root
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let! :rails_application do
|
12
|
+
Class.new(::Rails::Application).tap do |app|
|
13
|
+
app.configure do
|
14
|
+
config.active_support.deprecation = :stderr
|
15
|
+
config.eager_load = false
|
16
|
+
end
|
17
|
+
config(app)
|
18
|
+
app.initialize!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
after :each do
|
23
|
+
Rails.application = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
let :requests_stream do
|
27
|
+
YAML::dump_stream(*requests)
|
28
|
+
end
|
29
|
+
|
30
|
+
let :parser do
|
31
|
+
Fluoride::Analyzer::Parser.new.tap do |parser|
|
32
|
+
parser.parse_stream("test-file.yaml", requests_stream)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
let :results do
|
37
|
+
parser.formatted_results
|
38
|
+
end
|
39
|
+
|
40
|
+
let :requests do
|
41
|
+
[
|
42
|
+
{"type"=>"normal_exchange",
|
43
|
+
"tags"=>nil,
|
44
|
+
"request"=>
|
45
|
+
{"query_string"=>"",
|
46
|
+
"body"=>"",
|
47
|
+
"method"=>"GET",
|
48
|
+
"accept"=>
|
49
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
50
|
+
"content_type"=>nil,
|
51
|
+
"user_agent"=>
|
52
|
+
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36",
|
53
|
+
"referer"=>nil,
|
54
|
+
"host"=>"0.0.0.0:3000",
|
55
|
+
"authorization"=>nil,
|
56
|
+
"cookies"=>nil,
|
57
|
+
"path"=>"/",
|
58
|
+
"accept_encoding"=>"gzip,deflate,sdch"},
|
59
|
+
"response"=>
|
60
|
+
{"body"=>
|
61
|
+
["<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n<!--\n\tsomebody@gmail.com june 2014\n-->\n\n\t<meta charset=\"utf-8\" />\n\t<title>Application</title>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-sc"],
|
62
|
+
"status"=>200,
|
63
|
+
"headers"=>
|
64
|
+
{"Content-Type"=>"text/html; charset=utf-8",
|
65
|
+
"ETag"=>"\"5ff1d9c9172424e5d906216dcdacd9ef\"",
|
66
|
+
"X-UA-Compatible"=>"IE=Edge,chrome=1",
|
67
|
+
"Cache-Control"=>"max-age=0, private, must-revalidate",
|
68
|
+
"X-Runtime"=>"0.873536"}}
|
69
|
+
},
|
70
|
+
|
71
|
+
{"type"=>"normal_exchange",
|
72
|
+
"tags"=>nil,
|
73
|
+
"request"=>
|
74
|
+
{"query_string"=>"",
|
75
|
+
"body"=>"",
|
76
|
+
"method"=>"GET",
|
77
|
+
"accept"=>"text/css,*/*;q=0.1",
|
78
|
+
"content_type"=>nil,
|
79
|
+
"user_agent"=>
|
80
|
+
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36",
|
81
|
+
"referer"=>"http://0.0.0.0:3000/",
|
82
|
+
"host"=>"0.0.0.0:3000",
|
83
|
+
"authorization"=>nil,
|
84
|
+
"cookies"=>nil,
|
85
|
+
"path"=>"/stylesheets/2014-bootstrap.css",
|
86
|
+
"accept_encoding"=>"gzip,deflate,sdch"},
|
87
|
+
"response"=>
|
88
|
+
{"body"=>
|
89
|
+
["/*! normalize.css v3.0.0 | MIT License | git.io/normalize */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%; }\n\nbody {\n margin: 0; }\n\narticle,\naside,\n"],
|
90
|
+
"status"=>200,
|
91
|
+
"headers"=>
|
92
|
+
{"Content-Length"=>"15835",
|
93
|
+
"Content-Type"=>"text/css",
|
94
|
+
"Last-Modified"=>"Sun, 29 Jun 2014 00:00:48 GMT"}},
|
95
|
+
}
|
96
|
+
]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should have excluded 1 request" do
|
100
|
+
expect(parser.counts[:excluded]).to eq(1)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should have 0 unrecognized requests" do
|
104
|
+
expect(parser.counts[:unrecognized]).to eq(0)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should be a well formatted version of the results" do
|
108
|
+
expect(results).to match("/" => {"GET" => {200 => match([ a_hash_including("path" => "/") ])}})
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'fluoride-analyzer/request-templater'
|
2
|
+
|
3
|
+
describe Fluoride::Analyzer::RequestTemplater do
|
4
|
+
let :template_string do
|
5
|
+
<<-EOT
|
6
|
+
require 'spec_helper'
|
7
|
+
|
8
|
+
describe '<%= pattern %>' do
|
9
|
+
<% param_fields.each do |field| %>
|
10
|
+
let :<%= field %> do
|
11
|
+
FactoryGirl.create(:<%= field %>).id
|
12
|
+
end
|
13
|
+
<%end%>
|
14
|
+
<% each_status_group do |group| %>
|
15
|
+
|
16
|
+
# HTTP status code: <%= group.status_code %> <%= group.status_description %>
|
17
|
+
# <%= group.request_count %> cases recorded by Fluoride
|
18
|
+
# See example: '<%= group.example_source %>'
|
19
|
+
# example path: <%= group.example_path %>
|
20
|
+
describe '<%= group.request_spec_description %>' do
|
21
|
+
it "should <%= group.should_what %>" do
|
22
|
+
<%= group.test_request(6) %>
|
23
|
+
<% group.test_result.each do |test_result_line| %>
|
24
|
+
<%= test_result_line %>
|
25
|
+
<% end %>
|
26
|
+
end
|
27
|
+
end
|
28
|
+
<%end%>
|
29
|
+
end
|
30
|
+
EOT
|
31
|
+
end
|
32
|
+
|
33
|
+
let :results do
|
34
|
+
YAML.load(File.read(File.expand_path("../../spec-help/fixtures/results.yml", __FILE__)))
|
35
|
+
end
|
36
|
+
|
37
|
+
let :templater do
|
38
|
+
Fluoride::Analyzer::RequestTemplater.new.tap do |tmplr|
|
39
|
+
tmplr.template_string = template_string
|
40
|
+
tmplr.results = results
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
let :templated_list do
|
45
|
+
templated_list = []
|
46
|
+
templater.go do |path, contents|
|
47
|
+
templated_list << [path, contents]
|
48
|
+
end
|
49
|
+
templated_list
|
50
|
+
end
|
51
|
+
|
52
|
+
let :paths do
|
53
|
+
templated_list.map{|path, _| path}
|
54
|
+
end
|
55
|
+
|
56
|
+
let :contents do
|
57
|
+
templated_list.map{|_, contents| contents}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should template once per result" do
|
61
|
+
expect(templated_list.length).to eq(results.keys.length)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should put the right stuff in contents" do
|
65
|
+
expect(contents).to all(match(/HTTP status code:/))
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluoride-analyzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Judson Lester
|
8
|
+
- Evan Down
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-07-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mattock
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>'
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>'
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: valise
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>'
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>'
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: |2
|
43
|
+
Part of the Fluoride suite - tools for making your black box a bit whiter
|
44
|
+
email:
|
45
|
+
- nyarly@gmail.com
|
46
|
+
- evan@lrdesign.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- lib/fluoride-analyzer/parser.rb
|
52
|
+
- lib/fluoride-analyzer/exception-result.rb
|
53
|
+
- lib/fluoride-analyzer/rails/railtie.rb
|
54
|
+
- lib/fluoride-analyzer/group-context.rb
|
55
|
+
- lib/fluoride-analyzer/config.rb
|
56
|
+
- lib/fluoride-analyzer/request-templater.rb
|
57
|
+
- lib/fluoride-analyzer/group-collapser.rb
|
58
|
+
- lib/fluoride-analyzer/rails.rb
|
59
|
+
- lib/fluoride-analyzer/pattern-collapser.rb
|
60
|
+
- lib/fluoride-analyzer/request.rb
|
61
|
+
- lib/fluoride-analyzer/tasks.rake
|
62
|
+
- lib/fluoride-analyzer/result-collection.rb
|
63
|
+
- lib/fluoride-analyzer/request-processor.rb
|
64
|
+
- lib/fluoride-analyzer/pattern-context.rb
|
65
|
+
- lib/fluoride-analyzer/exchange-result.rb
|
66
|
+
- lib/fluoride-analyzer.rb
|
67
|
+
- spec/result-templater.rb
|
68
|
+
- spec/railtie_spec.rb
|
69
|
+
- spec/result-parser.rb
|
70
|
+
- spec-help/gem-test-suite.rb
|
71
|
+
homepage: http://github.com/lrdesign/fluoride-analyzer
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --inline-source
|
78
|
+
- --main
|
79
|
+
- doc/README
|
80
|
+
- --title
|
81
|
+
- fluoride-analyzer-0.0.1 Documentation
|
82
|
+
require_paths:
|
83
|
+
- lib/
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.0.14
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Analysis of recorded requests
|
100
|
+
test_files:
|
101
|
+
- spec-help/gem-test-suite.rb
|