oxidized-web 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of oxidized-web might be problematic. Click here for more details.

@@ -2,6 +2,6 @@
2
2
 
3
3
  module Oxidized
4
4
  module API
5
- WEB_VERSION = '0.14.0'
5
+ WEB_VERSION = '0.15.0'
6
6
  end
7
7
  end
@@ -20,10 +20,6 @@
20
20
  %a.nav-link{class: request.path_info == '/nodes/stats' ? 'active' : '',
21
21
  :'aria-current' => request.path_info == '/nodes/stats' ? 'page' : 'false',
22
22
  href: url_for('/nodes/stats')} Stats
23
- %li.nav-item
24
- %a.nav-link{class: request.path_info == '/migration' ? 'active' : '',
25
- :'aria-current' => request.path_info == '/migration' ? 'page' : 'false',
26
- href: url_for('/migration')} Migration
27
23
  %form.d-flex{role: 'search',
28
24
  action: url_for('/nodes/conf_search'),
29
25
  method: 'post'}
@@ -6,7 +6,6 @@ require 'tilt/haml'
6
6
  # rubocop:disable Lint/RedundantRequireStatement
7
7
  require 'pp'
8
8
  # rubocop:enable Lint/RedundantRequireStatement
9
- require 'oxidized/web/mig'
10
9
  require 'htmlentities'
11
10
  require 'charlock_holmes'
12
11
  module Oxidized
@@ -26,9 +25,17 @@ module Oxidized
26
25
  redirect url_for('/images/favicon.ico')
27
26
  end
28
27
 
29
- get '/nodes/:filter/:value.?:format?' do
28
+ # :filter can be "group" or "model"
29
+ # URL: /nodes/group/<GroupName>[.json]
30
+ # URL: /nodes/model/<ModelName>[.json]
31
+ # an optional .json extention returns the data as JSON
32
+ #
33
+ # as GroupName can include /, we use splat to match its value
34
+ # and extract the optional ".json" with route_parse
35
+ get '/nodes/:filter/*' do
36
+ value, @json = route_parse params[:splat].first
30
37
  @data = nodes.list.select do |node|
31
- next unless node[params[:filter].to_sym] == params[:value]
38
+ next unless node[params[:filter].to_sym] == value
32
39
 
33
40
  node[:status] = 'never'
34
41
  node[:time] = 'never'
@@ -100,7 +107,7 @@ module Oxidized
100
107
  out :text
101
108
  end
102
109
 
103
- # URL: /node/fetch/<group>/<node>.json
110
+ # URL: /node/fetch/<group>/<node>[.json]
104
111
  # <group> is optional, and not used
105
112
  # .json is optional. If given, will return 'ok'
106
113
  # if not, it redirects to /nodes
@@ -128,40 +135,17 @@ module Oxidized
128
135
  out :node
129
136
  end
130
137
 
131
- # redirect to the web page for rancid - oxidized migration
132
- get '/migration' do
133
- out :migration
134
- end
135
-
136
- # get the files send
137
- post '/migration' do
138
- number = params[:number].to_i
139
- cloginrc_file = params['cloginrc'][:tempfile]
140
- path_new_file = params['path_new_file']
141
-
142
- router_db_files = []
143
-
144
- i = 1
145
- while i <= number
146
- router_db_files.push({ file: params["file#{i}"][:tempfile], group: params["group#{i}"] })
147
- i += 1
148
- end
149
-
150
- migration = Mig.new(router_db_files, cloginrc_file, path_new_file)
151
- migration.go_rancid_migration
152
- redirect url_for('//nodes')
153
- end
154
-
155
- # show the lists of versions for a node
138
+ # display the versions of a node
139
+ # URL: /node/version[.json]?node_full=<GroupName/NodeName>
156
140
  get '/node/version.?:format?' do
157
141
  @data = nil
158
142
  @group = nil
159
143
  @node = nil
160
144
  node_full = params[:node_full]
161
145
  if node_full.include? '/'
162
- node_full = node_full.split('/')
146
+ node_full = node_full.rpartition("/")
163
147
  @group = node_full[0]
164
- @node = node_full[1]
148
+ @node = node_full[2]
165
149
  @data = nodes.version @node, @group
166
150
  else
167
151
  @node = node_full
@@ -182,7 +166,7 @@ module Oxidized
182
166
  }
183
167
 
184
168
  the_data = nodes.get_version node, @info[:group], @info[:oid]
185
- if params[:format] == 'json' || params[:format] == 'text'
169
+ if %w[json text].include?(params[:format])
186
170
  @data = the_data
