openstax_api 2.5.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -6
  3. data/app/controllers/openstax/api/v1/api_controller.rb +1 -1
  4. data/app/representers/openstax/api/v1/abstract_search_representer.rb +40 -0
  5. data/lib/openstax/api/constraints.rb +3 -4
  6. data/lib/openstax/api/engine.rb +3 -8
  7. data/lib/openstax/api/representable_schema_printer.rb +58 -35
  8. data/lib/openstax/api/roar.rb +50 -93
  9. data/lib/openstax/api/version.rb +1 -1
  10. data/spec/controllers/openstax/api/v1/api_controller_spec.rb +88 -2
  11. data/spec/dummy/README.md +1 -1
  12. data/spec/dummy/Rakefile +1 -2
  13. data/spec/dummy/app/assets/javascripts/application.js +3 -5
  14. data/spec/dummy/app/assets/stylesheets/application.css +5 -3
  15. data/spec/dummy/app/models/user.rb +2 -0
  16. data/spec/dummy/app/representers/user_representer.rb +13 -0
  17. data/spec/dummy/app/representers/user_search_representer.rb +5 -0
  18. data/spec/dummy/app/routines/search_users.rb +22 -0
  19. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  20. data/spec/dummy/bin/bundle +3 -0
  21. data/spec/dummy/bin/rails +4 -0
  22. data/spec/dummy/bin/rake +4 -0
  23. data/spec/dummy/config/application.rb +0 -30
  24. data/spec/dummy/config/boot.rb +4 -9
  25. data/spec/dummy/config/database.yml +8 -8
  26. data/spec/dummy/config/environment.rb +3 -3
  27. data/spec/dummy/config/environments/development.rb +21 -13
  28. data/spec/dummy/config/environments/production.rb +41 -32
  29. data/spec/dummy/config/environments/test.rb +17 -13
  30. data/spec/dummy/config/initializers/assets.rb +8 -0
  31. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  32. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/spec/dummy/config/initializers/inflections.rb +6 -5
  34. data/spec/dummy/config/initializers/mime_types.rb +0 -1
  35. data/spec/dummy/config/initializers/openstax_api.rb +2 -2
  36. data/spec/dummy/config/initializers/session_store.rb +1 -6
  37. data/spec/dummy/config/initializers/wrap_parameters.rb +6 -6
  38. data/spec/dummy/config/locales/en.yml +20 -2
  39. data/spec/dummy/config/secrets.yml +22 -0
  40. data/spec/dummy/config.ru +1 -1
  41. data/spec/dummy/db/development.sqlite3 +0 -0
  42. data/spec/dummy/db/migrate/1_create_users.rb +16 -0
  43. data/spec/dummy/db/schema.rb +38 -32
  44. data/spec/dummy/db/test.sqlite3 +0 -0
  45. data/spec/dummy/lib/controller_includes.rb +2 -6
  46. data/spec/dummy/log/development.log +486 -1445
  47. data/spec/dummy/log/test.log +16720 -3757
  48. data/spec/dummy/public/404.html +54 -13
  49. data/spec/dummy/public/422.html +54 -13
  50. data/spec/dummy/public/500.html +53 -12
  51. data/spec/factories/user.rb +8 -0
  52. data/spec/lib/openstax/api/apipie_spec.rb +15 -0
  53. data/spec/lib/openstax/api/constraints_spec.rb +6 -6
  54. data/spec/lib/openstax/api/doorkeeper_application_includes_spec.rb +1 -1
  55. data/spec/lib/openstax/api/representable_schema_printer_spec.rb +5 -3
  56. data/spec/lib/openstax/api/roar_spec.rb +22 -0
  57. data/spec/lib/openstax/api/routing_mapper_includes_spec.rb +1 -1
  58. data/spec/lib/openstax_api_spec.rb +19 -0
  59. data/spec/models/openstax/api/api_user_spec.rb +2 -2
  60. data/spec/rails_helper.rb +54 -0
  61. data/spec/representers/openstax/api/v1/abstract_search_representer_spec.rb +127 -0
  62. data/spec/spec_helper.rb +80 -13
  63. metadata +85 -18
  64. data/spec/dummy/app/models/dummy_user.rb +0 -2
  65. data/spec/dummy/app/representers/dummy_user_representer.rb +0 -9
  66. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  67. data/spec/dummy/db/migrate/1_create_dummy_users.rb +0 -10
  68. data/spec/dummy/script/rails +0 -6
  69. data/spec/lib/openstax/api/apipie.rb +0 -11
  70. data/spec/lib/openstax/api/roar.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e9f64fc680c9305cdab0cf7c5763750825793bc
