grape-swagger 0.7.2 → 0.8.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rubocop.yml +36 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +3 -0
  7. data/CHANGELOG.md +90 -0
  8. data/CONTRIBUTING.md +126 -0
  9. data/Gemfile +1 -21
  10. data/LICENSE.txt +1 -1
  11. data/README.md +397 -0
  12. data/RELEASING.md +80 -0
  13. data/Rakefile +6 -23
  14. data/UPGRADING.md +47 -0
  15. data/grape-swagger.gemspec +26 -84
  16. data/lib/grape-swagger.rb +237 -111
  17. data/lib/grape-swagger/errors.rb +9 -0
  18. data/lib/grape-swagger/markdown.rb +23 -0
  19. data/lib/grape-swagger/markdown/kramdown_adapter.rb +37 -0
  20. data/lib/grape-swagger/markdown/redcarpet_adapter.rb +89 -0
  21. data/lib/grape-swagger/version.rb +3 -0
  22. data/spec/api_description_spec.rb +41 -0
  23. data/spec/api_global_models_spec.rb +76 -0
  24. data/spec/api_models_spec.rb +190 -93
  25. data/spec/default_api_spec.rb +31 -36
  26. data/spec/form_params_spec.rb +56 -53
  27. data/spec/grape-swagger_helper_spec.rb +88 -49
  28. data/spec/grape-swagger_spec.rb +7 -5
  29. data/spec/hide_api_spec.rb +58 -55
  30. data/spec/markdown/kramdown_adapter_spec.rb +38 -0
  31. data/spec/markdown/markdown_spec.rb +27 -0
  32. data/spec/markdown/redcarpet_adapter_spec.rb +81 -0
  33. data/spec/namespaced_api_spec.rb +47 -0
  34. data/spec/non_default_api_spec.rb +372 -222
  35. data/spec/response_model_spec.rb +80 -0
  36. data/spec/simple_mounted_api_spec.rb +113 -118
  37. data/spec/spec_helper.rb +0 -1
  38. data/spec/version_spec.rb +8 -0
  39. data/test/api.rb +62 -0
  40. data/test/config.ru +10 -2
  41. data/test/splines.png +0 -0
  42. metadata +145 -91
  43. data/.rvmrc +0 -48
  44. data/CHANGELOG.markdown +0 -55
  45. data/Gemfile.lock +0 -94
  46. data/README.markdown +0 -168
  47. data/VERSION +0 -1
  48. data/test/nested_api.rb +0 -30