187
171
  else
188
172
  utf8_encoded_content = convert_to_utf8(the_data)
@@ -231,6 +215,10 @@ module Oxidized
231
215
  redirect url_for("/node/version/diffs?node=#{params[:node]}&group=#{params[:group]}&oid=#{params[:oid]}&date=#{params[:date]}&num=#{params[:num]}&oid2=#{params[:oid2]}")
232
216
  end
233
217
 
218
+ # Taken von Haml 5.0, so it still works in 6.0
219
+ HTML_ESCAPE = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#39;' }.freeze
220
+ HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
221
+
234
222
  private
235
223
 
236
224
  def out(template = :text)
@@ -328,9 +316,6 @@ module Oxidized
328
316
  { old_diff: old_diff, new_diff: new_diff }
329
317
  end
330
318
 
331
- # Taken von Haml 5.0, so it still works in 6.0
332
- HTML_ESCAPE = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#39;' }.freeze
333
- HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
334
319
  def escape_once(text)
335
320
  text = text.to_s
336
321
  text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
data/oxidized-web.gemspec CHANGED
@@ -18,17 +18,18 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.metadata['rubygems_mfa_required'] = 'true'
20
20
 
21
- s.required_ruby_version = '>= 3.1'
21
+ s.required_ruby_version = '>= 3.1'
22
22
 
23
- s.add_runtime_dependency 'charlock_holmes', '~> 0.7.5'
24
- s.add_runtime_dependency 'emk-sinatra-url-for', '~> 0.2'
25
- s.add_runtime_dependency 'haml', '~> 6.0'
26
- s.add_runtime_dependency 'htmlentities', '~> 4.3'
27
- s.add_runtime_dependency 'json', '~> 2.3'
28
- s.add_runtime_dependency 'oxidized', '~> 0.26'
29
- s.add_runtime_dependency 'puma', '>= 3.11.4', '< 6.5.0'
30
- s.add_runtime_dependency 'sinatra', '>= 1.4.6', '< 5.0'
31
- s.add_runtime_dependency 'sinatra-contrib', '>= 1.4.6', '< 5.0'
23
+ s.add_dependency 'charlock_holmes', '~> 0.7.5'
24
+ s.add_dependency 'emk-sinatra-url-for', '~> 0.2'
25
+ s.add_dependency 'haml', '~> 6.0'
26
+ s.add_dependency 'htmlentities', '~> 4.3'
27
+ s.add_dependency 'json', '~> 2.3'
28
+ s.add_dependency 'ostruct', '~> 0.6'
29
+ s.add_dependency 'oxidized', '~> 0.31'
30
+ s.add_dependency 'puma', '>= 3.11.4'
31
+ s.add_dependency 'sinatra', '>= 1.4.6'
32
+ s.add_dependency 'sinatra-contrib', '>= 1.4.6'
32
33
 
33
34
  s.add_development_dependency 'bundler', '~> 2.2'
34
35
  s.add_development_dependency 'minitest', '~> 5.18'
@@ -36,11 +37,11 @@ Gem::Specification.new do |s|
36
37
  s.add_development_dependency 'rack-test', '~> 2.1'
37
38
  s.add_development_dependency 'rails_best_practices', '~> 1.19'
38
39
  s.add_development_dependency 'rake', '~> 13.0'
39
- s.add_development_dependency 'rubocop', '~> 1.64.1'
40
- s.add_development_dependency 'rubocop-minitest', '~> 0.35.0'
41
- s.add_development_dependency 'rubocop-rails', '~> 2.25.0'
42
- s.add_development_dependency 'rubocop-rake', '~> 0.6.0'
40
+ s.add_development_dependency 'rubocop', '~> 1.72.1'
41
+ s.add_development_dependency 'rubocop-minitest', '~> 0.37.1'
42
+ s.add_development_dependency 'rubocop-rails', '~> 2.30.0'
43
+ s.add_development_dependency 'rubocop-rake', '~> 0.7.1'
43
44
  s.add_development_dependency 'simplecov', '~> 0.22.0'
44
45
  s.add_development_dependency 'simplecov-cobertura', '~> 2.1.0'
45
- s.add_development_dependency 'simplecov-html', '~> 0.12.3'
46
+ s.add_development_dependency 'simplecov-html', '~> 0.13.1'
46
47
  end
data/package-lock.json CHANGED
@@ -60,38 +60,38 @@
60
60
  ]
61
61
  },
