praxis 0.13.0 → 0.14.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +15 -2
  4. data/CHANGELOG.md +54 -1
  5. data/bin/praxis +49 -2
  6. data/lib/api_browser/Gruntfile.js +247 -90
  7. data/lib/api_browser/app/bower_components/angular-mocks/.bower.json +19 -0
  8. data/lib/api_browser/app/bower_components/angular-mocks/README.md +57 -0
  9. data/lib/api_browser/app/bower_components/angular-mocks/angular-mocks.js +2193 -0
  10. data/lib/api_browser/app/bower_components/angular-mocks/bower.json +9 -0
  11. data/lib/api_browser/app/bower_components/angular-mocks/package.json +27 -0
  12. data/lib/api_browser/app/bower_components/angular/.bower.json +6 -5
  13. data/lib/api_browser/app/bower_components/angular/README.md +23 -4
  14. data/lib/api_browser/app/bower_components/angular/angular-csp.css +6 -0
  15. data/lib/api_browser/app/bower_components/angular/angular.js +2287 -1597
  16. data/lib/api_browser/app/bower_components/angular/angular.min.js +212 -205
  17. data/lib/api_browser/app/bower_components/angular/angular.min.js.gzip +0 -0
  18. data/lib/api_browser/app/bower_components/angular/angular.min.js.map +3 -3
  19. data/lib/api_browser/app/bower_components/angular/bower.json +2 -1
  20. data/lib/api_browser/app/bower_components/angular/package.json +25 -0
  21. data/lib/api_browser/app/bower_components/showdown/.bower.json +39 -0
  22. data/lib/api_browser/app/bower_components/showdown/.jshintignore +2 -0
  23. data/lib/api_browser/app/bower_components/showdown/.travis.yml +8 -0
  24. data/lib/api_browser/app/bower_components/showdown/Gruntfile.js +100 -0
  25. data/lib/api_browser/app/bower_components/showdown/README.md +317 -0
  26. data/lib/api_browser/app/bower_components/showdown/bower.json +26 -0
  27. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js +1606 -0
  28. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js.map +1 -0
  29. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.min.js +2 -0
  30. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js +2 -0
  31. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js.map +1 -0
  32. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js +2 -0
  33. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js.map +1 -0
  34. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js +2 -0
  35. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js.map +1 -0
  36. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js +2 -0
  37. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js.map +1 -0
  38. data/lib/api_browser/app/bower_components/showdown/license.txt +34 -0
  39. data/lib/api_browser/app/bower_components/showdown/package.json +47 -0
  40. data/lib/api_browser/app/bower_components/showdown/src/extensions/github.js +25 -0
  41. data/lib/api_browser/app/bower_components/showdown/src/extensions/prettify.js +29 -0
  42. data/lib/api_browser/app/bower_components/showdown/src/extensions/table.js +106 -0
  43. data/lib/api_browser/app/bower_components/showdown/src/extensions/twitter.js +42 -0
  44. data/lib/api_browser/app/bower_components/showdown/src/ng-showdown.js +150 -0
  45. data/lib/api_browser/app/bower_components/showdown/src/showdown.js +1454 -0
  46. data/lib/api_browser/app/index.html +6 -4
  47. data/lib/api_browser/app/js/app.js +1 -2
  48. data/lib/api_browser/app/js/controllers/action.js +4 -4
  49. data/lib/api_browser/app/js/controllers/controller.js +1 -1
  50. data/lib/api_browser/app/js/controllers/menu.js +5 -3
  51. data/lib/api_browser/app/js/controllers/type.js +5 -5
  52. data/lib/api_browser/app/js/directives/attribute_description.js +5 -5
  53. data/lib/api_browser/app/js/directives/attribute_table.js +1 -1
  54. data/lib/api_browser/app/js/directives/attribute_table_row.js +2 -2
  55. data/lib/api_browser/app/js/directives/no_container.js +1 -1
  56. data/lib/api_browser/app/js/directives/request_body.js +5 -5
  57. data/lib/api_browser/app/js/directives/request_headers.js +3 -6
  58. data/lib/api_browser/app/js/directives/request_parameters.js +3 -6
  59. data/lib/api_browser/app/js/directives/type_label.js +4 -5
  60. data/lib/api_browser/app/js/factories/Documentation.js +4 -4
  61. data/lib/api_browser/app/js/factories/PayloadTemplates.js +2 -2
  62. data/lib/api_browser/app/js/factories/TypeTemplates.js +3 -3
  63. data/lib/api_browser/app/js/filters/markdown.js +6 -0
  64. data/lib/api_browser/app/js/filters/resource_name.js +2 -2
  65. data/lib/api_browser/app/sass/modules/_header.scss +2 -7
  66. data/lib/api_browser/app/sass/{main.scss → praxis.scss} +0 -0
  67. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +370 -367
  68. data/lib/api_browser/app/views/action.html +2 -2
  69. data/lib/api_browser/app/views/controller.html +2 -2
  70. data/lib/api_browser/app/views/directives/attribute_description.html +1 -1
  71. data/lib/api_browser/app/views/layout.html +2 -11
  72. data/lib/api_browser/app/views/navbar.html +9 -0
  73. data/lib/api_browser/app/views/resource/_actions.html +1 -1
  74. data/lib/api_browser/app/views/type.html +2 -2
  75. data/lib/api_browser/app/views/type/_details.html +2 -1
  76. data/lib/api_browser/bower.json +5 -0
  77. data/lib/api_browser/package.json +18 -7
  78. data/lib/praxis.rb +8 -3
  79. data/lib/praxis/action_definition.rb +28 -6
  80. data/lib/praxis/api_definition.rb +30 -2
  81. data/lib/praxis/api_general_info.rb +36 -0
  82. data/lib/praxis/bootloader.rb +1 -0
  83. data/lib/praxis/collection.rb +34 -0
  84. data/lib/praxis/controller.rb +7 -0
  85. data/lib/praxis/dispatcher.rb +3 -0
  86. data/lib/praxis/links.rb +2 -8
  87. data/lib/praxis/media_type.rb +6 -24
  88. data/lib/praxis/media_type_collection.rb +6 -2
  89. data/lib/praxis/plugin_concern.rb +2 -1
  90. data/lib/praxis/request.rb +24 -15
  91. data/lib/praxis/request_stages/request_stage.rb +19 -4
  92. data/lib/praxis/request_stages/validate_params_and_headers.rb +1 -1
  93. data/lib/praxis/request_stages/validate_payload.rb +1 -1
  94. data/lib/praxis/resource_definition.rb +45 -10
  95. data/lib/praxis/response_definition.rb +46 -27
  96. data/lib/praxis/restful_doc_generator.rb +94 -7
  97. data/lib/praxis/simple_media_type.rb +2 -9
  98. data/lib/praxis/stage.rb +1 -4
  99. data/lib/praxis/tasks/api_docs.rb +51 -19
  100. data/lib/praxis/tasks/routes.rb +19 -15
  101. data/lib/praxis/types/media_type_common.rb +31 -0
  102. data/lib/praxis/types/multipart.rb +4 -4
  103. data/lib/praxis/version.rb +1 -1
  104. data/praxis.gemspec +2 -2
  105. data/spec/api_browser/factories/documentation_spec.js +50 -0
  106. data/spec/api_browser/filters/attribute_name_spec.js +23 -0
  107. data/spec/functional_spec.rb +62 -10
  108. data/spec/praxis/action_definition_spec.rb +12 -4
  109. data/spec/praxis/api_definition_spec.rb +159 -0
  110. data/spec/praxis/api_general_info_spec.rb +36 -0
  111. data/spec/praxis/bootloader_spec.rb +10 -1
  112. data/spec/praxis/media_type_collection_spec.rb +46 -53
  113. data/spec/praxis/media_type_spec.rb +6 -6
  114. data/spec/praxis/request_stage_spec.rb +7 -2
  115. data/spec/praxis/request_stages_validate_spec.rb +12 -7
  116. data/spec/praxis/resource_definition_spec.rb +62 -0
  117. data/spec/praxis/response_definition_spec.rb +26 -16
  118. data/spec/praxis/stage_spec.rb +4 -8
  119. data/spec/praxis/types/collection_spec.rb +144 -0
  120. data/spec/spec_app/app/controllers/instances.rb +8 -2
  121. data/spec/spec_app/design/api.rb +11 -0
  122. data/spec/spec_app/design/media_types/instance.rb +12 -0
  123. data/spec/spec_app/design/media_types/volume.rb +9 -2
  124. data/spec/spec_app/design/media_types/volume_snapshot.rb +9 -6
  125. data/spec/spec_app/design/resources/instances.rb +25 -10
  126. data/spec/support/spec_media_types.rb +1 -1
  127. data/spec/support/spec_resource_definitions.rb +2 -0
  128. data/tasks/thor/app.rb +15 -10
  129. data/tasks/thor/example.rb +115 -115
  130. data/tasks/thor/templates/generator/empty_app/.gitignore +2 -0
  131. data/tasks/thor/templates/generator/empty_app/docs/app.js +1 -0
  132. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +3 -0
  133. metadata +50 -9
  134. data/lib/api_browser/app/css/main.css +0 -4511
  135. data/lib/praxis/types/collection.rb +0 -17