@@ -0,0 +1,9 @@
1
+ module GrapeSwagger
2
+ module Errors
3
+ class MarkdownDependencyMissingError < StandardError
4
+ def initialize(missing_gem)
5
+ super("Missing required dependency: #{missing_gem}")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module GrapeSwagger
2
+ class Markdown
3
+ attr_reader :adapter
4
+
5
+ ###
6
+ # Initializes the markdown class with an adapter.
7
+ # The adapter needs to implement the method markdown which will be called by this interface class.
8
+ # The adapters are responsible of loading the required markdown dependencies and throw errors.
9
+ ###
10
+ def initialize(adapter)
11
+ adapter = adapter.new if adapter.is_a?(Class)
12
+ fail(ArgumentError, "The configured markdown adapter should implement the method #{ :markdown }") unless adapter.respond_to? :markdown
13
+ @adapter = adapter
14
+ end
15
+
16
+ ###
17
+ # Calls markdown to the configured adapter.
18
+ ###
19
+ def as_markdown(text)
20
+ @adapter.markdown(text)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module GrapeSwagger
2
+ class Markdown
3
+ class KramdownAdapter
4
+ attr_reader :options
5
+
6
+ ###
7
+ # Initializes the kramdown adapter with options.
8
+ # See kramdown documentation what options can be passed.
9
+ # Default it uses Github flavoured markup as input and won't use coderay as converter for syntax highlighting.
10
+ # config: an hash of configuration options to be passed to the kramdown.
11
+ # usage:
12
+ # Add the kramdown gem to your gemfile or run:
13
+ # $ (sudo) gem install kramdown
14
+ #
15
+ # Then pass a new instance of GrapeSwagger::Markdown::KramdownAdapter as markdown option.
16
+ ###
17
+ def initialize(config = {})
18
+ require 'kramdown'
19
+ defaults = {
20
+ input: 'GFM',
21
+ enable_coderay: false
22
+ }
23
+ @options = defaults.merge(config)
24
+ rescue LoadError
25
+ raise GrapeSwagger::Errors::MarkdownDependencyMissingError, 'kramdown'
26
+ end
27
+
28
+ ###
29
+ # marks down the given text to html format.
30
+ # text: The text to be formatted.
31
+ ###
32
+ def markdown(text)
33
+ Kramdown::Document.new(text, @options).to_html
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,89 @@
1
+ module GrapeSwagger
2
+ class Markdown
3
+ class RedcarpetAdapter
4
+ module RenderWithoutSyntaxHighlighter
5
+ require 'cgi'
6
+
7
+ def block_code(code, language)
8
+ language ||= 'text'
9
+ "<div class=\"code_highlight\"><pre><code class=\"highlight #{language}\">#{CGI.escapeHTML(code)}</code></pre></div>"
10
+ end
11
+ end
12
+
13
+ attr_reader :extension_options
14
+
15
+ attr_reader :render_options
16
+
17
+ ###
18
+ # Initializes the redcarpet adapter with markup options.
19
+ # See redcarpet documentation what options can be passed.
20
+ # Default it uses fenced_code_blocks, autolinks and rouge as syntax highlighter.
21
+ # To configure an highlighter add {highlighter: :value} to the extentions hash.
22
+ # Currently supported highlighters:
23
+ # :rouge
24
+ #
25
+ # extensions: an hash of configuration options to be passed to markdown.
26
+ # render_options: an hash of configuration options to be passed to renderer.
27
+ #
28
+ # usage:
29
+ # Add the redcarpet gem to your gemfile or run:
30
+ # $ (sudo) gem install redcarpet
31
+ # when you want to have rouge as syntax highlighter add rouge to the gemfile or run:
32
+ # $ (sudo) gem install rouge
33
+ #
34
+ # GrapeSwagger::Markdown::RedcarpetAdapter.new({highlighter: :none},{no_links: true}) # will use no syntax highlighter and won't render links.
35
+ ###
36
+ def initialize(options = {})
37
+ require 'redcarpet'
38
+ extentions_defaults = {
39
+ fenced_code_blocks: true,
40
+ autolink: true
41
+ }
42
+ render_defaults = { highlighter: :rouge }
43
+ @extension_options = extentions_defaults.merge(options.fetch(:extensions, {}))
44
+ @render_options = render_defaults.merge(options.fetch(:render_options, {}))
45
+ @renderer = new_redcarpet_renderer(@render_options.delete(:highlighter)).new(@render_options)
46
+ @markdown = Redcarpet::Markdown.new(@renderer, @extension_options)
47
+ rescue LoadError
48
+ raise GrapeSwagger::Errors::MarkdownDependencyMissingError, 'redcarpet'
49
+ end
50
+
51
+ ###
52
+ # Marks down the given text to html format.
53
+ ###
54
+ def markdown(text)
55
+ @markdown.render(text)
56
+ end
57
+
58
+ private
59
+
60
+ ###
61
+ # Creates a new redcarpet renderer based on the highlighter given.
62
+ #
63
+ # render_options: options passed to the renderer.
64
+ #
65
+ # usage:
66
+ # new_redcarpet_renderer(:rouge) # uses rouge as highlighter.
67
+ # new_redcarpet_renderer # no highlight plugin
68
+ ###
69
+ def new_redcarpet_renderer(syntax_highlighter = nil)
70
+ case syntax_highlighter
71
+ when :rouge
72
+ begin
73
+ Class.new(Redcarpet::Render::HTML) do
74
+ require 'rouge'
75
+ require 'rouge/plugins/redcarpet'
76
+ include Rouge::Plugins::Redcarpet
77
+ end
78
+ rescue LoadError
79
+ raise GrapeSwagger::Errors::MarkdownDependencyMissingError, 'rouge'
80
+ end
81
+ else
82
+ Class.new(Redcarpet::Render::HTML) do
83
+ include RenderWithoutSyntaxHighlighter
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module GrapeSwagger
2
+ VERSION = '0.8.0'
3
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'API Description' do
4
+ context 'with no additional options' do
5
+ subject do
6
+ Class.new(Grape::API) do
7
+ add_swagger_documentation
8
+ end
9
+ end
10
+
11
+ it 'describes the API with defaults' do
12
+ routes = subject.endpoints.first.routes
13
+ expect(routes.count).to eq 2
14
+ expect(routes.first.route_description).to eq 'Swagger compatible API description'
15
+ expect(routes.first.route_params).to eq({})
16
+ expect(routes.last.route_description).to eq 'Swagger compatible API description for specific API'
17
+ expect(routes.last.route_params).to eq('name' => { desc: 'Resource name of mounted API', type: 'string', required: true })
18
+ end
19
+ end
20
+
21
+ context 'with additional options' do
22
+ subject do
23
+ Class.new(Grape::API) do
24
+ add_swagger_documentation \
25
+ api_documentation: { desc: 'First', params: { x: 1 }, xx: 11 },
26
+ specific_api_documentation: { desc: 'Second', params: { y: 42 }, yy: 4242 }
27
+ end
28
+ end
29
+
30
+ it 'describes the API with defaults' do
31
+ routes = subject.endpoints.first.routes
32
+ expect(routes.count).to eq 2
33
+ expect(routes.first.route_description).to eq 'First'
34
+ expect(routes.first.route_params).to eq(x: 1)
35
+ expect(routes.first.route_xx).to eq(11)
36
+ expect(routes.last.route_description).to eq 'Second'
37
+ expect(routes.last.route_params).to eq('name' => { desc: 'Resource name of mounted API', type: 'string', required: true }, y: 42)
38
+ expect(routes.last.route_yy).to eq(4242)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Global Models' do
4
+
5
+ before :all do
6
+ module Entities
7
+ module Some
8
+ class Thing < Grape::Entity
9
+ expose :text, documentation: { type: 'string', desc: 'Content of something.' }
10
+ end
11
+
12
+ class CombinedThing < Grape::Entity
13
+ expose :text, documentation: { type: 'string', desc: 'Content of something.' }
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ subject do
20
+ Class.new(Grape::API) do
21
+ desc 'This gets thing.', params: Entities::Some::Thing.documentation
22
+ get '/thing' do
23
+ thing = OpenStruct.new text: 'thing'
24
+ present thing, with: Entities::Some::Thing
25
+ end
26
+
27
+ desc 'This gets combined thing.',
28
+ params: Entities::Some::CombinedThing.documentation,
29
+ entity: Entities::Some::CombinedThing
30
+ get '/combined_thing' do
31
+ thing = OpenStruct.new text: 'thing'
32
+ present thing, with: Entities::Some::CombinedThing
33
+ end
34
+
35
+ add_swagger_documentation models: [Entities::Some::Thing]
36
+ end
37
+ end
38
+
39
+ def app
40
+ subject
41
+ end
42
+
43
+ it 'includes models specified' do
44
+ get '/swagger_doc/thing.json'
45
+ json = JSON.parse(last_response.body)
46
+ expect(json['models']).to eq(
47
+ 'Some::Thing' => {
48
+ 'id' => 'Some::Thing',
49
+ 'properties' => {
50
+ 'text' => { 'type' => 'string', 'description' => 'Content of something.' }
51
+ }
52
+ })
53
+ end
54
+
55
+ it 'uses global models and route endpoint specific entities together' do
56
+ get '/swagger_doc/combined_thing.json'
57
+ json = JSON.parse(last_response.body)
58
+
59
+ expect(json['models']).to include(
60
+ 'Some::Thing' => {
61
+ 'id' => 'Some::Thing',
62
+ 'properties' => {
63
+ 'text' => { 'type' => 'string', 'description' => 'Content of something.' }
64
+ }
65
+ })
66
+
67
+ expect(json['models']).to include(
68
+ 'Some::CombinedThing' => {
69
+ 'id' => 'Some::CombinedThing',
70
+ 'properties' => {
71
+ 'text' => { 'type' => 'string', 'description' => 'Content of something.' }
72
+ }
73
+ })
74
+
75
+ end
76
+ end
@@ -1,132 +1,229 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "API Models" do
3
+ describe 'API Models' do
4
4
 
