naranya_ecm-sdk 0.0.42 → 0.0.43

Sign up to get free protection for your applications and to get access to all the features.
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