@@ -6,7 +6,7 @@ module Praxis
6
6
  end
7
7
 
8
8
  @inspected_types = Set.new
9
- API_DOCS_DIRNAME = 'api_docs'
9
+ API_DOCS_DIRNAME = 'docs/api'
10
10
 
11
11
  EXCLUDED_TYPES_FROM_TOP_LEVEL = Set.new([
12
12
  Attributor::Boolean,
@@ -81,12 +81,25 @@ module Praxis
81
81
 
82
82
  add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.params)
83
83
  add_to_reachable RestfulDocGenerator.inspect_attributes(action_config.payload)
84
+
85
+ action_config.responses.values.each do |response|
86
+ add_to_reachable RestfulDocGenerator.inspect_attributes(response.media_type)
87
+ end
84
88
  end
85
89
 
86
90
  end
87
91
 
88
92
  # TODO: I think that the "id"/"name" of a resource should be provided by the definition/controller...not derived here
89
93
  def id
94
+ if @controller_config.controller
95
+ @controller_config.controller.id
96
+ else
97
+ # If an API doesn't quite have the controller defined, let's use the name from the resource definition
98
+ @controller_config.id
99
+ end
100
+ end
101
+
102
+ def name
90
103
  if @controller_config.controller
91
104
  @controller_config.controller.name
92
105
  else
@@ -95,6 +108,17 @@ module Praxis
95
108
  end
96
109
  end
97
110
 
111
+ def friendly_name
112
+ # FIXME: is it really about the controller? or the attached resource definition?
113
+ segments = self.name.split("::")
114
+ # FIXME: Crappy hack to derive a friendly name
115
+ if ["Collection","Links"].include? segments.last
116
+ segments[-2] + segments[-1] # concat the last 2
117
+ else
118
+ segments.last
119
+ end
120
+ end
121
+
98
122
  def add_to_reachable( found )
99
123
  return if found == nil
100
124
  @reachable_types += found
@@ -119,6 +143,7 @@ module Praxis
119
143
  write_resources
120
144
  write_types(types_for)
121
145
  write_index(types_for)
146
+ write_info(types_for)
122
147
  write_templates(types_for)
123
148
  end
124
149
 
@@ -182,7 +207,7 @@ module Praxis
182
207
  FileUtils.mkdir_p dirname unless File.exists? dirname
183
208
  reportable_types = types - EXCLUDED_TYPES_FROM_TOP_LEVEL
184
209
  reportable_types.each do |type|
185
- filename = File.join(dirname, "#{type.name}.json")
210
+ filename = File.join(dirname, "#{type.id}.json")
186
211
  #puts "Dumping #{type.name} to #{filename}"
187
212
  type_output = type.describe
188
213
  example_data = type.example(type.to_s)
@@ -233,12 +258,12 @@ module Praxis
233
258
 
234
259
  @resources.each do |r|
235
260
  index[r.version] ||= Hash.new
236
- info = {controller: r.id}
261
+ info = {controller: r.id, name: r.name}
237
262
  if r.media_type
238
- info[:media_type] = r.media_type.name
263
+ info[:media_type] = r.media_type.id
239
264
  media_types_seen_from_controllers << r.media_type
240
265
  end
241
- display_name = r.id.split("::").last
266
+ display_name = r.name.split("::").last
242
267
  index[r.version][display_name] = info
243
268
  end
244
269
 
@@ -255,9 +280,9 @@ module Praxis
255
280
  raise "Display name already taken for version #{version}! #{display_name}"
256
281
  end
257
282
  index[version][display_name] = if type < Praxis::MediaType
258
- {media_type: type.name }
283
+ {media_type: type.id }
259
284
  else
260
- {kind: type.name}
285
+ {kind: type.id}
261
286
  end
262
287
  end
263
288
  end
@@ -267,6 +292,68 @@ module Praxis
267
292
  File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(index))}
