naranya_ecm-sdk 0.0.42 → 0.0.43

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rspec +3 -0
  4. data/Gemfile +1 -2
  5. data/lib/aasm/persistence/rest_persistence.rb +11 -6
  6. data/lib/naranya_ecm-sdk.rb +4 -65
  7. data/lib/naranya_ecm-sdk/version.rb +1 -1
  8. data/lib/naranya_ecm/models/media_resource.rb +1 -1
  9. data/lib/naranya_ecm/models/notification.rb +2 -2
  10. data/lib/naranya_ecm/rest/errors.rb +16 -3
  11. data/lib/naranya_ecm/rest/finder_methods.rb +4 -4
  12. data/lib/naranya_ecm/rest/model.rb +87 -19
  13. data/lib/naranya_ecm/rest/persistence.rb +234 -46
  14. data/lib/naranya_ecm/rest/relation.rb +15 -5
  15. data/lib/naranya_ecm/search/results.rb +19 -17
  16. data/lib/ncontent-sdk-testing.rb +13 -0
  17. data/lib/ncontent-sdk.rb +85 -0
  18. data/lib/ncontent/sdk/config.rb +75 -0
  19. data/lib/ncontent/sdk/faraday_middleware.rb +4 -0
  20. data/lib/ncontent/sdk/faraday_middleware/required_response_format.rb +14 -0
  21. data/lib/ncontent/sdk/faraday_middleware/response_parser.rb +35 -0
  22. data/lib/ncontent/sdk/faraday_middleware/rest_api_call_benchmark.rb +49 -0
  23. data/lib/ncontent/sdk/railtie.rb +59 -0
  24. data/lib/ncontent/sdk/rest_client.rb +131 -0
  25. data/lib/ncontent/sdk/testing/server_mock.rb +316 -0
  26. data/naranya_ecm-sdk.gemspec +24 -10
  27. metadata +85 -68
  28. data/lib/naranya_ecm/rest/client.rb +0 -92
  29. data/spec/models/category_spec.rb +0 -16
  30. data/spec/models/content_spec.rb +0 -20
  31. data/spec/models/content_version_spec.rb +0 -7
  32. data/spec/models/download_authorization.rb +0 -7
  33. data/spec/models/media_spec.rb +0 -7
  34. data/spec/models/module_spec.rb +0 -18
  35. data/spec/spec_helper.rb +0 -47
  36. data/spec/support/naranya_ecms_shared_specs.rb +0 -21