5
5
  before :all do
6
6
  module Entities
7
7
  class Something < Grape::Entity
8
- expose :text, :documentation => { :type => "string", :desc => "Content of something." }
8
+ expose :text, documentation: { type: 'string', desc: 'Content of something.' }
9
+ end
10
+ end
11
+
12
+ module Entities
13
+ class EnumValues < Grape::Entity
14
+ expose :gender, documentation: { type: 'string', desc: 'Content of something.', values: %w(Male Female) }
15
+ expose :number, documentation: { type: 'integer', desc: 'Content of something.', values: proc { [1, 2] } }
9
16
  end
10
17
  end
11
18
 
12
19
  module Entities
13
20
  module Some
14
21
  class Thing < Grape::Entity
15
- expose :text, :documentation => { :type => "string", :desc => "Content of something." }
22
+ expose :text, documentation: { type: 'string', desc: 'Content of something.' }
23
+ end
24
+ end
25
+ end
26
+
27
+ module Entities
28
+ class ComposedOf < Grape::Entity
29
+ expose :part_text, documentation: { type: 'string', desc: 'Content of composedof.' }
30
+ end
31
+
32
+ class ComposedOfElse < Grape::Entity
33
+ def self.entity_name
34
+ 'composed'
16
35
  end
36
+ expose :part_text, documentation: { type: 'string', desc: 'Content of composedof else.' }
37
+ end
38
+
39
+ class SomeThingElse < Grape::Entity
40
+ expose :else_text, documentation: { type: 'string', desc: 'Content of something else.' }
41
+ expose :parts, using: Entities::ComposedOf, documentation: { type: 'ComposedOf',
42
+ is_array: true,
43
+ required: true }
44
+
45
+ expose :part, using: Entities::ComposedOfElse, documentation: { type: 'composes' }
46
+ end
47
+ end
48
+
49
+ module Entities
50
+ class AliasedThing < Grape::Entity
51
+ expose :something, as: :post, using: Entities::Something, documentation: { type: 'Something', desc: 'Reference to something.' }
17
52
  end