268
293
  end
269
294
 
295
+ # Writes an "index" type file inside each version, with some higher level information about the API
296
+ def write_info( versioned_types )
297
+
298
+ resources_by_version = Hash.new do |hash, v|
299
+ hash[v] = Set.new
300
+ end
301
+ types_by_version = Hash.new do |hash, v|
302
+ hash[v] = Set.new
303
+ end
304
+
305
+ @resources.each do |r|
306
+ resources_by_version[r.version] << { id: r.id, name: r.name, friendly_name: r.friendly_name}
307
+ end
308
+
309
+ versioned_types.each do |version, types|
310
+ # # Discard any mediatypes that we've already seen and processed as controller related
311
+ reportable_types = types - EXCLUDED_TYPES_FROM_TOP_LEVEL
312
+ # #TODO: think about these special cases, is it needed?
313
+ # reportable_types.reject!{|type| type < Praxis::Links || type < Praxis::MediaTypeCollection }
314
+
315
+ reportable_types.each do |type|
316
+ segments = type.name.split("::")
317
+ # FIXME: Crappy hack to derive a friendly name
318
+ friendly_name = if ["Collection","Links"].include? segments.last
319
+ segments[-2] + segments[-1] # concat the last 2
320
+ else
321
+ segments.last
322
+ end
323
+
324
+ types_by_version[version] << { id: type.id, name: type.name, friendly_name: friendly_name}
325
+ end
326
+ end
327
+ ###############################
328
+
329
+ diff = resources_by_version.keys - types_by_version.keys - versioned_types.keys
330
+ raise "!!!!!!!! somehow we have a list of different versions from the types vs. resources seen" unless diff.empty?
331
+
332
+ infos = {}
333
+ versioned_types.each do |version, types|
334
+ infos[version] = {info:{}}
335
+ end
336
+ infos.merge!(ApiDefinition.instance.describe)
337
+
338
+ # Add resources and types list
339
+ versioned_types.keys.each do |v|
340
+
341
+ infos[v][:resources] = resources_by_version[v].each_with_object({}) do |element,hash|
342
+ hash[element[:id]] = { name: element[:name], friendly_name: element[:friendly_name] }
343
+ end
344
+ infos[v][:schemas] = types_by_version[v].each_with_object({}) do |element,hash|
345
+ hash[element[:id]] = { name: element[:name], friendly_name: element[:friendly_name] }
346
+ end
347
+ end
348
+
349
+ versioned_types.each do |version, types|
350
+ dirname = File.join(@doc_root_dir, version)
351
+ filename = File.join(dirname, "version_index.json")
352
+ File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(infos[version]))}
353
+ end
354
+ end
355
+
356
+
270
357
  def write_templates(versioned_types)
