openstax_exchange 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +3 -1
- data/README.md +58 -20
- data/Rakefile +5 -13
- data/exchange-client.gemspec +30 -0
- data/lib/openstax/exchange/client_instance.rb +23 -0
- data/lib/openstax/exchange/configuration.rb +12 -0
- data/lib/openstax/exchange/exceptions.rb +21 -0
- data/lib/openstax/exchange/exchange.rb +65 -0
- data/lib/openstax/exchange/fake_client/configuration.rb +13 -0
- data/lib/openstax/exchange/fake_client/fake_client.rb +71 -0
- data/lib/openstax/exchange/real_client/real_client.rb +76 -0
- data/lib/openstax/exchange/version.rb +1 -1
- data/lib/openstax_exchange.rb +8 -112
- data/spec/cassettes/OpenStax_Exchange/client_instance_configuration/can_be_configured_to_use_a_real_exchange_client.yml +48 -0
- data/spec/cassettes/OpenStax_Exchange/internal_client_instance/_client_defaults_to_a_real_exchange_client.yml +48 -0
- data/spec/cassettes/OpenStax_Exchange/internal_client_instance/_reset_causes_a_new_client_object_to_be_returned_by_client.yml +48 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_create_identifier/success/creates_a_distinct_identifer_per_invokation.yml +146 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_create_identifier/success/creates_and_returns_a_new_identifier.yml +97 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_initialize/invalid_platform_id/raises_an_exception.yml +52 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_initialize/invalid_platform_secret/raises_an_exception.yml +52 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_initialize/success/initializes_the_authentication_token.yml +48 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/duplicate_identifer_resource_trial_triplet/raises_an_exception.yml +194 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/invalid_resource_string/raises_an_exception.yml +145 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/success/allows_answers_with_distinct_identifiers_to_be_saved.yml +244 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/success/allows_answers_with_distinct_resources_to_be_saved.yml +195 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/success/allows_answers_with_distinct_trials_to_be_saved.yml +195 -0
- data/spec/cassettes/OpenStax_Exchange_RealClient/behaves_like_exchange_client_api_v1/_record_multiple_choice_answer/success/creates_a_multiple_choice_answer_associated_with_the_given_identifier.yml +146 -0
- data/spec/lib/openstax/exchange/client_configuration_spec.rb +58 -0
- data/spec/lib/openstax/exchange/fake_client_v1_spec.rb +14 -0
- data/spec/lib/openstax/exchange/real_client_v1_spec.rb +9 -0
- data/spec/lib/openstax/exchange/shared_examples_for_exchange_client_v1.rb +208 -0
- data/spec/spec_helper.rb +52 -38
- metadata +97 -100
- checksums.yaml +0 -15
- data/spec/dummy/README.md +0 -1
- data/spec/dummy/Rakefile +0 -7
- data/spec/dummy/app/assets/javascripts/application.js +0 -15
- data/spec/dummy/app/assets/stylesheets/application.css +0 -13
- data/spec/dummy/app/controllers/api/dummy_controller.rb +0 -11
- data/spec/dummy/app/controllers/api/events_controller.rb +0 -7
- data/spec/dummy/app/controllers/application_controller.rb +0 -7
- data/spec/dummy/app/controllers/oauth_controller.rb +0 -8
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -55
- data/spec/dummy/config/boot.rb +0 -10
- data/spec/dummy/config/database.yml +0 -25
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -29
- data/spec/dummy/config/environments/production.rb +0 -69
- data/spec/dummy/config/environments/test.rb +0 -35
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/inflections.rb +0 -15
- data/spec/dummy/config/initializers/mime_types.rb +0 -5
- data/spec/dummy/config/initializers/openstax_exchange.rb +0 -10
- data/spec/dummy/config/initializers/secret_token.rb +0 -7
- data/spec/dummy/config/initializers/session_store.rb +0 -8
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/en.yml +0 -5
- data/spec/dummy/config/routes.rb +0 -11
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +0 -16
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -929
- data/spec/dummy/log/test.log +0 -46753
- data/spec/dummy/public/404.html +0 -26
- data/spec/dummy/public/422.html +0 -26
- data/spec/dummy/public/500.html +0 -25
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +0 -6
- data/spec/lib/openstax_exchange_spec.rb +0 -23
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format documentation
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
exchange-ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3-p545
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
exchange-ruby
|
2
2
|
=============
|
3
3
|
|
4
|
-
[![Gem Version](https://badge.fury.io/rb/
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/openstax_exchange.svg)](http://badge.fury.io/rb/openstax_exchange)
|
5
5
|
[![Build Status](https://travis-ci.org/openstax/exchange-ruby.svg?branch=master)](https://travis-ci.org/openstax/exchange-ruby)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/openstax/exchange-ruby.png)](https://codeclimate.com/github/openstax/exchange-ruby)
|
7
7
|
|
@@ -10,35 +10,73 @@ A ruby client for interfacing with the OpenStax Exchange API.
|
|
10
10
|
Usage
|
11
11
|
-----
|
12
12
|
|
13
|
-
|
13
|
+
Include the gem in your project:
|
14
|
+
```rb
|
15
|
+
gem 'openstax_exchange'
|
16
|
+
```
|
17
|
+
|
18
|
+
Include the following in your script:
|
19
|
+
|
20
|
+
```rb
|
21
|
+
require 'openstax_exchange'
|
22
|
+
```
|
14
23
|
|
15
|
-
|
16
|
-
with at least the following contents:
|
24
|
+
Configure the client's knowledge of the Exchange server:
|
17
25
|
|
18
26
|
```rb
|
19
27
|
OpenStax::Exchange.configure do |config|
|
20
|
-
config.
|
21
|
-
config.
|
28
|
+
config.client_platform_id = '123'
|
29
|
+
config.client_platform_secret = 'abc' ## do not check real secrets into version control!
|
30
|
+
config.client_server_url = 'http://www.example.com:3000/base/path'
|
31
|
+
config.client_api_version = 'v1'
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
By default the real Exchange client will be used. However, the choice can be made explicitly by using the following:
|
36
|
+
|
37
|
+
```rb
|
38
|
+
OpenStax::Exchange.use_real_client
|
39
|
+
OpenStax::Exchange.use_fake_client
|
40
|
+
```
|
41
|
+
|
42
|
+
If using the fake client, configure the faked server settings:
|
43
|
+
|
44
|
+
```rb
|
45
|
+
OpenStax::Exchange::FakeClient.configure do |config|
|
46
|
+
config.registered_platforms = {'123' => 'abc'}
|
47
|
+
config.server_url = 'http://www.example.com:3000/base/path'
|
48
|
+
config.supported_api_versions = ['v1']
|
22
49
|
end
|
23
50
|
```
|
24
51
|
|
25
|
-
|
26
|
-
|
52
|
+
After changing the client configuration, use:
|
53
|
+
```rb
|
54
|
+
OpenStax::Exchange.reset!
|
55
|
+
```
|
56
|
+
to ensure that the changes take effect.
|
27
57
|
|
58
|
+
The following Exchange API methods are currently supported:
|
28
59
|
```rb
|
29
|
-
|
60
|
+
identifier = OpenStax::Exchange.create_identifier
|
30
61
|
```
|
62
|
+
```rb
|
63
|
+
response = OpenStax::Exchange.record_multiple_choice_answer(identifier, resource_uri, trial, answer)
|
64
|
+
```
|
65
|
+
|
66
|
+
Running the Specs
|
67
|
+
-----------------
|
31
68
|
|
32
|
-
|
33
|
-
|
69
|
+
Create a local clone of the repo, and run the following commands:
|
70
|
+
```rb
|
71
|
+
bundle install
|
72
|
+
bundle exec rake
|
73
|
+
```
|
74
|
+
The result should be a set of passing specs.
|
34
75
|
|
35
|
-
|
36
|
-
the OpenStax Exchange API.
|
76
|
+
## Contributing
|
37
77
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
plus the optional values :api_version (to specify an API version) and
|
44
|
-
:access_token (to specify an OAuth access token).
|
78
|
+
1. Fork it ( https://github.com/[my-github-username]/exchange-ruby/fork )
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create a new Pull Request
|
data/Rakefile
CHANGED
@@ -1,22 +1,14 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
|
-
|
3
1
|
begin
|
4
2
|
require 'bundler/setup'
|
5
3
|
rescue LoadError
|
6
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
5
|
end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Bundler::GemHelper.install_tasks
|
13
|
-
|
14
|
-
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
7
|
+
require "bundler/gem_tasks"
|
8
|
+
require "rspec/core/rake_task"
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
desc 'Run all specs in spec directory (excluding plugin specs)'
|
20
|
-
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
11
|
+
task.rspec_opts = ['--color', '--format', 'documentation']
|
12
|
+
end
|
21
13
|
|
22
14
|
task :default => :spec
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'openstax/exchange/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "openstax_exchange"
|
8
|
+
spec.version = OpenStax::Exchange::VERSION
|
9
|
+
spec.authors = ["Kevin Burleigh"]
|
10
|
+
spec.email = ["klb@kindlinglabs"]
|
11
|
+
spec.summary = %q{Ruby client for OpenStax Exchange}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = "http://github.com/openstax/exchange-ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "oauth2", ">= 0.5.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "simplecov"
|
28
|
+
spec.add_development_dependency "vcr"
|
29
|
+
spec.add_development_dependency "webmock"
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Exchange
|
3
|
+
module ClientInstance
|
4
|
+
|
5
|
+
def is_real_client?
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def token
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_identifier
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def record_multiple_choice_answer(identifier, resource, trial, answer)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Exchange
|
3
|
+
|
4
|
+
class ClientError < StandardError
|
5
|
+
def initialize(msg, original=$!)
|
6
|
+
super(msg)
|
7
|
+
@original = original
|
8
|
+
set_backtrace(original.backtrace)
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
[super, @original.inspect].join(" ")
|
13
|
+
end
|
14
|
+
|
15
|
+
def message
|
16
|
+
[super, "(#{@original.message})"].join(" ")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module OpenStax
|
2
|
+
module Exchange
|
3
|
+
|
4
|
+
@use_real_client = true
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
yield configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configuration
|
11
|
+
@configuration ||= Configuration.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.reset!
|
15
|
+
@client = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.use_real_client
|
19
|
+
@use_real_client = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.use_fake_client
|
23
|
+
@use_real_client = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.use_real_client?
|
27
|
+
!!@use_real_client
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.create_identifier
|
31
|
+
begin
|
32
|
+
client.create_identifier
|
33
|
+
rescue StandardError => error
|
34
|
+
raise ClientError.new("create_identifier failure", error)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.record_multiple_choice_answer(*args)
|
39
|
+
begin
|
40
|
+
client.record_multiple_choice_answer(*args)
|
41
|
+
rescue StandardError => error
|
42
|
+
raise ClientError.new("record_multiple_choice_answer failure", error)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.client
|
49
|
+
begin
|
50
|
+
@client ||= create_client
|
51
|
+
rescue StandardError => error
|
52
|
+
raise ClientError.new("initialization failure", error)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.create_client
|
57
|
+
if use_real_client?
|
58
|
+
RealClient.new configuration
|
59
|
+
else
|
60
|
+
FakeClient.new configuration
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module OpenStax
|
5
|
+
module Exchange
|
6
|
+
|
7
|
+
class FakeClient
|
8
|
+
include ClientInstance
|
9
|
+
|
10
|
+
def self.configure
|
11
|
+
yield configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(exchange_configuration)
|
19
|
+
@client_config_platform_id = exchange_configuration.client_platform_id
|
20
|
+
@client_config_platform_secret = exchange_configuration.client_platform_secret
|
21
|
+
@client_config_server_url = exchange_configuration.client_server_url
|
22
|
+
@client_config_api_version = exchange_configuration.client_api_version
|
23
|
+
|
24
|
+
@server_registered_platforms = self.class.configuration.registered_platforms
|
25
|
+
@server_server_url = self.class.configuration.server_url
|
26
|
+
|
27
|
+
raise "invalid server url" \
|
28
|
+
unless @client_config_server_url == @server_server_url
|
29
|
+
|
30
|
+
raise "invalid platform credentials" \
|
31
|
+
unless @server_registered_platforms.fetch(@client_config_platform_id) == @client_config_platform_secret
|
32
|
+
|
33
|
+
@token = SecureRandom.hex(64)
|
34
|
+
@multiple_choice_responses = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def is_real_client?
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def token
|
42
|
+
@token
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_identifier
|
46
|
+
SecureRandom.hex(64)
|
47
|
+
end
|
48
|
+
|
49
|
+
def record_multiple_choice_answer(identifier, resource, trial, answer)
|
50
|
+
@multiple_choice_responses[identifier] ||= {}
|
51
|
+
@multiple_choice_responses[identifier][resource] ||= {}
|
52
|
+
|
53
|
+
raise "invalid resource" \
|
54
|
+
unless URI(resource).host == "exercises.openstax.org"
|
55
|
+
|
56
|
+
raise "duplicate response for (identifier,resource,trial) triplet" \
|
57
|
+
if @multiple_choice_responses[identifier][resource][trial]
|
58
|
+
|
59
|
+
@multiple_choice_responses[identifier][resource][trial] = answer
|
60
|
+
|
61
|
+
return {
|
62
|
+
'identifier' => identifier,
|
63
|
+
'resource' => resource,
|
64
|
+
'trial' => trial,
|
65
|
+
'answer' => answer
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module OpenStax
|
4
|
+
module Exchange
|
5
|
+
|
6
|
+
class RealClient
|
7
|
+
include ClientInstance
|
8
|
+
|
9
|
+
def initialize(exchange_configuration)
|
10
|
+
@platform_id = exchange_configuration.client_platform_id
|
11
|
+
@platform_secret = exchange_configuration.client_platform_secret
|
12
|
+
@server_url = exchange_configuration.client_server_url
|
13
|
+
@api_version = exchange_configuration.client_api_version
|
14
|
+
|
15
|
+
@oauth_client = OAuth2::Client.new(
|
16
|
+
@platform_id,
|
17
|
+
@platform_secret,
|
18
|
+
site: @server_url)
|
19
|
+
|
20
|
+
@oauth_token = @oauth_client.client_credentials.get_token
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_real_client?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def token
|
28
|
+
@oauth_token.token
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_identifier
|
32
|
+
options = {}
|
33
|
+
add_accept_header! options
|
34
|
+
|
35
|
+
response = @oauth_token.request(
|
36
|
+
:post,
|
37
|
+
"#{@server_url}/api/identifiers",
|
38
|
+
options)
|
39
|
+
|
40
|
+
return JSON.parse(response.body)['identifier']
|
41
|
+
end
|
42
|
+
|
43
|
+
def record_multiple_choice_answer(identifier, resource, trial, answer)
|
44
|
+
options = {}
|
45
|
+
add_accept_header! options
|
46
|
+
add_authorization_header! options
|
47
|
+
|
48
|
+
options[:body] = { identifier: identifier, resource: resource, trial: trial, answer: answer }.to_json
|
49
|
+
|
50
|
+
response = @oauth_token.request(
|
51
|
+
:post,
|
52
|
+
"#{@server_url}/api/events/platforms/multiple_choices",
|
53
|
+
options)
|
54
|
+
|
55
|
+
return JSON.parse(response.body)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def add_header_hash!(options)
|
61
|
+
options[:headers] = {} unless options.has_key? :headers
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_accept_header!(options)
|
65
|
+
add_header_hash! options
|
66
|
+
options[:headers].merge!({ 'Accept' => "application/vnd.exchange.openstax.#{@api_version}" })
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_authorization_header!(options)
|
70
|
+
add_header_hash! options
|
71
|
+
options[:headers].merge!({ 'Authorization' => "Bearer #{token}" })
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|