@@ -0,0 +1,131 @@
1
+ require 'oauth2'
2
+ require 'ncontent/sdk/faraday_middleware'
3
+
4
+ module NContent
5
+
6
+ module SDK
7
+
8
+ class RESTClient
9
+
10
+ CLIENT_TOKEN_CACHE_KEY = "ncontent/api_client/client_token".freeze
11
+
12
+ class << self
13
+ delegate :config, :logger, :cache, to: NContent::SDK
14
+ end
15
+
16
+ # Returns the thread's OAuth2 client instance.
17
+ #
18
+ # In multi-thread environments, each thread will have it's own client
19
+ # instance object - since it's saved as a @instance_variable - but
20
+ # shouldn't be a problem since the mutable data is actually the
21
+ # client_token (see self.client_token).
22
+ def self.instance
23
+ @instance ||= OAuth2::Client.new(
24
+ config.api_key, config.api_secret, site: config.api_host
25
+ ) do |faraday|
26
+ ### Oauth2 Client (Faraday) builder:
27
+
28
+ faraday.request :url_encoded # them posts...
29
+
30
+ faraday.response :logger, logger
31
+
32
+ faraday.use NContent::SDK::FaradayMiddleware::RESTAPICallBenchmark
33
+
34
+ faraday.use NContent::SDK::FaradayMiddleware::ResponseParser
35
+
36
+ if defined?(Patron)
37
+ faraday.adapter :patron # Prefer patron if exists
38
+ else
39
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
40
+ end
41
+ end
42
+ end
43
+
44
+ # Returns a client_token initialized from cache.
45
+ #
46
+ # In multi-thread environments, each thread will have it's own
47
+ # client_token object - since it's saved as a @instance_variable -, but
48
+ # all of them should have the same token data - since it's initialized
49
+ # from cache (We're assuming a thread-safe cache such as redis gem).
50
+ def self.client_token
51
+ @client_token ||= begin
52
+ cached_token_data = cache.read CLIENT_TOKEN_CACHE_KEY
53
+ if cached_token_data.present?
54
+ OAuth2::AccessToken.from_hash instance, cached_token_data
55
+ else
56
+ new_token = instance.client_credentials.get_token
57
+ cache.write CLIENT_TOKEN_CACHE_KEY, new_token.to_hash
58
+ new_token
59
+ end
60
+ end
61
+ end
62
+
63
+ # Clears the thread's token object. If called with 'true', it also
64
+ # clears the token data from the cache.
65
+ def self.clear_client_token!(clear_from_cache = false)
66
+ cache.delete CLIENT_TOKEN_CACHE_KEY if clear_from_cache
67
+ @client_token = nil
68
+ true
69
+ end
70
+
71
+ # Make a request to the API:
72
+ #
73
+ # @param [Symbol] verb the HTTP request method
74
+ # @param [String] path the HTTP URL path of the request
75
+ # @param [Hash] opts the options to make the request with
76
+ def self.request(verb, path=nil, opts = {}, &block)
77
+
78
+ cleared_cached_token_data = false
79
+
80
+ begin
81
+
82
+ # If we beforehand know that the current token expired then clear the
83
+ # token object from the current thread. We'll not clear it from the
84
+ # cache store here, as maybe another thread has already updated the
85
+ # cached token data:
86
+ clear_client_token! if client_token.expired?
87
+
88
+ client_token.request(verb, path, opts) do |req|
89
+ req.options.timeout = config.api_call_timeout
90
+ req.options.open_timeout = config.api_call_open_timeout
91
+ yield req if block_given?
92
+ end
93
+
94
+ rescue OAuth2::Error => error
95
+
96
+ if error.response.status == 401 && !cleared_cached_token_data
97
+ # Here the cached token data is stale, so we'll force the cached
98
+ # token data to be cleared:
99
+ cleared_cached_token_data = clear_client_token!(true)
100
+ retry
101
+ else
102
+ ::NaranyaEcm::Rest::RestError.raise_by_failed_response(error.response)
103
+ end
104
+ end
105
+ end
106
+
107
+ def self.get(path=nil, opts = {}, &block)
108
+ request(:get, path, opts, &block)
109
+ end
110
+
111
+ def self.post(path=nil, opts = {}, &block)
112
+ request(:post, path, opts, &block)
113
+ end
114
+
115
+ def self.put(path=nil, opts = {}, &block)
116
+ request(:put, path, opts, &block)
117
+ end
118
+
119
+ def self.patch(path=nil, opts = {}, &block)
120
+ request(:patch, path, opts, &block)
121
+ end
122
+
123
+ def self.delete(path=nil, opts = {}, &block)
124
+ request(:delete, path, opts, &block)
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,316 @@
1
+ require 'sinatra/base'
2
+ require 'bson'
3
+
4
+ module NContent
5
+ module SDK
6
+ module Testing
7
+
8
+ ##
9
+ # Clase que simula el servicio de nContent:
10
+ # Ver https://robots.thoughtbot.com/how-to-stub-external-services-in-tests
11
+ class ServerMock < Sinatra::Base
12
+
13
+ class ValidationError < StandardError
14
+ attr_reader :errors
15
+ def initialize(errors = {})
16
+ @errors = errors.collect({}) do |hsh, keyval|
17
+ key, val = keyval
18
+ hsh[key] = [] unless hsh[key].is_a? Array
19
+ hsh[key] << val
20
+ hsh
21
+ end
22
+ end
23
+ end
24
+
25
+ delegate :config, to: :class
26
+
27
+ class << self
28
+ delegate :config, to: NContent::SDK
29
+ end
30
+
31
+ COLLECTION_NAMES = %w(
32
+ categories content_versions contents download_authorizations
33
+ media_processes media_profiles media_resources
34
+ ).freeze
35
+
36
+ RESOURCE_TYPES = COLLECTION_NAMES.map(&:singularize).freeze
37
+
38
+ def self.get_blank_collection_set
39
+ COLLECTION_NAMES.inject({}) do |hash, collection_name|
40
+ hash[collection_name] = {}
41
+ hash
42
+ end.with_indifferent_access
43
+ end
44
+
45
+ def self.clear_collections!
46
+ @collections = nil
47
+ end
48
+
49
+ def self.collections
50
+ @collections ||= get_blank_collection_set
51
+ end
52
+
53
+ def self.method_missing(method_name, *args, &block)
54
+ if COLLECTION_NAMES.include? method_name.to_s
55
+
56
+ # Create the method:
57
+ define_singleton_method method_name do
58
+ collections[method_name.to_s]
59
+ end
60
+
61
+ # Call the newly defined method:
62
+ self.send method_name, *args
63
+ elsif method_name =~ /\Aget_(\w+)\z/ && RESOURCE_TYPES.include?($1)
64
+
65
+ # Create the method:
66
+ define_singleton_method method_name do |resource_id|
67
+ get_resource $1, resource_id
68
+ end
69
+
70
+ # Call the newly defined method:
71
+ self.send method_name, *args
72
+ elsif method_name =~ /\A(create|update|upsert)_(\w+)\z/ && RESOURCE_TYPES.include?($2)
73
+
74
+ # Create the methods:
75
+ %w(create update upsert).each do |method_prefix|
76
+ new_method_name = "#{method_prefix}_#{$2}".to_sym
77
+ define_singleton_method new_method_name do |*args|
78
+ upsert_resource $2.pluralize, *args
79
+ end
80
+ end
81
+
82
+ # Call the newly defined method:
83
+ self.send method_name, *args
84
+ else
85
+ super # You *must* call super if you don't handle the
86
+ # method, otherwise you'll mess up Ruby's method
87
+ # lookup.
88
+ end
89
+ end
90
+
91
+ def self.upsert_resource(collection_name, given_resource_attributes, resource_id = nil)
92
+ # Ignore the ID:
93
+ given_resource_attributes.delete 'id'
94
+
95
+ invalid_attributes = given_resource_attributes.select do |k, v|
96
+ v =~ /invalid/i
97
+ end
98
+
99
+ raise ValidationError, invalid_attributes.collect {} do |h, kv|
100
+ k, v = kv;
101
+ h[k] = ['is invalid']
102
+ h
103
+ end if invalid_attributes.any?
104
+
105
+ if resource_id.present? && collections[collection_name].key?(resource_id)
106
+ collections[collection_name][resource_id].merge! given_resource_attributes
107
+ else
108
+ # Caso especial:
109
+ if collection_name == 'contents'
110
+ given_resource_attributes.reverse_merge "lifecycle_state" => 'draft'
111
+
112
+ # Generar iconos y screenshots en caso de que sea Game:
113
+ if given_resource_attributes['type'] == "Game"
114
+ icon = self.upsert_media_resource(
115
+ roles: ["icon"],
116
+ downloadable_url: "http://test-bucket.s3.amazonaws.com/icons/original/#{resource_id}.jpg",
117
+ type: "image/jpg",
118
+ access_type: "public",
119
+ parent_document_id: resource_id,
120
+ parent_document_type: "Content"
121
+ )
122
+
123
+ %w(120w 150w 300w).each do |processed_role|
124
+ self.upsert_media_resource(
125
+ roles: ["icon", "processed", "v2", processed_role],
126
+ downloadable_url: "http://test-bucket.s3.amazonaws.com/icons/#{processed_role}/#{resource_id}.jpg",
127
+ type: "image/jpg",
128
+ access_type: "public",
129
+ parent_document_id: icon[:id],
130
+ parent_document_type: "MediaResource"
131
+ )
132
+ end
133
+
134
+ end
135
+ end
136
+
137
+ resource_id = (BSON::ObjectId.new).to_s
138
+ collections[collection_name][resource_id] = { id: resource_id }.merge(
139
+ given_resource_attributes
140
+ ).with_indifferent_access
141
+ end
142
+
143
+ collections[collection_name][resource_id]
144
+ end
145
+
146
+ # Éste método es útil para llamar "stub_request", usando el host+port configurado por ENV["NARANYA_ECM_SITE"]
147
+ def self.url_matcher
148
+ @url_matcher ||= begin
149
+ site_url = URI config.api_host
150
+ pattern_string = site_url.host
151
+ pattern_string += ":#{site_url.port}" unless site_url.port == 80
152
+ Regexp.new pattern_string, "i"
153
+ end
154
+ end
155
+
156
+ def self.get_resource(resource_type, resource_id)
157
+ collection_name = resource_type.pluralize
158
+ requested_resource = self.collections[collection_name.to_s].fetch resource_id, nil
159
+
160
+ # Traerse los MR's:
161
+ requested_resource[:media_resources] = self.media_resources.values.select do |mr|
162
+ mr[:parent_document_type] == "Content" && mr[:parent_document_id] == resource_id
163
+ end.map do |mr|
164
+ mr[:media_resources] = self.media_resources.values.select do |c_mr|
165
+ c_mr[:parent_document_type] == "MediaResource" && c_mr[:parent_document_id] == mr[:id]
166
+ end
167
+ mr
168
+ end if requested_resource.present? && collection_name == 'contents'
169
+
170
+ requested_resource
171
+ end
172
+
173
+ def self.get_resource_headers(collection_name, resource_id)
174
+ raise "no collection name given" unless collection_name.present?
175
+ raise "no resource id given" unless resource_id.present?
176
+
177
+ headers = {}
178
+ headers = {
179
+ "X-Frame-Options" => "SAMEORIGIN",
180
+ "X-Xss-Protection" => "1; mode=block",
181
+ "X-Content-Type-Options" => "nosniff",
182
+ "Location" => "http://ecm.naranya.net:5000/#{collection_name}/#{resource_id}",
183
+ "Content-Type" => "application/json; charset=utf-8",
184
+ "Vary" => "Accept-Encoding",
185
+ "Etag" => '"a547f3ead872de01928f79724390aba4"',
186
+ "Cache-Control" => "max-age=0, private, must-revalidate",
187
+ "Set-Cookie" => "request_method=POST; path=/",
188
+ "X-Request-Id" => "a6463225-df54-450d-b571-7068f3c96364",
189
+ "X-Runtime" => "'0.241093'",
190
+ "Transfer-Encoding" => "chunked"
191
+ } if self.collections[collection_name].key? resource_id
192
+ end
193
+
194
+ delegate :collections, :get_resource, :get_resource_headers,
195
+ :upsert_resource, to: :class
196
+
197
+ # Servicio de token de oauth:
198
+ # Responderá con un token "fake" de prueba...
199
+ post "/oauth/token" do
200
+ case params[:grant_type]
201
+ when "client_credentials"
202
+ status 201
203
+
204
+ headers \
205
+ "X-Frame-Options" => "SAMEORIGIN",
206
+ "X-XSS-Protection" => "1; mode=block",
207
+ "X-Content-Type-Options" => "nosniff",
208
+ "Cache-Control" => "no-store",
209
+ "Pragma" => "no-cache",
210
+ "Content-Type" => "application/json; charset=utf-8",
211
+ "Vary" => "Accept-Encoding",
212
+ "ETag" => '"75e236bc0e47293b0bfbb7a5d16a06c3"',
213
+ "X-Request-Id" => "f4d38d78-84b9-4c41-8943-cf2c6f519061",
214
+ "X-Runtime" => "0.015009",
215
+ "Transfer-Encoding" => "chunked"
216
+
217
+ body ActiveSupport::JSON.encode(
218
+ access_token: "NContentClientTestAccessTokenA",
219
+ refresh_token: "NContentClientTestRefreshToken",
220
+ token_type: "bearer",
221
+ expires_in: 7200
222
+ )
223
+ else
224
+ status 401
225
+ end
226
+
227
+ end
228
+
229
+ get '/:collection_name.json' do
230
+ status 200
231
+
232
+ headers \
233
+ "X-Frame-Options" => "SAMEORIGIN",
234
+ "X-Xss-Protection" => "1; mode=block",
235
+ "X-Content-Type-Options" => "nosniff",
236
+ "Content-Type" => "application/json; charset=utf-8",
237
+ "Vary" => "Accept-Encoding",
238
+ "Etag" => '"d751713988987e9331980363e24189ce"',
239
+ "Cache-Control" => "max-age=0, private, must-revalidate",
240
+ "Set-Cookie" => "request_method=GET; path=/",
241
+ "X-Request-Id" => "4fe9e39f-2b2d-400f-9da7-0b9ad611ce26",
242
+ "X-Runtime" => "'0.019181'",
243
+ "Transfer-Encoding" => "chunked"
244
+
245
+ body ActiveSupport::JSON.encode collections[params[:collection_name]].values
246
+
247
+ end
248
+
249
+ post "/:collection_name.json" do
250
+ collection_name = params[:collection_name]
251
+ params = ActiveSupport::JSON.decode request.body.read
252
+ attributes = params[collection_name.singularize]
253
+
254
+ if attributes.present?
255
+ begin
256
+ resource = upsert_resource collection_name, attributes
257
+ status 201
258
+ headers get_resource_headers collection_name, resource[:id]
259
+ body ActiveSupport::JSON.encode resource
260
+ rescue ValidationError => e
261
+ status 422
262
+ body ActiveSupport::JSON.encode e.errors
263
+ end
264
+ else
265
+ status 422
266
+ end
267
+
268
+ end
269
+
270
+ get "/:collection_name/:id.json" do
271
+ resource = get_resource params[:collection_name], params[:id]
272
+ if resource.present?
273
+ status 201
274
+ headers get_resource_headers params[:collection_name], resource[:id]
275
+ body ActiveSupport::JSON.encode resource
276
+ else
277
+ status 404
278
+ end
279
+ end
280
+
281
+ put '/:collection_name/:id.json' do
282
+
283
+ collection_name = params[:collection_name]
284
+ resource_id = params[:id]
285
+
286
+ if collection_name != 'download_authorizations' and resource = get_resource(collection_name, resource_id)
287
+
288
+ params = ActiveSupport::JSON.decode(request.body.read)
289
+
290
+ if attributes = params[collection_name.singularize]
291
+ begin
292
+ resource = upsert_resource collection_name, attributes, resource_id
293
+ status 200
294
+ headers get_resource_headers collection_name, resource[:id]
295
+ body ActiveSupport::JSON.encode resource
296
+ rescue ValidationError => e
297
+ status 422
298
+ body ActiveSupport::JSON.encode e.errors
299
+ end
300
+ else
301
+ status 422
302
+ end
303
+
304
+ else
305
+ status 404
306
+ end
307
+ end
308
+
309
+ get // do
310
+ byebug
311
+ end
312
+ end
313
+
314
+ end
315
+ end
316
+ end
@@ -1,7 +1,6 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
-
5
4
  require "naranya_ecm-sdk/version"