62
62
  "node_modules/datatables.net": {
63
- "version": "2.0.8",
64
- "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.0.8.tgz",
65
- "integrity": "sha512-4/2dYx4vl975zQqZbyoVEm0huPe61qffjBRby7K7V+y9E+ORq4R8KavkgrNMmIgO6cl85Pg4AvCbVjvPCIT1Yg==",
63
+ "version": "2.2.2",
64
+ "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.2.2.tgz",
65
+ "integrity": "sha512-gfODIKE3gpgbVeZy2QGj2Dq9roO6hy00S+k1knklrqlMyAMrh1wt0Q6ryBUM7gU96U77ysbq8dYhxFdmcC/oPQ==",
66
66
  "dependencies": {
67
67
  "jquery": ">=1.7"
68
68
  }
69
69
  },
70
70
  "node_modules/datatables.net-bs5": {
71
- "version": "2.0.8",
72
- "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.0.8.tgz",
73
- "integrity": "sha512-rpz/yO2NZMP1Uso/sSsaFAKwdCjYPa1/KLxAVr0JNJJV9ygFLHcuKTcNmoc1cekcsjYcGyybWKaNu4NfpZ74vg==",
71
+ "version": "2.2.2",
72
+ "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.2.2.tgz",
73
+ "integrity": "sha512-0mAbpUf0EpnIEc0RlN6vSrSk9y/+NuReiwDpjHYY3RfzdvH6Lt0+7Q9OU5RIbYxaFxES/z60thxdrw7IUFnBhw==",
74
74
  "dependencies": {
75
- "datatables.net": "2.0.8",
75
+ "datatables.net": "2.2.2",
76
76
  "jquery": ">=1.7"
77
77
  }
78
78
  },
79
79
  "node_modules/datatables.net-buttons": {
80
- "version": "3.0.2",
81
- "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-3.0.2.tgz",
82
- "integrity": "sha512-J+vk4hLtTivnl+RxzpKPE7CG4ggdgHPQcHnpqViy9w6ia18Uh69dQktX6NJ87QrqNPCTMUyHDzUzsRFURG4/Fw==",
80
+ "version": "3.2.2",
81
+ "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-3.2.2.tgz",
82
+ "integrity": "sha512-+aLTbkbksNmyGpK+8KXbpwYKXYOXvZQR2ySA/8oOQeJU53Xw/67cOHowenEr2d43/RLaz+I0zvV/1Yn+jMRiDw==",
83
83
  "dependencies": {
84
84
  "datatables.net": "^2",
85
85
  "jquery": ">=1.7"
86
86
  }
87
87
  },
88
88
  "node_modules/datatables.net-buttons-bs5": {
89
- "version": "3.0.2",
90
- "resolved": "https://registry.npmjs.org/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-3.0.2.tgz",
91
- "integrity": "sha512-whufHsfKgzzdmTqM7JnFUph5hveHTCAvs9N0CP+5t7k7sIr7b94rIxv22/2Wt4veLcd3v73NdhPWl4j/GfzyhA==",
89
+ "version": "3.2.2",
90
+ "resolved": "https://registry.npmjs.org/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-3.2.2.tgz",
91
+ "integrity": "sha512-xjUcbYCBHcUthD1pvo5ghTNjqE6fTMygRrKd0QjBHKQxcqxmHG/m0djD2s6cFBfm8oov132U7U2JCXgQifOoUA==",
92
92
  "dependencies": {
93
93
  "datatables.net-bs5": "^2",
94
- "datatables.net-buttons": "3.0.2",
94
+ "datatables.net-buttons": "3.2.2",
95
95
  "jquery": ">=1.7"
96
96
  }
97
97
  },
data/spec/node_spec.rb CHANGED
@@ -66,9 +66,9 @@ describe Oxidized::API::WebApp do
66
66
  end
67
67
 
68
68
  # Don't know if this feature is used by anyone...
