apiculture 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/apiculture.rb CHANGED
@@ -9,40 +9,41 @@ module Apiculture
9
9
  require_relative 'apiculture/markdown_segment'
10
10
  require_relative 'apiculture/timestamp_promise'
11
11
  require_relative 'apiculture/app_documentation'
12
-
12
+ require_relative 'apiculture/openapi_documentation'
13
+
13
14
  def self.extended(in_class)
14
15
  in_class.send(:include, SinatraInstanceMethods)
15
16
  super
16
17
  end
17
-
18
+
18
19
  IDENTITY_PROC = ->(arg) { arg }
19
-
20
+
20
21
  AC_APPLY_TYPECAST_PROC = ->(cast_proc_or_method, v) {
21
22
  cast_proc_or_method.is_a?(Symbol) ? v.public_send(cast_proc_or_method) : cast_proc_or_method.call(v)
22
23
  }
23
-
24
+
24
25
  AC_CHECK_PRESENCE_PROC = ->(name_as_string, params) {
25
26
  params.has_key?(name_as_string) or raise MissingParameter.new(name_as_string)
26
27
  }
27
-
28
+
28
29
  AC_CHECK_TYPE_PROC = ->(param, value) {
29
30
  param.matchable === value or raise ParameterTypeMismatch.new(param, value.class)
30
31
  }
31
-
32
+
32
33
  class Parameter < Struct.new(:name, :description, :required, :matchable, :cast_proc_or_method)
33
34
  # Return Strings since Sinatra prefers string keys for params{}
34
35
  def name_as_string; name.to_s; end
35
36
  end
36
-
37
+
37
38
  class RouteParameter < Parameter
38
39
  end
39
-
40
+
40
41
  class PossibleResponse < Struct.new(:http_status_code, :description, :jsonable_object_example)
41
42
  def no_body?
42
43
  jsonable_object_example.nil?
43
44
  end
44
45
  end
45
-
46
+
46
47
  # Indicates where this API will be mounted. This is only used
47
48
  # for the generated documentation. In general, this should match
48
49
  # the SCRIPT_NAME of the Sinatra application when it will be called.
@@ -60,13 +61,13 @@ module Apiculture
60
61
  def mounted_at(path)
61
62
  @apiculture_mounted_at = path.to_s.gsub(/\/$/, '')
62
63
  end
63
-
64
+
64
65
  # Inserts the generation timestamp into the documentation at this point.
65
66
  # The timestamp will be not very precise (to the minute) and in UTC time
66
67
  def documentation_build_time!
67
68
  apiculture_stack << Apiculture::TimestampPromise
68
69
  end
69
-
70
+
70
71
  # Inserts a literal Markdown string into the documentation at this point.
71
72
  # For instance, if used after an API method declaration, it will insert
72
73
  # the header between the API methods in the doc.
@@ -81,7 +82,7 @@ module Apiculture
81
82
  def markdown_string(str)
82
83
  apiculture_stack << MarkdownSegment.new(str)
83
84
  end
84
-
85
+
85
86
  # Inserts the contents of the file at +path+ into the documentation, using +markdown_string+.
86
87
  # For instance, if used after an API method declaration, it will insert
87
88
  # the header between the API methods in the doc.
@@ -94,25 +95,25 @@ module Apiculture
94
95
  md = File.read(path_to_markdown).encode(Encoding::UTF_8)
95
96
  markdown_string(md)
96
97
  end
97
-
98
+
98
99
  # Describe the API method that is going to be defined
99
100
  def desc(action_description)
100
101
  @apiculture_action_definition ||= ActionDefinition.new
101
102
  @apiculture_action_definition.description = action_description.to_s
102
103
  end
103
-
104
+
104
105
  # Add an optional parameter for the API call
105
106
  def param(name, description, matchable, cast: IDENTITY_PROC)
106
107
  @apiculture_action_definition ||= ActionDefinition.new
107
108
  @apiculture_action_definition.parameters << Parameter.new(name, description, required=false, matchable, cast)