6
5
 
7
6
  Gem::Specification.new do |spec|
@@ -9,26 +8,41 @@ Gem::Specification.new do |spec|
9
8
  spec.version = NaranyaEcm::VERSION
10
9
  spec.authors = ["Roberto Quintanilla"]
11
10
  spec.email = ["roberto.quintanilla@naranya.com"]
11
+
12
12
  spec.summary = %q{Cliente Ruby de NaranyaEcm}
13
13
  spec.description = %q{Cliente Ruby de NaranyaEcm para aplicaciones cliente.}
14
14
  spec.homepage = "http://naranya.com"
15
15
  spec.license = "MIT"
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.bindir = "exe"
20
28
  spec.require_paths = ["lib"]
21
29
 
22
30
  spec.add_dependency "activesupport", "~> 4.0", ">= 4.0.5"
23
31
  spec.add_dependency "activemodel", "~> 4.0", ">= 4.0.5"
24
- spec.add_dependency "httparty", "~> 0.13", ">= 0.13.1"
32
+
33
+ spec.add_dependency "oauth2", "~> 1.0"
34
+ # which uses Faraday as it's client... we get to choose which HTTP driver to use!
35
+
25
36
  spec.add_dependency "aasm", "~> 3.2", ">= 3.2.1"
26
37
  spec.add_dependency "fog-core", "1.25.0"
27
38
  spec.add_dependency "fog", "~> 1.22", ">= 1.22.1"
28
39
 
29
- spec.add_development_dependency "bundler", "~> 1.6"
30
- spec.add_development_dependency "rake", "~> 10.3"
31
- spec.add_development_dependency "rspec", "~> 3.0"
32
- spec.add_development_dependency "vcr", "~> 2.9"
33
- spec.add_development_dependency "webmock", "~> 1.18"
40
+ spec.add_development_dependency "bundler", "~> 1.6"
41
+ spec.add_development_dependency "rake", "~> 10.3"
42
+ spec.add_development_dependency "factory_girl", "~> 4.5"
43
+ spec.add_development_dependency "rspec", "~> 3.2"
44
+ spec.add_development_dependency "sinatra", "~> 1.4"
45
+ spec.add_development_dependency "bson", "~> 3.0"
46
+
47
+ spec.add_development_dependency "webmock", "~> 1.18"
34
48
  end