4
- data.tar.gz: 0d469d8177f3417341d18e502ba8c575bc7bb153
3
+ metadata.gz: e4a4d7a6e7dd7a7ccda5ca535edb33ce7dc6694c
4
+ data.tar.gz: 1337c07dcb6b7097a7411850b09781ab0729110e
5
5
  SHA512:
6
- metadata.gz: acdfccc6cda3472e3f14f7cee2e2feb21175d5208719a0adbf42f8f4a368259b583f3dafa8012504e170e31f736826aa18f8a52ba5b2ee0c9b3bb13cdd15c6b7
7
- data.tar.gz: 1e9d2348a19c27782de4f31ed8740120b3ce6346e45ce8d452d56a6fb8cb91c3cf258dbb98a4c4e7df3889e4852b08e44f8ddc0b93df2d490d466ee4e450988e
6
+ metadata.gz: d7b974c2cb4bc39dea96d829e94b5296db85fa15d03e8d9cb30f075e27a3e606296ad3188a68cb6108d451a9ba322ba386087469e40d85718125af75ae57a609
7
+ data.tar.gz: dc0ee731484d0a14411327ee1cde7d0e622421d0973d47108255a11ab22a5104df0a3ee1975ffd14fa5e0d26a0ab158fd3d0c413023a435da40bb2644040a1e6
data/Rakefile CHANGED
@@ -11,12 +11,10 @@ load 'rails/tasks/engine.rake'
11
11
 
12
12
  Bundler::GemHelper.install_tasks
13
13
 
14
- require 'rake/testtask'
14
+ require 'rspec/core'
15
+ require 'rspec/core/rake_task'
15
16
 
16
- Rake::TestTask.new(:spec => 'app:db:test:prepare') do |t|
17
- t.libs << 'spec'
18
- t.pattern = 'spec/**/*_spec.rb'
19
- t.verbose = false
20
- end
17
+ desc 'Run all specs in spec directory (excluding plugin specs)'
18
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
21
19
 
22
20
  task :default => :spec
@@ -37,7 +37,7 @@ module OpenStax
37
37
  protected
38
38
 
39
39
  def session_user?
40
- current_session_user && doorkeeper_token.blank?
40
+ !!current_session_user && doorkeeper_token.blank?
41
41
  end
42
42
 
43
43
  end
@@ -0,0 +1,40 @@
1
+ # Represents search results for a JSON API
2
+ #
3
+ # Subclasses should define the representer for the search results:
4
+ # collection :items, inherit: true, decorator: SomeRepresenter
5
+ #
6
+ # See spec/dummy/app/representers/user_search_representer.rb for an example search representer
7
+
8
+ module OpenStax
9
+ module Api
10
+ module V1
11
+ class AbstractSearchRepresenter < ::Roar::Decorator
12
+
13
+ include ::Roar::Representer::JSON
14
+
15
+ property :total_count,
16
+ type: Integer,
17
+ readable: true,
18
+ writeable: false,
19
+ exec_context: :decorator,
20
+ schema_info: {
21
+ required: true,
22
+ description: "The number of items matching the query; can be more than the number returned if paginating"
23
+ }
24
+
25
+ collection :items,
26
+ readable: true,
27
+ writeable: false,
28
+ schema_info: {
29
+ required: true,
30
+ description: "The items matching the query or a subset thereof when paginating"
31
+ }
32
+
33
+ def total_count
34
+ represented[:total_count] || represented[:items].limit(nil).offset(nil).count
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,16 +1,15 @@
1
+ require 'openstax_utilities'
2
+
1
3
  module OpenStax
2
4
  module Api
3
5
  class Constraints
4
- cattr_accessor :main_app_name
5
-
6
6
  def initialize(options)
7
7
  @version = options[:version]
8
8
  @default = options[:default]
9
9
  end
10
10
 
11
11
  def api_accept_header