108
109
  end
109
-
110
+
110
111
  # Add a requred parameter for the API call
111
112
  def required_param(name, description, matchable, cast: IDENTITY_PROC)
112
113
  @apiculture_action_definition ||= ActionDefinition.new
113
114
  @apiculture_action_definition.parameters << Parameter.new(name, description, required=true, matchable, cast)
114
115
  end
115
-
116
+
116
117
  # Describe a parameter that has to be included in the URL of the API call.
117
118
  # Route parameters are always required, and all the parameters specified
118
119
  # using +route_param+ should also be included in the path given for the route
@@ -121,28 +122,28 @@ module Apiculture
121
122
  @apiculture_action_definition ||= ActionDefinition.new
122
123
  @apiculture_action_definition.route_parameters << RouteParameter.new(name, description, required=false, matchable, cast)
123
124
  end
124
-
125
+
125
126
  # Add a possible response, specifying the code and the JSON Response by example.
126
127
  # Multiple response packages can be specified.
127
128
  def responds_with(http_status, description, example_jsonable_object = nil)
128
129
  @apiculture_action_definition ||= ActionDefinition.new
129
130
  @apiculture_action_definition.responses << PossibleResponse.new(http_status, description, example_jsonable_object)
130
131
  end
131
-
132
+
132
133
  DefinitionError = Class.new(StandardError)
133
134
  ValidationError = Class.new(StandardError)
134
-
135
+
135
136
  class RouteParameterNotInPath < DefinitionError; end
136
137
  class ReservedParameter < DefinitionError; end
137
138
  class ConflictingParameter < DefinitionError; end
138
-
139
+
139
140
  # Gets raised when a parameter is missing
140
141
  class MissingParameter < ValidationError
141
142
  def initialize(parameter_name)
142
143
  super "Missing parameter :#{parameter_name}"
143
144
  end
144
145
  end
145
-
146
+
146
147
  # Gets raised when a parameter is supplied and has a wrong type
147
148
  class ParameterTypeMismatch < ValidationError
148
149
  def initialize(ac_parameter, received_ruby_type)
@@ -151,7 +152,7 @@ module Apiculture
151
152
  super "Received #{received_type}, expected #{expected_type.inspect} for :#{parameter_name}"
152
153
  end
153
154
  end
154
-
155
+
155
156
  # Returns a Proc that calls the strong parameters to check the presence/types
156
157
  def parametric_validator_proc_from(parametric_validators, implicitly_defined_route_parameter_names)
157
158
  required_params = parametric_validators.select{|e| e.required }
@@ -163,23 +164,23 @@ module Apiculture
163
164
  parametric_validators.each do |param|
164
165
  param_name = param.name_as_string
165
166
  next unless params.has_key?(param_name) # this is checked via required_params
166
-
167
+
167
168
  # Apply the type cast and save it (since using our override we can mutate the params)
168
169
  value_after_type_cast = AC_APPLY_TYPECAST_PROC.call(param.cast_proc_or_method, params[param_name])
169
170
  params[param_name] = value_after_type_cast
170
-
171
+
171
172
  # Ensure the typecast value adheres to the enforced Ruby type
172
173
  AC_CHECK_TYPE_PROC.call(param, params[param_name])
173
174
  end
174
-
175
- # The following only applies if the app does not use strong_parameters -
175
+
176
+ # The following only applies if the app does not use strong_parameters -
176
177
  # this makes use of parameter mutability again to kill the parameters that are not permitted
177
178
  # or mentioned in the API specification. We need to keep the params which are specified in the
178
179
  # route but not documented via Apiculture though
179
180
  unexpected_parameters = Set.new(params.keys.map(&:to_s)) -
180
181
  Set.new(parametric_validators.map(&:name).map(&:to_s)) -
181
182
  Set.new(implicitly_defined_route_parameter_names.map(&:to_s))
182
-
183
+
183
184
  unexpected_parameters.each do | parameter_to_discard |
184
185
  # TODO: raise or record a warning