271
358
  # Calculate and write top-level (non-versioned) templates
272
359
  top_templates = write_template("")
@@ -5,15 +5,8 @@ module Praxis
5
5
  self.class.name
6
6
  end
7
7
 
8
- def ===(other_thing)
9
- case other_thing
10
- when String
11
- identifier == other_thing
12
- when MediaType
13
- identifier == other_thing.identifier
14
- else
15
- raise 'can not compare'
16
- end
8
+ def id
9
+ self.class.name.gsub("::",'-')
17
10
  end
18
11
 
19
12
  def describe(shallow=true)
data/lib/praxis/stage.rb CHANGED
@@ -24,14 +24,13 @@ module Praxis
24
24
  end
25
25
 
26
26
  def run
27
- setup!
28
- setup_deferred_callbacks!
29
27
  execute_callbacks(self.before_callbacks)
30
28
  execute
31
29
  execute_callbacks(self.after_callbacks)
32
30
  end
33
31
 
34
32
  def setup!
33
+ setup_deferred_callbacks!
35
34
  end
36
35
 
37
36
  def setup_deferred_callbacks!
@@ -48,8 +47,6 @@ module Praxis
48
47
  end
49
48
 
50
49
  def execute
51
- raise NotImplementedError, 'Subclass must implement Stage#execute' unless @stages.any?
52
-
53
50
  @stages.each do |stage|