12
- self.main_app_name ||= OpenStax::Api::Engine::MAIN_APP_NAME.underscore
13
- "application/vnd.#{self.main_app_name}.openstax.#{@version.to_s}"
12
+ "application/vnd.openstax.#{OSU::SITE_NAME}.#{@version.to_s}"
14
13
  end
15
14
 
16
15
  def matches?(req)
@@ -1,5 +1,6 @@
1
1
  require 'roar-rails'
2
2
  require 'exception_notification'
3
+ require 'openstax_utilities'
3
4
  require 'openstax/api/roar'
4
5
  require 'openstax/api/apipie'
5
6
 
@@ -8,17 +9,11 @@ module OpenStax
8
9
  class Engine < ::Rails::Engine
9
10
  isolate_namespace OpenStax::Api
10
11
 
11
- config.after_initialize do
12
- MAIN_APP_NAME = ::Rails.application.class.parent_name
13
- end
14
-
15
12
  config.generators do |g|
16
13
  g.test_framework :rspec, :fixture => false
17
14
  g.fixture_replacement :factory_girl, :dir => 'spec/factories'
18
- end
19
-
20
- ActiveSupport::Inflector.inflections do |inflect|
21
- inflect.acronym 'OpenStax'
15
+ g.assets false
16
+ g.helper false
22
17
  end
23
18
  end
24
19
  end
@@ -2,6 +2,7 @@ module OpenStax
2
2
  module Api
3
3
  class RepresentableSchemaPrinter
4
4
 
5
+ # Returns the json schema for a representer
5
6
  def self.json_schema(representer, options = {})
6
7
  definitions = {}
7
8
  schema = json_object(representer, definitions, options)
@@ -9,6 +10,8 @@ module OpenStax
9
10
  schema
10
11
  end
11
12
 
13
+ # Returns some formatted Markdown with HTML containing the
14
+ # JSON schema for a given representer
12
15
  def self.json(representer, options={})
13
16
  options[:include] ||= [:readable, :writeable]
14
17
 
@@ -20,29 +23,34 @@ module OpenStax
20
23
  "\n<pre class='code'>\n#{json_string}\n</pre>\n"
21
24
  end
22
25
 
23
- protected
26
+ protected
24
27
 
28
+ # Attempts to extract the given representer's name
25
29
  def self.representer_name(representer)
26
30
  name = representer.name
27
31
  return nil if name.nil?
28
32
  name.chomp('Representer').demodulize.camelize(:lower)
29
33
  end
30
34
 
35
+ # Gets the definition name for the given representer name
31
36
  def self.definition_name(name)
32
37
  "#/definitions/#{name}"
33
38
  end
34
39
 
40
+ # Helper function for json_schema
35
41
  def self.json_object(representer, definitions, options = {})
42
+ # Initialize the schema
36
43
  schema = { type: :object, required: [], properties: {},
37
44
  additionalProperties: false }
38
45
 
39
46
  representer.representable_attrs.each do |attr|
40
- name = attr[:as].evaluate(self)
47
+ # Handle some common attributes (as, schema_info, required)
48
+ name = attr[:as].evaluate(representer)
41
49
  schema_info = attr[:schema_info] || {}
42
50
 
43
51
  schema[:required].push(name.to_sym) if schema_info[:required]
44
52
 