185
186
  if env['rack.logger'].respond_to?(:warn)
@@ -189,7 +190,7 @@ module Apiculture
189
190
  end
190
191
  }
191
192
  end
192
-
193
+
193
194
  # Serve the documentation for the API at the given URL
194
195
  def serve_api_documentation_at(url)
195
196
  get(url) do
@@ -197,7 +198,7 @@ module Apiculture
197
198
  self.class.api_documentation.to_html
198
199
  end
199
200
  end
200
-
201
+
201
202
  # Returns an +AppDocumentation+ object for all actions defined so far.
202
203
  #
203
204
  # MyApi.api_documentation.to_markdown #=> "..."
@@ -205,14 +206,14 @@ module Apiculture
205
206
  def api_documentation
206
207
  AppDocumentation.new(self, @apiculture_mounted_at.to_s, @apiculture_actions_and_docs || [])
207
208
  end
208
-
209
+
209
210
  # Define an API method. Under the hood will call the related methods in Sinatra
210
211
  # to define the route.
211
212
  def api_method(http_verb, path, options={}, &blk)
212
213
  action_def = (@apiculture_action_definition || ActionDefinition.new)
213
214
  action_def.http_verb = http_verb
214
215
  action_def.path = path
215
-
216
+
216
217
  # Ensure no reserved Sinatra parameters are used
217
218
  all_parameter_names = action_def.all_parameter_names_as_strings
218
219
  %w( splat captures ).each do | reserved_param |
@@ -220,34 +221,34 @@ module Apiculture
220
221
  raise ReservedParameter.new(":#{reserved_param} is a reserved magic parameter name in Sinatra")
221
222
  end
222
223
  end
223
-
224
+
224
225
  # Ensure no conflations between route/req params
225
226
  seen_params = {}
226
- all_parameter_names.each do |e|
227
+ all_parameter_names.each do |e|
227
228
  if seen_params[e]