54
51
  stage.run
55
52
  end
@@ -1,30 +1,62 @@
1
1
  namespace :praxis do
2
- desc "Generate API docs (JSON definitions) for a Praxis App"
3
- task :api_docs => [:environment] do |t, args|
4
- require 'fileutils'
5
2
 
6
- Praxis::Blueprint.caching_enabled = false
7
- generator = Praxis::RestfulDocGenerator.new(Dir.pwd)
8
- end
3
+ namespace :docs do
4
+ path = File.expand_path(File.join(File.dirname(__FILE__), '../../api_browser'))
9
5
 
10
- desc "Run API Documentation Browser"
11
- task :doc_browser, [:port] => :api_docs do |t, args|
12
- args.with_defaults port: 4567
6
+ desc "Install dependencies"
7
+ task :install do
8
+ unless system("npm install --production", chdir: path)
9
+ raise Exception.new("NPM Install Failed")
10
+ end
11
+
12
+ docs_dir = File.join(Dir.pwd, 'docs')
13
+ FileUtils.mkdir_p docs_dir unless File.directory? docs_dir
13
14
 
14
- public_folder = File.expand_path("../../../", __FILE__) + "/api_browser/app"
15
- app = Rack::Builder.new do
16
- map "/docs" do # application JSON docs
17
- use Rack::Static, urls: [""], root: File.join(Dir.pwd, Praxis::RestfulDocGenerator::API_DOCS_DIRNAME)
15
+ # The doc browser will need to have a minimal app.js and styles.css file at the root
16
+ # Let's add them if the app has not overriden them
17
+ js_file = File.join(Dir.pwd, 'docs', 'app.js')
18
+ scss_file = File.join(Dir.pwd, 'docs', 'styles.scss')
19
+ template_directory = File.expand_path(File.join(File.dirname(__FILE__), '../../../tasks/thor/templates/generator/empty_app/docs'))
20
+
21
+ unless File.exists? js_file
22
+ FileUtils.cp File.join(template_directory, 'app.js'), js_file
18
23
  end