18
53
  end
54
+ end
19
55
 
20
- class ModelsApi < Grape::API
56
+ def app
57
+ Class.new(Grape::API) do
21
58
  format :json
22
- desc 'This gets something.', {
23
- entity: Entities::Something
24
- }
59
+ desc 'This gets something.', entity: Entities::Something
60
+
25
61
  get '/something' do
26
62
  something = OpenStruct.new text: 'something'
27
63
  present something, with: Entities::Something
28
64
  end
29
65
 
30
- desc 'This gets thing.', {
31
- entity: Entities::Some::Thing
32
- }
33
- get "/thing" do
66
+ desc 'This gets thing.', entity: Entities::Some::Thing
67
+ get '/thing' do
34
68
  thing = OpenStruct.new text: 'thing'
35
69
  present thing, with: Entities::Some::Thing
36
70
  end
71
+
72
+ desc 'This gets somthing else.', entity: Entities::SomeThingElse
73
+ get '/somethingelse' do
74
+ part = OpenStruct.new part_text: 'part thing'
75
+ thing = OpenStruct.new else_text: 'else thing', parts: [part], part: part
76
+
77
+ present thing, with: Entities::SomeThingElse
78
+ end
79
+
80
+ desc 'This tests the enum values in params and documentation.', entity: Entities::EnumValues, params: Entities::EnumValues.documentation
81
+ get '/enum_description_in_entity' do
82
+
83
+ enum_value = OpenStruct.new gender: 'Male', number: 1
84
+
85
+ present enum_value, with: Entities::EnumValues
86
+ end
87
+
88
+ desc 'This gets an aliased thing.', entity: Entities::AliasedThing
89
+ get '/aliasedthing' do
90
+ something = OpenStruct.new(something: OpenStruct.new(text: 'something'))
91
+ present something, with: Entities::AliasedThing
92
+ end
93
+
37
94
  add_swagger_documentation