228
- raise ConflictingParameter.new(":#{e} mentioned twice as a possible parameter. Note that URL" +
229
+ raise ConflictingParameter.new(":#{e} mentioned twice as a possible parameter. Note that URL" +
229
230
  " parameters and request parameters share a namespace.")
230
231
  else
231
232
  seen_params[e] = true
232
233
  end
233
234
  end
234
-
235
+
235
236
  # Ensure the path has the route parameters that were predeclared
236
237
  action_def.route_parameters.map(&:name).each do | route_parameter_key |
237
238
  unless path.include?(':%s' % route_parameter_key)
238
239
  raise RouteParameterNotInPath.new("Parameter :#{route_parameter_key} not present in path #{path.inspect}")
239
240
  end
240
241
  end
241
-
242
+
242
243
  # TODO: ensure all route parameters are documented
243
-
244
+
244
245
  # Pick out all the defined parameters and set up a block that can validate them
245
246
  # when the action is called. With that, set up the actual Sinatra method that will
246
247
  # respond to the request. We take care to preserve all the params that have NOT been documented
247
248
  # using Apiculture but _were_ in fact specified in the actual path.
248
249
  route_parameter_names = path.scan(/:([^:\/]+)/).flatten.map(&:to_sym)
249
250
  parametric_checker_proc = parametric_validator_proc_from(action_def.parameters + action_def.route_parameters, route_parameter_names)
250
- public_send(http_verb, path, options) do |*matched_sinatra_route_params|
251
+ public_send(http_verb, path, **options) do |*matched_sinatra_route_params|
251
252
  # Extract all the parameter names from the route path as given to the method
252
253
  route_parameters = Hash[route_parameter_names.zip(matched_sinatra_route_params)]
253
254
 
@@ -268,13 +269,13 @@ module Apiculture
268
269
  # Execute the original action via instance_exec, passing along the route args
269
270
  instance_exec(*route_parameters.values, &blk)
270
271
  end
271
-
272
+
272
273
  # Reset for the subsequent action definition
273
274
  @apiculture_action_definition = ActionDefinition.new
274
275
  # and store the just defined action for future use
275
276
  apiculture_stack << action_def
276
277
  end
277
-
278
+
278
279
  def apiculture_stack
279
280
  @apiculture_actions_and_docs ||= []
280
281
  @apiculture_actions_and_docs
metadata CHANGED
@@ -1,32 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apiculture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  - WeTransfer
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-01-27 00:00:00.000000000 Z
12
+ date: 2023-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: mustermann
15
+ name: builder
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1'
20
+ version: '3'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1'
27
+ version: '3'
28
28
  - !ruby/object:Gem::Dependency
29
- name: builder
29
+ name: github-markup
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - "~>"
@@ -40,21 +40,21 @@ dependencies:
40
40
  - !ruby/object:Gem::Version
41
41
  version: '3'
42
42
  - !ruby/object:Gem::Dependency
43
- name: rdiscount
43
+ name: mustache
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '2'
48
+ version: '1'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '2'
55
+ version: '1'
56
56
  - !ruby/object:Gem::Dependency
57
- name: github-markup
57
+ name: mustermann
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
@@ -68,61 +68,61 @@ dependencies:
68
68
  - !ruby/object:Gem::Version
69
69
  version: '3'
70
70
  - !ruby/object:Gem::Dependency
71
- name: mustache
71
+ name: rdiscount
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: '1'
76
+ version: '2'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: '1'
83
+ version: '2'
84
84
  - !ruby/object:Gem::Dependency
85
- name: rack-test
85
+ name: bundler
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - ">="
88
+ - - "~>"
89
89
  - !ruby/object:Gem::Version
90
- version: '0'
90
+ version: '2.0'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
- - - ">="
95
+ - - "~>"
96
96
  - !ruby/object:Gem::Version
97
- version: '0'
97
+ version: '2.0'
98
98
  - !ruby/object:Gem::Dependency
99
- name: rspec
99
+ name: cgi
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - "~>"
102
+ - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: '3'
104
+ version: '0'
105
105
  type: :development
106
106
  prerelease: false
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
- - - "~>"
109
+ - - ">="
110
110
  - !ruby/object:Gem::Version
111
- version: '3'
111
+ version: '0'
112
112
  - !ruby/object:Gem::Dependency
113
- name: rdoc
113
+ name: rack-test
114
114
  requirement: !ruby/object:Gem::Requirement
115
115
  requirements:
116
- - - "~>"
116
+ - - ">="
117
117
  - !ruby/object:Gem::Version
118
- version: '6.0'
118
+ version: '0'
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
- - - "~>"
123
+ - - ">="
124
124
  - !ruby/object:Gem::Version
125
- version: '6.0'
125
+ version: '0'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rake
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -138,33 +138,33 @@ dependencies:
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
- name: bundler
141
+ name: rdoc
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
144
  - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: '1.0'
146
+ version: '6.0'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: '1.0'
153
+ version: '6.0'
154
154
  - !ruby/object:Gem::Dependency
155
- name: simplecov
155
+ name: rspec
156
156
  requirement: !ruby/object:Gem::Requirement
157
157
  requirements:
158
- - - ">="
158
+ - - "~>"
159
159
  - !ruby/object:Gem::Version
160
- version: '0'
160
+ version: '3'
161
161
  type: :development
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
- - - ">="
165
+ - - "~>"
166
166
  - !ruby/object:Gem::Version
167
- version: '0'
167
+ version: '3'
168
168
  description: A toolkit for building REST APIs on top of Rack
169
169
  email: me@julik.nl
170
170
  executables: []
@@ -175,6 +175,7 @@ extra_rdoc_files:
175
175
  files:
176
176
  - ".gitignore"
177
177
  - ".travis.yml"
178
+ - CHANGELOG.md
178
179
  - Gemfile
179
180
  - LICENSE.txt
180
181
  - README.md
@@ -191,20 +192,16 @@ files:
191
192
  - lib/apiculture/indifferent_hash.rb
192
193
  - lib/apiculture/markdown_segment.rb
193
194
  - lib/apiculture/method_documentation.rb
195
+ - lib/apiculture/openapi_documentation.rb
194
196
  - lib/apiculture/sinatra_instance_methods.rb
195
197
  - lib/apiculture/timestamp_promise.rb
196
198
  - lib/apiculture/version.rb
197
- - spec/apiculture/action_spec.rb
198
- - spec/apiculture/app_documentation_spec.rb
199
- - spec/apiculture/method_documentation_spec.rb
200
- - spec/apiculture_spec.rb
201
- - spec/spec_helper.rb
202
199
  homepage: https://github.com/WeTransfer/apiculture
203
200
  licenses:
204
201
  - MIT
205
202
  metadata:
206
203
  allowed_push_host: https://rubygems.org
207
- post_install_message:
204
+ post_install_message:
208
205
  rdoc_options: []
209
206
  require_paths:
210
207
  - lib
@@ -219,8 +216,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
216
  - !ruby/object:Gem::Version
220
217
  version: '0'
221
218
  requirements: []
222
- rubygems_version: 3.0.3
223
- signing_key:
219
+ rubygems_version: 3.2.33
220
+ signing_key:
224
221
  specification_version: 4
225
222
  summary: Sweet API sauce on top of Rack
226
223
  test_files: []
@@ -1,45 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe Apiculture::Action do
4
- context '.new' do
5
- it 'exposes the methods of the object given as a first argument to initialize' do
6
- action_class = Class.new(described_class)
7
- fake_app = double('Apiculture::App', something: 'value')
8
- action = action_class.new(fake_app)
9
- expect(action).to respond_to(:something)
10
- expect(action.something).to eq('value')
11
- end
12
-
13
- it 'converts keyword arguments to instance variables' do
14
- action_class = Class.new(described_class)
15
- action = action_class.new(nil, foo: 'a string')
16
- expect(action.instance_variable_get('@foo')).to eq('a string')
17
- end
18
- end
19
-
20
- it 'responds to perform()' do
21
- expect(described_class.new(nil)).to respond_to(:perform)
22
- end
23
-
24
- it 'can use bail() to throw a Sinatra halt' do
25
- fake_app = double('Apiculture::App')
26
- expect(fake_app).to receive(:json_halt).with('Failure', status: 400)
27
- action_class = Class.new(described_class)
28
- action_class.new(fake_app).bail "Failure"
29
- end
30
-
31
- it 'can use bail() to throw a Sinatra halt with a custom status' do
32
- fake_app = double('Apiculture::App')
33
- expect(fake_app).to receive(:json_halt).with("Failure", status: 417)
34
-
35
- action_class = Class.new(described_class)
36
- action_class.new(fake_app).bail "Failure", status: 417
37
- end
38
-
39
- it 'can use bail() to throw a Sinatra halt with extra JSON attributes' do
40
- fake_app = double('Apiculture::App')
41
- expect(fake_app).to receive(:json_halt).with("Failure", status: 417, message: "Totale")
42
- action_class = Class.new(described_class)
43
- action_class.new(fake_app).bail "Failure", status: 417, message: 'Totale'
44
- end
45
- end
@@ -1,126 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe "Apiculture.api_documentation" do
4
- let(:app) do
5
- Class.new(Apiculture::App) do
6
- extend Apiculture
7
-
8
- markdown_string 'This API is very important. Because it has to do with pancakes.'
9
-
10
- documentation_build_time!
11
-
12
- desc 'Order a pancake'
13
- required_param :diameter, "Diameter of the pancake. The pancake will be **bold**", Integer
14
- param :topping, 'Type of topping', String
15
- pancake_response_info = <<~EOS
16
- When the pancake has been baked successfully
17
- The pancake will have the following properties:
18
-
19
- * It is going to be round
20
- * It is going to be delicious
21
- EOS
22
- responds_with 200, pancake_response_info, { id: 'abdef..c21' }
23
- api_method :post, '/pancakes' do
24
- end
25
-
26
- desc 'Check the pancake status'
27
- route_param :id, 'Pancake ID to check status on'
28
- responds_with 200, 'When the pancake is found', { status: 'Baking' }
29
- responds_with 404, 'When no such pancake exists', { status: 'No such pancake' }
30
- api_method :get, '/pancake/:id' do
31
- end
32
-
33
- desc 'Throw away the pancake'
34
- route_param :id, 'Pancake ID to delete'
35
- api_method :delete, '/pancake/:id' do
36
- end
37
-
38
- desc 'Pancake ingredients are in the URL'
39
- route_param :topping_id, 'Pancake topping ID', Integer, cast: :to_i
40
- api_method :get, '/pancake/with/:topping_id' do |topping_id|
41
- end
42
- end
43
- end
44
-
45
- it 'generates app documentation as HTML without the body element' do
46
- docco = app.api_documentation
47
- generated_html = docco.to_html_fragment
48
-
49
- expect(generated_html).not_to include('<body')
50
- expect(generated_html).to include('Pancake ID to check status on')
51
- expect(generated_html).to include('Pancake ID to delete')
52
- end
53
-
54
- it 'generates app documentation in HTML' do
55
- docco = app.api_documentation
56
- generated_html = docco.to_html
57
-
58
- if ENV['SHOW_TEST_DOC']
59
- File.open('t.html', 'w') do |f|
60
- f.write(generated_html)
61
- f.flush
62
- `open #{f.path}`
63
- end
64
- end
65
-
66
- expect(generated_html).to include('<body')
67
- expect(generated_html).to include('Pancake ID to check status on')
68
- expect(generated_html).to include('When the pancake has been baked successfully')
69
- expect(generated_html).to include('"id": "abdef..c21"')
70
- expect(generated_html).to end_with("\n")
71
- end
72
-
73
- it 'generates app documentation in Markdown' do
74
- docco = app.api_documentation
75
- generated_markdown = docco.to_markdown
76
-
77
- expect(generated_markdown).not_to include('<body')
78
- expect(generated_markdown).to include('## POST /pancakes')
79
- end
80
-
81
- it 'generates app documentation honoring the mount point' do
82
- overridden = Class.new(Apiculture::App) do
83
- extend Apiculture
84
- mounted_at '/api/v2/'
85
- api_method :get, '/pancakes' do
86
- end
87
- end
88
-
89
- generated_markdown = overridden.api_documentation.to_markdown
90
- expect(generated_markdown).to include('## GET /api/v2/pancakes')
91
- end
92
-
93
- it 'generates app documentation injecting the inline Markdown strings' do
94
- app_class = Class.new(Apiculture::App) do
95
- extend Apiculture
96
- markdown_string '# This describes important stuff'
97
- api_method :get, '/pancakes' do
98
- end
99
- markdown_string '# This describes even more important stuff'
100
- markdown_string 'This is a paragraph'
101
- end
102
-
103
- generated_html = app_class.api_documentation.to_html
104
- expect(generated_html).to include('<h2>GET /pancakes</h2>')
105
- expect(generated_html).to include('<h1>This describes even more important stuff')
106
- expect(generated_html).to include('<h1>This describes important stuff')
107
- expect(generated_html).to include('<p>This is a paragraph')
108
- end
109
-
110
- context 'with a file containing Markdown that has to be spliced into the docs' do
111
- before(:each) { File.open('./TEST.md', 'w') { |f| f << "# This is an important header" } }
112
- after(:each) { File.unlink('./TEST.md') }
113
- it 'splices the contents of the file using markdown_file' do
114
- app_class = Class.new(Apiculture::App) do
115
- extend Apiculture
116
- markdown_file './TEST.md'
117
- api_method :get, '/pancakes' do
118
- end
119
- end
120
-
121
- generated_html = app_class.api_documentation.to_html
122
- expect(generated_html).to include('<h2>GET /pancakes</h2>')
123
- expect(generated_html).to include('<h1>This is an important header')
124
- end
125
- end
126
- end