69
- it 'attaches author/email/message to the commit when using put and json' do
69
+ it 'attaches user/email/message to the commit when using put and json' do
70
70
  data = {
71
- 'author' => 'me',
71
+ 'user' => 'me',
72
72
  'email' => 'me@example.com',
73
73
  'message' => 'minitest, rack/test & mock simply rock',
74
74
  'from' => 'unused variable!'
@@ -83,7 +83,7 @@ describe Oxidized::API::WebApp do
83
83
 
84
84
  it 'attaches data to the commit when using a group and put, then redirects' do
85
85
  data = {
86
- 'author' => 'me',
86
+ 'user' => 'me',
87
87
  'email' => 'me@example.com',
88
88
  'message' => 'minitest, rack/test & mock simply rock',
89
89
  'from' => 'unused variable!'
@@ -96,48 +96,4 @@ describe Oxidized::API::WebApp do
96
96
  _(last_response.location).must_equal 'http://example.org/nodes'
97
97
  end
98
98
  end
99
-
100
- describe '/node/version/view.?:format?' do
101
- it 'fetches a previous version from git' do
102
- @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns('Old configuration of sw42')
103
-
104
- get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
105
- _(last_response.ok?).must_equal true
106
- _(last_response.body.include?('Old configuration of sw42')).must_equal true
107
- end
108
-
109
- it 'does not display binary content' do
110
- @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns("\xff\x42 binary content\x00")
111
-
112
- get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
113
- _(last_response.ok?).must_equal true
114
- _(last_response.body.include?('cannot display')).must_equal true
115
- end
116
-
117
- it 'fetches a git-version when using a group containing /' do
118
- @nodes.expects(:get_version).with('sw5', 'my/group', 'c8aa93cab5').returns('Old configuration of sw42')
119
-
120
- get '/node/version/view?node=sw5&group=my/group&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
121
- _(last_response.ok?).must_equal true
122
- _(last_response.body.include?('Old configuration of sw42')).must_equal true
123
- end
124
-
125
- it 'does not encode html-chars in text-format' do
126
- configuration = "text &/<> \n ascii;"
127
- @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
128
- get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2&format=text'
129
-
130
- _(last_response.ok?).must_equal true
131
- _(last_response.body).must_equal configuration
132
- end
133
-
134
- it 'does not encode html-chars in json-format' do
135
- configuration = "text &/<> \n ascii;"
136
- @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
137
- get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2&format=json'
138
-
139
- _(last_response.ok?).must_equal true
140
- _(last_response.body).must_equal '["text &/<> \n"," ascii;"]'
141
- end
142
- end
143
99
  end
@@ -0,0 +1,102 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Oxidized::API::WebApp do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Oxidized::API::WebApp
8
+ end
9
+
10
+ before do
11
+ @nodes = mock('Oxidized::Nodes')
12
+ app.set(:nodes, @nodes)
13
+ end
14
+
15
+ describe '/node/version.?:format?' do
16
+ it 'fetches all versions of a node without a group' do
17
+ @nodes.expects(:version).with('sw5', nil).returns(
18
+ [{ oid: "C006", date: "2025-02-05 19:49:00 +0100" },
19
+ { oid: "C003", date: "2025-02-05 19:03:00 +0100" },
20
+ { oid: "C001", date: "2025-02-05 19:01:00 +0100" }]
21
+ )
22
+
23
+ get '/node/version?node_full=sw5'
24
+ _(last_response.ok?).must_equal true
25
+ _(last_response.body.include?(
26
+ "<tr>\n<td>3</td>\n<td>2025-02-05 19:49:00 +0100</td>\n"
27
+ )).must_equal true
28
+ end
29
+
30
+ it 'fetches all versions of a node with a group' do
31
+ @nodes.expects(:version).with('sw5', 'group1').returns(
32
+ [{ oid: "C006", date: "2025-02-05 19:49:00 +0100" },
33
+ { oid: "C003", date: "2025-02-05 19:03:00 +0100" },
34
+ { oid: "C001", date: "2025-02-05 19:01:00 +0100" }]
35
+ )
36
+
37
+ get '/node/version?node_full=group1/sw5'
38
+ _(last_response.ok?).must_equal true
39
+ _(last_response.body.include?(
40
+ "<tr>\n<td>3</td>\n<td>2025-02-05 19:49:00 +0100</td>\n"
41
+ )).must_equal true
42
+ end
43
+
44
+ it 'fetches all versions of a node with a group with /' do
45
+ @nodes.expects(:version).with('sw5', 'gr/oup1').returns(
46
+ [{ oid: "C006", date: "2025-02-05 19:49:00 +0100" },
47
+ { oid: "C003", date: "2025-02-05 19:03:00 +0100" },
48
+ { oid: "C001", date: "2025-02-05 19:01:00 +0100" }]
49
+ )
50
+
51
+ get '/node/version?node_full=gr/oup1/sw5'
52
+ _(last_response.ok?).must_equal true
53
+ _(last_response.body.include?(
54
+ "<tr>\n<td>3</td>\n<td>2025-02-05 19:49:00 +0100</td>\n"
55
+ )).must_equal true
56
+ end
57
+ end
58
+
59
+ describe '/node/version/view.?:format?' do
60
+ it 'fetches a previous version from git' do
61
+ @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns('Old configuration of sw5')
62
+
63
+ get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
64
+ _(last_response.ok?).must_equal true
65
+ _(last_response.body.include?('Old configuration of sw5')).must_equal true
66
+ end
67
+
68
+ it 'does not display binary content' do
69
+ @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns("\xff\x42 binary content\x00")
70
+
71
+ get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
72
+ _(last_response.ok?).must_equal true
73
+ _(last_response.body.include?('cannot display')).must_equal true
74
+ end
75
+
76
+ it 'fetches a git-version when using a group containing /' do
77
+ @nodes.expects(:get_version).with('sw5', 'my/group', 'c8aa93cab5').returns('Old configuration of sw5')
78
+
79
+ get '/node/version/view?node=sw5&group=my/group&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2'
80
+ _(last_response.ok?).must_equal true
81
+ _(last_response.body.include?('Old configuration of sw5')).must_equal true
82
+ end
83
+
84
+ it 'does not encode html-chars in text-format' do
85
+ configuration = "text &/<> \n ascii;"
86
+ @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
87
+ get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2&format=text'
88
+
89
+ _(last_response.ok?).must_equal true
90
+ _(last_response.body).must_equal configuration
91
+ end
92
+
93
+ it 'does not encode html-chars in json-format' do
94
+ configuration = "text &/<> \n ascii;"
95
+ @nodes.expects(:get_version).with('sw5', '', 'c8aa93cab5').returns(configuration)
96
+ get '/node/version/view?node=sw5&group=&oid=c8aa93cab5&date=2024-06-07 08:27:37 +0200&num=2&format=json'
97
+
98
+ _(last_response.ok?).must_equal true
99
+ _(last_response.body).must_equal '["text &/<> \n"," ascii;"]'
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,57 @@
1
+ require_relative 'spec_helper'
2
+ require 'json'
3
+
4
+ describe Oxidized::API::WebApp do
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ Oxidized::API::WebApp
9
+ end
10
+
11
+ before do
12
+ @nodes = mock('Oxidized::Nodes')
13
+ @nodes.expects(:list).returns(
14
+ [{ name: 'sw4', ip: '10.10.10.10', model: 'ios', time: 'time', mtime: 'mtime' },
15
+ { name: 'sw5', ip: '10.10.10.5', model: 'ios', time: 'time', mtime: 'mtime' },
16
+ { name: 'sw6', ip: '10.10.10.6', model: 'ios', time: 'time', mtime: 'mtime' },
17
+ { name: 'sw7', ip: '10.10.10.7', model: 'ios', time: 'time', mtime: 'mtime', group: 'group1' },
18
+ { name: 'sw8', ip: '10.10.10.8', model: 'aos', time: 'time', mtime: 'mtime', group: 'group1' },
19
+ { name: 'sw9', ip: '10.10.10.9', model: 'aos', time: 'time', mtime: 'mtime', group: 'gr/oup1' }]
20
+ )
21
+ app.set(:nodes, @nodes)
22
+ end
23
+
24
+ describe '/nodes.?:format?' do
25
+ it 'shows all nodes' do
26
+ get '/nodes.json'
27
+
28
+ _(last_response.ok?).must_equal true
29
+ result = JSON.parse(last_response.body)
30
+ _(result.length).must_equal 6
31
+ end
32
+ end
33
+
34
+ describe '/nodes/:filter/*' do
35
+ it 'shows all nodes of a group' do
36
+ get '/nodes/group/group1.json'
37
+
38
+ _(last_response.ok?).must_equal true
39
+ result = JSON.parse(last_response.body)
40
+ _(result.length).must_equal 2
41
+ end
42
+ it 'shows all nodes of a group with /' do
43
+ get '/nodes/group/gr/oup1.json'
44
+
45
+ _(last_response.ok?).must_equal true
46
+ result = JSON.parse(last_response.body)
47
+ _(result.length).must_equal 1
48
+ end
49
+ it 'shows all nodes of a model' do
50
+ get '/nodes/model/ios.json'
51
+
52
+ _(last_response.ok?).must_equal true
53
+ result = JSON.parse(last_response.body)
54
+ _(result.length).must_equal 4
55
+ end
56
+ end
57
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # Needed to get the error output on the console and not in last_response.body
2
2
  ENV['APP_ENV'] = 'test'
3
3
 
4
+ require 'simplecov'
5
+ SimpleCov.start
6
+
4
7
  require 'minitest/autorun'
5
8
  require 'rack/test'
6
9
  require 'oxidized'