38
95
  end
39
96
  end
40
97
 
41
- def app; ModelsApi; end
42
-
43
- it "should document specified models" do
44
- get '/swagger_doc'
45
- JSON.parse(last_response.body).should == {
46
- "apiVersion" => "0.1",
47
- "swaggerVersion" => "1.2",
48
- "basePath" => "http://example.org",
49
- "info" => {},
50
- "produces" => ["application/json"],
51
- "operations" => [],
52
- "apis" => [
53
- { "path" => "/swagger_doc/something.{format}" },
54
- { "path" => "/swagger_doc/thing.{format}" },
55
- { "path" => "/swagger_doc/swagger_doc.{format}" }
98
+ context 'swagger_doc' do
99
+ subject do
100
+ get '/swagger_doc'
101
+ JSON.parse(last_response.body)
102
+ end
103
+
104
+ it 'returns a swagger-compatible doc' do
105
+ expect(subject).to include(
106
+ 'apiVersion' => '0.1',
107
+ 'swaggerVersion' => '1.2',
108
+ 'info' => {},
109
+ 'produces' => ['application/json']
110
+ )
111
+ end
112
+
113
+ it 'documents apis' do
114
+ expect(subject['apis']).to eq [
115
+ { 'path' => '/something.{format}', 'description' => 'Operations about somethings' },
116
+ { 'path' => '/thing.{format}', 'description' => 'Operations about things' },
117
+ { 'path' => '/somethingelse.{format}', 'description' => 'Operations about somethingelses' },
118
+ { 'path' => '/enum_description_in_entity.{format}', 'description' => 'Operations about enum_description_in_entities' },
119
+ { 'path' => '/aliasedthing.{format}', 'description' => 'Operations about aliasedthings' },
120
+ { 'path' => '/swagger_doc.{format}', 'description' => 'Operations about swagger_docs' }
56
121
  ]
57
- }
122
+ end
58
123
  end
59
124
 
60
- it "should include type when specified" do
125
+ it 'returns type' do
61
126
  get '/swagger_doc/something.json'
62
- JSON.parse(last_response.body).should == {
63
- "apiVersion" => "0.1",
64
- "swaggerVersion" => "1.2",
65
- "basePath" => "http://example.org",
66
- "resourcePath" => "",
67
- "apis" => [{
68
- "path" => "/something.{format}",
69
- "operations" => [{
70
- "produces" => [
71
- "application/json"
72
- ],
73
- "notes" => "",
74
- "type" => "Something",
75
- "summary" => "This gets something.",
76
- "nickname" => "GET-something---format-",
77
- "httpMethod" => "GET",
78
- "parameters" => []
79
- }]
80
- }],
81
- "models" => {
82
- "Something" => {
83
- "id" => "Something",
84
- "name" => "Something",
85
- "properties" => {
86
- "text" => {
87
- "type" => "string",
88
- "description" => "Content of something."
89
- }
90
- }
91
- }
92
- }
93
- }
127
+ result = JSON.parse(last_response.body)
128
+ expect(result['apis'].first['operations'].first['type']).to eq 'Something'
94
129
  end
95
130
 
96
- it "should include nested type when specified" do
131
+ it 'includes nested type' do
97
132
  get '/swagger_doc/thing.json'
98
- JSON.parse(last_response.body).should == {
99
- "apiVersion" => "0.1",
100
- "swaggerVersion" => "1.2",
101
- "basePath" => "http://example.org",
102
- "resourcePath" => "",
103
- "apis" => [{
104
- "path" => "/thing.{format}",
105
- "operations" => [{
106
- "produces" => [
107
- "application/json"
108
- ],
109
- "notes" => "",
110
- "type" => "Some::Thing",
111
- "summary" => "This gets thing.",
112
- "nickname" => "GET-thing---format-",
113
- "httpMethod" => "GET",
114
- "parameters" => []
115
- }]
116
- }],
117
- "models" => {
118
- "Some::Thing" => {
119
- "id" => "Some::Thing",
120
- "name" => "Some::Thing",
121
- "properties" => {
122
- "text" => {
123
- "type" => "string",
124
- "description" => "Content of something."
125
- }
126
- }
127
- }
128
- }
129
- }
133
+ result = JSON.parse(last_response.body)
134
+ expect(result['apis'].first['operations'].first['type']).to eq 'Some::Thing'
135
+ end
136
+
137
+ it 'includes entities which are only used as composition' do
138
+ get '/swagger_doc/somethingelse.json'
139
+ result = JSON.parse(last_response.body)
140
+ expect(result['apis']).to eq([{
141
+ 'path' => '/somethingelse.{format}',
142
+ 'operations' => [{
143
+ 'notes' => '',
144
+ 'type' => 'SomeThingElse',
145
+ 'summary' => 'This gets somthing else.',
146
+ 'nickname' => 'GET-somethingelse---format-',
147
+ 'method' => 'GET',
148
+ 'parameters' => []
149
+ }]
150
+ }])
151
+
152
+ expect(result['models']['SomeThingElse']).to include('id' => 'SomeThingElse',
153
+ 'properties' => {
154
+ 'else_text' => {
155
+ 'type' => 'string',
156
+ 'description' => 'Content of something else.'
157
+ },
158
+ 'parts' => {
159
+ 'type' => 'array',
160
+ 'items' => { '$ref' => 'ComposedOf' }
161
+ },
162
+ 'part' => { '$ref' => 'composes' }
163
+ },
164
+ 'required' => ['parts']
165
+
166
+ )
167
+
168
+ expect(result['models']['ComposedOf']).to include(
169
+ 'id' => 'ComposedOf',
170
+ 'properties' => {
171
+ 'part_text' => {
172
+ 'type' => 'string',
173
+ 'description' => 'Content of composedof.'
174
+ }
175
+ }
176
+ )
177
+
178
+ expect(result['models']['composed']).to include(
179
+ 'id' => 'composed',
180
+ 'properties' => {
181
+ 'part_text' => {
182
+ 'type' => 'string',
183
+ 'description' => 'Content of composedof else.'
184
+ }
185
+
186
+ }
187
+ )
130
188
  end
131
189
 
190
+ it 'includes enum values in params and documentation.' do
191
+ get '/swagger_doc/enum_description_in_entity.json'
192
+ result = JSON.parse(last_response.body)
193
+ expect(result['models']['EnumValues']).to eq(
194
+ 'id' => 'EnumValues',
195
+ 'properties' => {
196
+ 'gender' => { 'type' => 'string', 'description' => 'Content of something.', 'enum' => %w(Male Female) },
197
+ 'number' => { 'type' => 'integer', 'description' => 'Content of something.', 'enum' => [1, 2] }
198
+ }
199
+ )
200
+
201
+ expect(result['apis'][0]['operations'][0]).to include(
202
+ 'parameters' =>
203
+ [
204
+ { 'paramType' => 'query', 'name' => 'gender', 'description' => 'Content of something.', 'type' => 'string', 'required' => false, 'allowMultiple' => false, 'enum' => %w(Male Female) },
205
+ { 'paramType' => 'query', 'name' => 'number', 'description' => 'Content of something.', 'type' => 'integer', 'required' => false, 'allowMultiple' => false, 'format' => 'int32', 'enum' => [1, 2] }
206
+ ],
207
+ 'type' => 'EnumValues'
208
+ )
209
+
210
+ end
211
+
212
+ it 'includes referenced models in those with aliased references.' do
213
+ get '/swagger_doc/aliasedthing.json'
214
+ result = JSON.parse(last_response.body)
215
+ expect(result['models']['AliasedThing']).to eq(
216
+ 'id' => 'AliasedThing',
217
+ 'properties' => {
218
+ 'post' => { '$ref' => 'Something', 'description' => 'Reference to something.' }
219
+ }
220
+ )
221
+
222
+ expect(result['models']['Something']).to eq(
223
+ 'id' => 'Something',
224
+ 'properties' => {
225
+ 'text' => { 'type' => 'string', 'description' => 'Content of something.' }
226
+ }
227
+ )
228
+ end
132
229
  end