45
- # Skip unless attr includes the specified key or is required
53
+ # Skip attr it attr includes the specified key or is "required"
46
54
  next unless [options[:include]].flatten.any?{ |inc|
47
55
  m = inc.to_s + "?"
48
56
  attr.respond_to?(m) ? attr.send(m) : attr[inc]
@@ -54,65 +62,79 @@ module OpenStax
54
62
  (name.end_with?('id') ? :integer : :string) : type
55
63
  attr_info ||= { type: type }
56
64
 
65
+ # Process the schema_info attribute
57
66
  schema_info.each do |key, value|
67
+ # Handle special keys (required, definitions, type)
58
68
  next if key == :required
59
69
  if key == :definitions
60
70
  definitions.merge!(value)
61
71
  next
62
72
  end
63
73
  value = value.to_s.downcase if key == :type
74
+ # Store the schema_info k-v pair in attr_info
64
75
  attr_info[key] = value
65
76
  end
66
77
 
67
78
  # Overwrite type for collections
68
79
  attr_info[:type] = 'array' if attr[:collection]
69
80
 
81
+ # Handle nested representers
70
82
  if attr[:extend]
71
- # We're dealing with a nested representer. It may just be a simple representer
72
- # or it could be an Uber::Callable (representing that the representer could be
73
- # one of a number of possible representers)
74
-
75
- if attr[:extend].is_a?(Uber::Options::Value) && attr[:extend].dynamic?
76
- # We're dealing with an Uber::Callable situation, so need to get the list of
77
- # possible representers and add each one to the "oneOf" list as well as to
78
- # the definitions hash
79
-
80
- attr_info[:type] = 'object'
81
- attr_info[:oneOf] = []
82
-
83
- attr[:extend].evaluate(:all_sub_representers).each do |sub_representer|
84
- srname = representer_name(sub_representer)
85
- attr_info[:oneOf].push(:$ref => definition_name(srname))
86
- definitions[srname] = json_object(sub_representer, definitions, options) if definitions[srname].nil?
87
- end
88
- else
89
- # We're dealing with a simple representer
90
-
91
- decorator = attr[:extend].evaluate(self)
83
+ # The nested representer can be either a simple representer or
84
+ # an Uber::Callable, in which case there could be multiple representers
85
+
86
+ # Get the nested representers
87
+ # Evaluate syntax is evaluate(context, instance or class, *args)
88
+ # We have no instance or class (since we have no fragment), so we pass nil
89
+ # By convention, the callables we use should return an array of all
90
+ # possible representers when we pass the :all_sub_representers => true option
91
+ decorators = [attr[:extend].evaluate(representer, nil, :all_sub_representers => true)].flatten
92
+
93
+ # Count the representers
94
+ include_oneof = decorators.length > 1
95
+ # If we have more than one possible representer, use oneOf to list them all
96
+ sreps = include_oneof ? { oneOf: [] } : {}
97
+
98
+ decorators.each do |decorator|
99
+ # Attempt to get the representer's name
92
100
  rname = representer_name(decorator)
93
101
 
94
102
  if rname
103
+ # We have a representer name, so use a schema definition with that name
95
104
  dname = definition_name(rname)
105
+ dhash = { :$ref => dname }
106
+
107
+ include_oneof ? sreps[:oneOf].push(dhash) : sreps = dhash
96
108
 
97
- if attr[:collection]
98
- attr_info[:items] = { :$ref => dname }
99
- else
100
- # Type is included in ref
101
- attr_info.delete(:type)
102
- attr_info[:$ref] = dname
103
- end
104
109
  if definitions[rname].nil?
110
+ # No definition with that name found, so add it
111
+ # A blank definition is added first to prevent infinite loops
105
112
  definitions[rname] = {}
106
- definitions[rname] = json_object(decorator,
107
- definitions, options)
113
+ definitions[rname] = json_object(decorator, definitions, options)
108
114
  end
109
115
  else
110
- attr_info.merge!(json_object(decorator, definitions, options))
116
+ # No representer name, so add the object inline instead
117
+ obj = json_object(decorator, definitions, options)
118
+ include_oneof ? sreps[:oneOf].push(obj) : sreps = obj
111
119
  end
112
- end # .is_a?(...)
120
+ end
121
+
122
+ if attr[:collection]
123
+ # Collection
124
+ # Type already set above (array)
125
+ # Add sub representers under :items
126
+ attr_info[:items] = sreps
127
+ else
128
+ # Not a collection
129
+ # Type included in ref
130
+ # Add sub representers inline
131
+ attr_info.delete(:type)
132
+ attr_info.merge!(sreps)
133
+ end
113
134
 
114
135
  end
115
136
 
137
+ # Merge attr_info back into the schema
116
138
  schema[:properties][name.to_sym] = attr_info
117
139
  end
118
140
 
@@ -121,6 +143,7 @@ module OpenStax
121
143
  schema.delete(field) if schema[field].blank?
122
144
  end
123
145
 
146
+ # Return the completed object schema
124
147
  schema
125
148
  end
126
149
 
@@ -8,43 +8,46 @@ module OpenStax
8
8
 
9
9
  module Roar
10
10
 
11
- def get_representer(represent_with, model=nil)
12
- return nil if represent_with.nil?
13
- if represent_with.is_a? Proc
14
- represent_with.call(model)
15
- else
16
- represent_with
17
- end
11
+ def standard_create(model, represent_with=nil, &block)
12
+ standard_nested_create(model, nil, nil, represent_with, &block)
18
13
  end
19
14
 
20
- def standard_read(model_klass, id, represent_with=nil)
21
- @model = model_klass.find(id)
15
+ def standard_read(model, represent_with=nil)
22
16
  OSU::AccessPolicy.require_action_allowed!(:read, current_api_user, @model)
23
- respond_with @model, represent_with: get_representer(represent_with, @model)
17
+ respond_with model, represent_with: represent_with
24
18
  end
25
19
 
26
- def standard_update(model_klass, id, represent_with=nil)
27
- @model = model_klass.find(id)
28
- OSU::AccessPolicy.require_action_allowed!(:update, current_api_user, @model)
29
- consume!(@model, represent_with: get_representer(represent_with, @model))
20
+ def standard_update(model, represent_with=nil)
21
+ OSU::AccessPolicy.require_action_allowed!(:update, current_api_user, model)
22
+
23
+ model_klass.transaction do
24
+ consume!(model, represent_with: represent_with)
25
+ yield model if block_given?
26
+ OSU::AccessPolicy.require_action_allowed!(:update, current_api_user, model)
30
27
 
31
- if @model.save
32
- head :no_content
33
- else
34
- render json: @model.errors, status: :unprocessable_entity
28
+ if model.save
29
+ head :no_content
30
+ else
31
+ render json: model.errors, status: :unprocessable_entity
32
+ end
35
33
  end
36
34
  end
37
35
 
38
- def standard_create(model_klass, represent_with=nil, &block)
39
- standard_nested_create(model_klass, nil, nil, represent_with, &block)
36
+ def standard_destroy(model)
37
+ OSU::AccessPolicy.require_action_allowed!(:destroy, current_api_user, model)
38
+
39
+ if model.destroy
40
+ head :no_content
41
+ else
42
+ render json: model.errors, status: :unprocessable_entity
43
+ end
40
44
  end
41
45
 
42
- def standard_nested_create(model_klass, container_association=nil, container_id=nil, represent_with=nil)
43
- @model = model_klass.new()
44
-
45
- if container_association && container_id
46
- foreign_key = model_klass.reflect_on_association(container_association).association_foreign_key
47
- @model.send(foreign_key + '=', container_id)
46
+ def standard_nested_create(model, container_association=nil,
47
+ container=nil, represent_with=nil)
48
+ if container_association && container
49
+ OSU::AccessPolicy.require_action_allowed!(:update, current_api_user, container)
50
+ model.send("#{container_association.to_s}=", container)
48
51
  end
49
52
 
50
53
  # Unlike the implications of the representable README, "consume!" can
@@ -52,80 +55,34 @@ module OpenStax
52
55
  # We do want to consume before checking the permissions so we can know
53
56
  # what we're dealing with, but if user doesn't have permission we don't
54
57
  # want to have changed the DB. Wrap in a transaction to protect ourselves.
55
-
56
58
  model_klass.transaction do
57
- consume!(@model, represent_with: get_representer(represent_with, @model))
58
- yield @model if block_given?
59
- OSU::AccessPolicy.require_action_allowed!(:create, current_api_user, @model)
60
- end
61
-
62
- if @model.save
63
- respond_with @model, represent_with: get_representer(represent_with, @model), status: :created
64
- else
65
- render json: @model.errors, status: :unprocessable_entity
59
+ consume!(model, represent_with: represent_with)
60
+ yield model if block_given?
61
+ OSU::AccessPolicy.require_action_allowed!(:create, current_api_user, model)
62
+
63
+ if model.save
64
+ respond_with model, represent_with: represent_with, status: :created
65
+ else
66
+ render json: model.errors, status: :unprocessable_entity
67
+ end
66
68
  end
67
69
  end
68
70
 
69
- def standard_destroy(model_klass, id)
70
- @model = model_klass.find(id)
71
- OSU::AccessPolicy.require_action_allowed!(:destroy, current_api_user, @model)
72
-
73
- if @model.destroy
74
- head :no_content
75
- else
76
- render json: @model.errors, status: :unprocessable_entity
77
- end
71
+ def standard_index(relation, represent_with)
72
+ model_klass = relation.base_class
73
+ OSU::AccessPolicy.require_action_allowed!(:index, current_api_user, model_klass)
74
+ respond_with relation, represent_with: represent_with
78
75
  end
79
76
 
80
- def standard_sort(model_klass)
81
- # Take array of all IDs or hash of id => position,
82
- # Regardless, build up an array of all IDs in the right order and pass those to sort
83
-
84
- new_positions = params['newPositions']
85
- return head :no_content if new_positions.length == 0
86
-
87
- # Can't have duplicate positions or IDs
88
- unique_ids = new_positions.collect{|np| np['id']}.uniq
89
- unique_positions = new_positions.collect{|np| np['position']}.uniq
90
-
91
- return head :bad_request if unique_ids.length != new_positions.length
92
- return head :bad_request if unique_positions.length != new_positions.length
93
-
94
- first = model_klass.where(:id => new_positions[0]['id']).first
95
-
96
- return head :not_found if first.blank?
97
-
98
- originalOrdered = first.me_and_peers.ordered.all
99
-
100
- originalOrdered.each do |item|
101
- raise SecurityTransgression unless item.send(:container_column) == originalOrdered[0].send(:container_column) \
102
- if item.respond_to?(:container_column)
103
- OSU::AccessPolicy.require_action_allowed!(:sort, current_api_user, item)
104
- end
105
-
106
- originalOrderedIds = originalOrdered.collect{|sc| sc.id}
107
-
108
- newOrderedIds = Array.new(originalOrderedIds.size)
109
-
110
- new_positions.each do |newPosition|
111
- id = newPosition['id'].to_i
112
- newOrderedIds[newPosition['position']] = id
113
- originalOrderedIds.delete(id)
114
- end
115
-
116
- ptr = 0
117
- for oldId in originalOrderedIds
118
- while !newOrderedIds[ptr].nil?; ptr += 1; end
119
- newOrderedIds[ptr] = oldId
120
- end
121
-
122
- begin
123
- model_klass.sort!(newOrderedIds)
124
- rescue Exception => e
125
- return head :internal_server_error
126
- end
77
+ def standard_search(routine, query, options, represent_with)
78
+ model_klass = routine.send(:initial_relation).base_class
79
+ OSU::AccessPolicy.require_action_allowed!(:search, current_api_user, model_klass)
80
+ outputs = routine.call(query, options).outputs
81
+ respond_with outputs, represent_with: represent_with
82
+ end
127
83
 
128
- head :no_content
84
+ def standard_sort(*args)
85
+ raise NotYetImplemented
129
86
  end
130
87
 
131
88
  end
@@ -1,5 +1,5 @@
1
1
  module OpenStax
2
2
  module Api
3
- VERSION = "2.5.1"
3
+ VERSION = "3.0.0"
4
4
  end
5
5
  end
@@ -1,10 +1,96 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
 
3
3
  module OpenStax
4
4
  module Api
5
5
  module V1
6
6
  describe ApiController do
7
- # Nothing to test yet
7
+
8
+ let!(:user) { FactoryGirl.create :user }
9
+ let!(:user_2) { FactoryGirl.create :user }
10
+ let!(:application) { double('Doorkeeper::Application') }
11
+ let!(:doorkeeper_token) { double('Doorkeeper::AccessToken') }
12
+ let!(:non_doorkeeper_user_proc) { lambda { user } }
13
+ let!(:controller) { ApiController.new }
14
+
15
+ context 'no authentication' do
16
+ before (:each) do
17
+ controller.doorkeeper_token = nil
18
+ controller.present_user = nil
19
+ end
20
+
21
+ it 'has no human_user and no application' do
22
+ expect(controller.send :session_user?).to eq false
23
+ expect(controller.current_application).to be_nil
24
+ expect(controller.current_human_user).to be_nil
25
+ expect(controller.current_session_user).to be_nil
26
+ end
27
+ end
28
+
29
+ context 'session' do
30
+ before (:each) do
31
+ controller.doorkeeper_token = nil
32
+ controller.present_user = user
33
+ end
34
+
35
+ it 'has a human_user but no application' do
36
+ expect(controller.send :session_user?).to eq true
37
+ expect(controller.current_application).to be_nil
38
+ expect(controller.current_human_user).to eq user
39
+ expect(controller.current_session_user).to eq user
40
+ end
41
+ end
42
+
43
+ context 'token with application and human user' do
44
+ before (:each) do
45
+ controller.doorkeeper_token = doorkeeper_token
46
+ controller.present_user = nil
47
+ end
48
+
49
+ it 'has a human_user from token and an application' do
50
+ allow(doorkeeper_token).to receive(:application).and_return(application)
51
+ allow(doorkeeper_token).to receive(:resource_owner_id).and_return(user.id)
52
+
53
+ expect(controller.send :session_user?).to eq false
54
+ expect(controller.current_application).to eq application
55
+ expect(controller.current_human_user).to eq user
56
+ expect(controller.current_session_user).to be_nil
57
+ end
58
+ end
59
+
60
+ context 'token with application only' do
61
+ before (:each) do
62
+ controller.doorkeeper_token = doorkeeper_token
63
+ controller.present_user = nil
64
+ end
65
+
66
+ it 'has an application but no human_user' do
67
+ allow(doorkeeper_token).to receive(:application).and_return(application)
68
+ allow(doorkeeper_token).to receive(:resource_owner_id).and_return(nil)
69
+
70
+ expect(controller.send :session_user?).to eq false
71
+ expect(controller.current_application).to eq application
72
+ expect(controller.current_human_user).to eq nil
73
+ expect(controller.current_session_user).to eq nil
74
+ end
75
+ end
76
+
77
+ context 'session and token' do
78
+ before (:each) do
79
+ controller.doorkeeper_token = doorkeeper_token
80
+ controller.present_user = user_2
81
+ end
82
+
83
+ it 'ignores the session unless explicitly asked' do
84
+ allow(doorkeeper_token).to receive(:application).and_return(application)
85
+ allow(doorkeeper_token).to receive(:resource_owner_id).and_return(user)
86
+
87
+ expect(controller.send :session_user?).to eq false
88
+ expect(controller.current_application).to eq application
89
+ expect(controller.current_human_user).to eq user
90
+ expect(controller.current_session_user).to eq user_2
91
+ end
92
+ end
93
+
8
94
  end
9
95
  end
10
96
  end
data/spec/dummy/README.md CHANGED
@@ -1 +1 @@
1
- Dummy application used to test the openstax_api gem.
1
+ Dummy application used to test the openstax_api gem.
data/spec/dummy/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
- #!/usr/bin/env rake
2
1
  # Add your own tasks in files placed in lib/tasks ending in .rake,
3
2
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
3
 
5
4
  require File.expand_path('../config/application', __FILE__)
6
5
 
7
- Dummy::Application.load_tasks
6
+ Rails.application.load_tasks
@@ -5,11 +5,9 @@
5
5
  // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
6
  //
7
7
  // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
- // the compiled file.
8
+ // compiled file.
9
9
  //
10
- // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
- // GO AFTER THE REQUIRES BELOW.
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
12
  //
13
- //= require jquery
14
- //= require jquery_ujs
15
13
  //= require_tree .
@@ -5,9 +5,11 @@
5
5
  * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
6
  * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
7
  *
8
- * You're free to add application-wide styles to this file and they'll appear at the top of the
9
- * compiled file, but it's generally better to create a new file per style scope.
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
10
12
  *
11
- *= require_self
12
13
  *= require_tree .
14
+ *= require_self
13
15
  */
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,13 @@
1
+ require 'representable/json'
2
+
3
+ class UserRepresenter < ::Roar::Decorator
4
+
5
+ include Roar::Representer::JSON
6
+
7
+ property :username, readable: false, writeable: false,
8
+ schema_info: { required: true }
9
+ property :name, readable: true, writeable: true
10
+ property :email, readable: false, writeable: true
11
+ property :password_hash, readable: false, writeable: false
12
+
13
+ end
@@ -0,0 +1,5 @@
1
+ require 'representable/json'
2
+
3
+ class UserSearchRepresenter < OpenStax::Api::V1::AbstractSearchRepresenter
4
+ collection :items, inherit: true, class: User, decorator: UserRepresenter
5
+ end