19
- map "/" do # Assets mapping
20
- use Rack::Static, urls: [""], root: public_folder, index: "index.html"
24
+ unless File.exists? scss_file
25
+ FileUtils.cp File.join(template_directory, 'styles.scss'), scss_file
21
26
  end
27
+ end
22
28
 
23
- run lambda { |env| [404, {'Content-Type' => 'text/plain'}, ['Not Found']] }
29
+ desc "Run API Documentation Browser"
30
+ task :preview, [:port] => [:install, :generate] do |t, args|
31
+ doc_port = args[:port] || '9090'
32
+ exec({'USER_DOCS_PATH' => File.join(Dir.pwd, 'docs'), 'DOC_PORT' => doc_port}, "#{path}/node_modules/.bin/grunt serve --gruntfile '#{path}/Gruntfile.js'")
24
33
  end
25
34
 
26
-
27
- Rack::Server.start app: app, Port: args[:port]
35
+ desc "Build docs that can be shipped"
36
+ task :build => [:install, :generate] do
37
+ exec({'USER_DOCS_PATH' => File.join(Dir.pwd, 'docs')}, "#{path}/node_modules/.bin/grunt build --gruntfile '#{path}/Gruntfile.js'")
38
+ end
39
+
40
+ desc "Generate API docs (JSON definitions) for a Praxis App"
41
+ task :generate => [:environment] do |t, args|
42
+ require 'fileutils'
43
+
44
+ Praxis::Blueprint.caching_enabled = false
45
+ generator = Praxis::RestfulDocGenerator.new(Dir.pwd)
46
+ end
47
+
48
+ end
49
+
50
+ desc "Generate API docs (JSON definitions) for a Praxis App"
51
+ task :api_docs do
52
+ STDERR.puts "DEPRECATION: praxis:api_docs is deprecated and will be removed by 1.0. Please use praxis:docs:generate instead."
53
+ Rake::Task["praxis:docs:generate"].invoke
28
54
  end
29
-
55
+
56
+ desc "Run API Documentation Browser"
57
+ task :doc_browser, [:port] do |t, args|
58
+ STDERR.puts "DEPRECATION: praxis:doc_browser is deprecated and will be removed by 1.0. Please use praxis:docs:preview instead. The doc browser now runs on port 9090."
59
+ Rake::Task["praxis:docs:preview"].invoke
60
+ end
61
+
30
62
  end
@@ -4,7 +4,6 @@ namespace :praxis do
4
4
  task :routes, [:format] => [:environment] do |t, args|
5
5
  require 'terminal-table'
6
6
 
7
-
8
7
  table = Terminal::Table.new title: "Routes",
9
8
  headings: [
10
9
  "Version", "Path", "Verb",
@@ -22,23 +21,28 @@ namespace :praxis do
22
21
 
23
22
  method_name = method ? "#{method.owner.name}##{method.name}" : 'n/a'
24
23
 
25
- action.routes.each do |route|
26
- rows << {
27
- resource: resource_definition.name,
28
- version: route.version,
29
- verb: route.verb,
30
- path: route.path,
31
- action: name,
32
- implementation: method_name,
33
- name: route.name,
34
- primary: (action.primary_route == route ? 'yes' : '')
35
- }
24
+ row = {
25
+ resource: resource_definition.name,
26
+ action: name,
27
+ implementation: method_name,
28
+ }
29
+
30
+ if action.routes.empty?
31
+ warn "Warning: No routes defined for #{resource_definition.name}##{name}."
32
+ rows << row
33
+ else
34
+ action.routes.each do |route|
35
+ rows << row.merge({
36
+ version: route.version,
37
+ verb: route.verb,
38
+ path: route.path,
39
+ name: route.name,
40
+ primary: (action.primary_route == route ? 'yes' : '')
41
+ })
36
42
  end
37
43
  end
38
44
  end
39
-
40
-
41
-
45
+ end
42
46
 
43
47
  case args[:format] || "table"
44
48
  when "json"
@@ -0,0 +1,31 @@
1
+ module Praxis
2
+ module Types
3
+
4
+ module MediaTypeCommon
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def describe(shallow = false)
9
+ hash = super
10
+ unless shallow
11
+ hash.merge!(identifier: @identifier, description: @description)
12
+ end
13
+ hash
14
+ end
15
+
16
+ def description(text=nil)
17
+ @description = text if text
18
+ @description
19
+ end
20
+
21
+ def identifier(identifier=nil)
22
+ return @identifier unless identifier
23
+ # TODO: parse the string and extract things like collection , and format type?...
24
+ @identifier = identifier
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -28,10 +28,10 @@ module Praxis
28
28
 
29
29
  super(context, options: options).each do |k,v|
30
30
  body = if v.respond_to?(:dump) && !v.kind_of?(String)
31
- JSON.pretty_generate(v.dump)
32
- else
33
- v
34
- end
31
+ JSON.pretty_generate(v.dump)
32
+ else
33
+ v
34
+ end
35
35
 
36
36
  entity = MIME::Text.new(body)
37
37
 
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.13.0'
2
+ VERSION = '0.14.0'
3
3
  end
data/praxis.gemspec CHANGED
@@ -25,8 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency 'activesupport', '>= 3'
26
26
  spec.add_dependency 'mime', '~> 0'
27
27
  spec.add_dependency 'praxis-mapper', '~> 3.3'
28
- spec.add_dependency 'praxis-blueprints', '~> 1.2'
29
- spec.add_dependency 'attributor', '~> 2.5.0'
28
+ spec.add_dependency 'praxis-blueprints', '~> 1.3'
29
+ spec.add_dependency 'attributor', '~> 2.6.0'
30
30
  spec.add_dependency 'thor', '~> 0.18'
31
31
  spec.add_dependency 'terminal-table', '~> 1.4'
32
32
  spec.add_dependency 'harness', '~> 2'
@@ -0,0 +1,50 @@
1
+ describe('Documentation service', function() {
2
+ var $scope, Documentation, $httpBackend;
3
+
4
+ beforeEach(angular.mock.module('PraxisDocBrowser'));
5
+
6
+ beforeEach(inject(function($rootScope, $injector) {
7
+ $scope = $rootScope.$new();
8
+ Documentation = $injector.get('Documentation');
9
+ $httpBackend = $injector.get('$httpBackend');
10
+ }));
11
+
12
+ afterEach(function() {
13
+ $httpBackend.verifyNoOutstandingExpectation();
14
+ $httpBackend.verifyNoOutstandingRequest();
15
+ });
16
+
17
+ describe('#getIndex', function() {
18
+ var result, response = {
19
+ '1.0': {
20
+ 'Blogs': {
21
+ 'controller': 'V1-Controllers-Blogs',
22
+ 'name': 'V1::Controllers::Blogs',
23
+ 'media_type': 'V1-MediaTypes-Blog'
24
+ },
25
+ 'Posts': {
26
+ 'controller': 'V1-ResourceDefinitions-Posts',
27
+ 'name': 'V1::ResourceDefinitions::Posts',
28
+ 'media_type': 'V1-MediaTypes-Post'
29
+ },
30
+ 'Users': {
31
+ 'controller': 'V1-ResourceDefinitions-Users',
32
+ 'name': 'V1::ResourceDefinitions::Users',
33
+ 'media_type': 'V1-MediaTypes-User'
34
+ }
35
+ }
36
+ };
37
+ beforeEach(function() {
38
+ $httpBackend.expectGET('api/index.json').respond(response);
39
+ Documentation.getIndex().then(function(data) {
40
+ result = data;
41
+ });
42
+ $httpBackend.flush();
43
+ $scope.$apply();
44
+ });
45
+
46
+ it('returns the index data', function() {
47
+ expect(result.data).toEqual(response);
48
+ });
49
+ });
50
+ });