scorm_cloud 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +7 -0
  3. data/Rakefile +25 -0
  4. data/bin/scorm_cloud +6 -0
  5. data/features/api_bugs.feature +12 -0
  6. data/features/course_service.feature +19 -0
  7. data/features/debug_service.feature +12 -0
  8. data/features/registration_service.feature +21 -0
  9. data/features/step_definitions/scorm_cloud_steps.rb +184 -0
  10. data/features/support/env.rb +71 -0
  11. data/features/upload_service.feature +9 -0
  12. data/lib/scorm_cloud.rb +36 -0
  13. data/lib/scorm_cloud/base.rb +110 -0
  14. data/lib/scorm_cloud/base_object.rb +23 -0
  15. data/lib/scorm_cloud/base_service.rb +27 -0
  16. data/lib/scorm_cloud/connection.rb +67 -0
  17. data/lib/scorm_cloud/course.rb +13 -0
  18. data/lib/scorm_cloud/course_service.rb +53 -0
  19. data/lib/scorm_cloud/debug_service.rb +25 -0
  20. data/lib/scorm_cloud/dispatch_service.rb +9 -0
  21. data/lib/scorm_cloud/export_service.rb +7 -0
  22. data/lib/scorm_cloud/railtie.rb +13 -0
  23. data/lib/scorm_cloud/registration.rb +19 -0
  24. data/lib/scorm_cloud/registration_service.rb +50 -0
  25. data/lib/scorm_cloud/reporting_service.rb +7 -0
  26. data/lib/scorm_cloud/scorm_rails_helpers.rb +15 -0
  27. data/lib/scorm_cloud/tagging_service.rb +9 -0
  28. data/lib/scorm_cloud/upload_service.rb +37 -0
  29. data/lib/scorm_cloud/version.rb +3 -0
  30. data/readme.textile +53 -0
  31. data/scorm_cloud.gemspec +24 -0
  32. data/spec/apikey_template.rb +3 -0
  33. data/spec/course_service_spec.rb +31 -0
  34. data/spec/debug_service_spec.rb +26 -0
  35. data/spec/dispatch_service_spec.rb +21 -0
  36. data/spec/export_service_spec.rb +13 -0
  37. data/spec/registration_service_spec.rb +28 -0
  38. data/spec/reporting_service_spec.rb +11 -0
  39. data/spec/small_scorm_package.zip +0 -0
  40. data/spec/spec_helper.rb +4 -0
  41. data/spec/tagging_service_spec.rb +20 -0
  42. data/spec/upload_service_spec.rb +18 -0
  43. metadata +139 -0
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ spec/apikey.rb
6
+ .rspec
7
+
8
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rspec'
6
+ gem 'cucumber'
7
+
@@ -0,0 +1,25 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'cucumber/rake/task'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+ RSpec::Core::RakeTask.new('spec')
7
+
8
+ Cucumber::Rake::Task.new do |t|
9
+ t.cucumber_opts = "--tags ~@apibug"
10
+ end
11
+
12
+ namespace :cucumber do
13
+ Cucumber::Rake::Task.new('apibugs') do |t|
14
+ t.cucumber_opts = "--tags @apibug"
15
+ end
16
+ Cucumber::Rake::Task.new('wip') do |t|
17
+ t.cucumber_opts = "--tags @wip"
18
+ end
19
+ end
20
+
21
+ task :test do
22
+ [:spec, :cucumber].each { |t| Rake::Task[t].execute }
23
+ end
24
+
25
+ task :default => :test
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path('../../lib', __FILE__))
3
+ require 'scorm_cloud'
4
+ require 'scorm_cloud/CLI'
5
+ ScormCloud::CLI.start
6
+
@@ -0,0 +1,12 @@
1
+ @apibug
2
+ Feature: API Bugs and Questions
3
+
4
+ Scenario: Deleting a non-existant package should return an error
5
+ in this case it seems to return deleted=true even when the file
6
+ does not exist
7
+
8
+ When I delete a non-existant package
9
+ Then I should get an error
10
+
11
+
12
+ # Preview documentation doesn't list redirect URL as a parameter
@@ -0,0 +1,19 @@
1
+ Feature: Course Service Interface
2
+
3
+ Scenario: A user can import a previously uploaded course
4
+ When I import a course
5
+ Then there should be 1 course in the list
6
+ And the course should be in the course list
7
+ And the course should exist
8
+ And I can get a preview URL for the course
9
+ And I can get the manifest for the course
10
+ And I can get the attributes for the course
11
+
12
+ When I delete the course
13
+ Then there should be 0 courses in the list
14
+ Then the course should not be in the course list
15
+
16
+ Scenario: A user can update course attributes
17
+ When I import a course
18
+ And I update course attributes
19
+ Then the course attributes should be updated
@@ -0,0 +1,12 @@
1
+ Feature: Debug Service Interface
2
+
3
+ Scenario: Debug Services Function
4
+
5
+ When I ping the server
6
+ Then the response should be "pong"
7
+
8
+ When I authping the server
9
+ Then the response should be "pong"
10
+
11
+ When I get the current time
12
+ Then the resonse should be the current time
@@ -0,0 +1,21 @@
1
+ Feature: Registration Service
2
+
3
+ Background: Setup a course
4
+ Given a registered course
5
+
6
+ Scenario: Register a learner
7
+
8
+ When I register a learner
9
+ Then the learner should be in the registration list
10
+ And I can get the registration results
11
+
12
+ When I launch the course
13
+ Then I will get a valid url
14
+
15
+ When I reset the registration
16
+ Then the learner should be in the registration list
17
+ And I can get the registration results
18
+
19
+ When I delete the registration
20
+ Then the learner should not be in the registration list
21
+
@@ -0,0 +1,184 @@
1
+ ##
2
+ ## Generic
3
+ ##
4
+ Then /^I will get a valid url$/ do
5
+ @last_url.should match(/http\:\/\/.+/)
6
+ end
7
+
8
+
9
+ ##
10
+ ## Debug Service
11
+ ##
12
+
13
+ When /^I ping the server$/ do
14
+ @last_response = @c.debug.ping
15
+ end
16
+
17
+ When /^I authping the server$/ do
18
+ @last_response = @c.debug.auth_ping
19
+ end
20
+
21
+ When /^I get the current time$/ do
22
+ @last_response = @c.debug.get_time
23
+ end
24
+
25
+ Then /^the response should be "([^"]*)"$/ do |arg1| #"
26
+ @last_response.should eq(arg1)
27
+ end
28
+
29
+ Then /^the resonse should be the current time$/ do
30
+ @last_response.should match(/\d+/)
31
+ end
32
+
33
+ ##
34
+ ## Upload Service
35
+ ##
36
+
37
+ When /^I upload a package$/ do
38
+ token = @c.upload.get_upload_token
39
+ token.should_not be_nil
40
+ path = File.join(File.dirname(__FILE__), '..', '..', 'spec', 'small_scorm_package.zip')
41
+ @last_upload_path = @c.upload.upload_file(token, path)
42
+ @last_uploaded_dir, @last_uploaded_file = @last_upload_path.split('/')
43
+ @last_uploaded_file.should include('.zip')
44
+ end
45
+
46
+ Then /^the package files should be available$/ do
47
+ list = @c.upload.list_files.map { |f| f[:file] }
48
+ list.should include(@last_uploaded_file)
49
+ end
50
+
51
+ Then /^the package files should not be available$/ do
52
+ list = @c.upload.list_files.map { |f| f[:file] }
53
+ list.should_not include(@last_uploaded_file)
54
+ end
55
+
56
+ When /^I delete the package$/ do
57
+ @c.upload.delete_files(@last_uploaded_file)
58
+ end
59
+
60
+ When /^I delete a non\-existant package$/ do
61
+ @last_error = nil
62
+ begin
63
+ @c.upload.delete_files("thiscoursedoesnotexist")
64
+ rescue => e
65
+ puts e.inspect
66
+ @last_error = e
67
+ end
68
+ end
69
+
70
+ Then /^I should get an error$/ do
71
+ @last_error.should_not be_nil
72
+ end
73
+
74
+
75
+
76
+ ##
77
+ ## Course Service
78
+ ##
79
+
80
+ When /^I import a course$/ do
81
+ @last_uploaded_file.should_not be_nil
82
+ @last_course_id = "small_scorm_course_#{rand(1000)}"
83
+ hash = @c.course.import_course(@last_course_id, @last_uploaded_path)
84
+ hash.should_not be_nil
85
+ hash[:title].should_not be_nil
86
+ end
87
+
88
+ Given /^a registered course$/ do
89
+ When "I import a course"
90
+ end
91
+
92
+ Then /^the course should be in the course list$/ do
93
+ @c.course.get_course_list.find { |c| c.id == @last_course_id}.should_not be_nil
94
+ end
95
+
96
+ Then /^there should be (\d+) courses? in the list$/ do |count|
97
+ @c.course.get_course_list.size.should eq(count.to_i)
98
+ end
99
+
100
+ When /^I delete the course$/ do
101
+ @c.course.delete_course(@last_course_id).should eq(true)
102
+ end
103
+
104
+ Then /^the course should not be in the course list$/ do
105
+ @c.course.get_course_list.find { |c| c.id == @last_course_id}.should be_nil
106
+ end
107
+
108
+ Then /^I can get a preview URL for the course$/ do
109
+ @c.course.preview(@last_course_id, "http://www.example.com").should match(/http:\/\/.+/)
110
+ end
111
+
112
+ Then /^the course will have a properties url$/ do
113
+ @c.course.properties(@last_course_id).should match(/http:\/\/.+/)
114
+ end
115
+
116
+ Then /^the course should exist$/ do
117
+ @c.course.exists(@last_course_id).should be_true
118
+ end
119
+
120
+ Then /^I can get the manifest for the course$/ do
121
+ xml = @c.course.get_manifest(@last_course_id)
122
+ doc = REXML::Document.new(xml)
123
+ doc.elements["//manifest"].should_not be_nil
124
+ end
125
+
126
+ Then /^I can get the attributes for the course$/ do
127
+ h = @c.course.get_attributes(@last_course_id)
128
+ h.should be_kind_of(Hash)
129
+ h[:showProgressBar].should eq("false")
130
+ h[:showCourseStructure].should eq("false")
131
+ end
132
+
133
+ When /^I update course attributes$/ do
134
+ updated = @c.course.update_attributes(@last_course_id,
135
+ {:desiredHeight => "700", :commCommitFrequency => "59999" })
136
+ updated[:desiredHeight].should eq("700")
137
+ updated[:commCommitFrequency].should eq("59999")
138
+ end
139
+
140
+ Then /^the course attributes should be updated$/ do
141
+ h = @c.course.get_attributes(@last_course_id)
142
+ h.should be_kind_of(Hash)
143
+ h[:desiredHeight].should eq("700")
144
+ h[:commCommitFrequency].should eq("59999")
145
+ end
146
+
147
+
148
+ ##
149
+ ## Registration
150
+ ##
151
+
152
+ When /^I register a learner$/ do
153
+ @last_reg_id = "small_scorm_course_#{rand(1000)}"
154
+ r = @c.registration.create_registration(@last_course_id, @last_reg_id,
155
+ "fname", "lname", "lid", :email => "lid@example.com")
156
+ r.should be_true
157
+ end
158
+
159
+ Then /^the learner should be in the registration list$/ do
160
+ list = @c.registration.get_registration_list
161
+ list.find { |r| r.id == @last_reg_id }.should_not be_nil
162
+ end
163
+
164
+ Then /^the learner should not be in the registration list$/ do
165
+ list = @c.registration.get_registration_list
166
+ list.find { |r| r.id == @last_reg_id }.should be_nil
167
+ end
168
+
169
+ When /^I delete the registration$/ do
170
+ @c.registration.delete_registration(@last_reg_id).should be_true
171
+ end
172
+
173
+ When /^I reset the registration$/ do
174
+ @c.registration.reset_registration(@last_reg_id).should be_true
175
+ end
176
+
177
+ When /^I launch the course$/ do
178
+ @last_url = @c.registration.launch(@last_reg_id, "http://www.example.com/")
179
+ end
180
+
181
+ Then /^I can get the registration results$/ do
182
+ @reg_results = @c.registration.get_registration_result(@last_reg_id, "full")
183
+ @reg_results.should include('<rsp stat="ok"><registrationreport')
184
+ end
@@ -0,0 +1,71 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
11
+ require File.join(File.dirname(__FILE__), '/../../spec/apikey.rb')
12
+ require 'scorm_cloud'
13
+
14
+ require 'rspec/expectations'
15
+
16
+
17
+ ##
18
+ ## Cleanup before testing
19
+ ##
20
+ Before do
21
+
22
+ # Grab a connection
23
+ unless @c
24
+ @c = ScormCloud::ScormCloud.new($scorm_cloud_appid,$scorm_cloud_secret)
25
+ end
26
+
27
+ # Cleanup all courses
28
+ @c.course.get_course_list.each do |course|
29
+ @c.course.delete_course(course.id)
30
+ end
31
+ @c.course.get_course_list.count.should eq(0)
32
+
33
+ unless @last_uploaded_file
34
+
35
+ # Cleanup all zip packages
36
+ @c.upload.list_files.each do |file|
37
+ @c.upload.delete_files(file[:file])
38
+ end
39
+ raise "Cannot delete files" unless @c.upload.list_files.length == 0
40
+
41
+ # Upload one we can use for testing
42
+ token = @c.upload.get_upload_token
43
+ path = File.join(File.dirname(__FILE__), '..', '..', 'spec', 'small_scorm_package.zip')
44
+ @last_uploaded_path = @c.upload.upload_file(token, path)
45
+ @last_uploaded_dir, @last_uploaded_file = @last_uploaded_path.split('/')
46
+ @last_uploaded_file.should include('.zip')
47
+
48
+ sleep(5)
49
+
50
+ end
51
+
52
+ # was a course created?
53
+ @c.course.get_course_list.count.should eq(0)
54
+ @c.upload.list_files.count.should eq(1)
55
+
56
+ @last_course_id = nil
57
+ @last_error = nil
58
+ @last_response = nil
59
+
60
+ end
61
+
62
+
63
+ After do
64
+
65
+ # Cleanup all courses
66
+ @c.course.get_course_list.each do |course|
67
+ @c.course.delete_course(course.id)
68
+ end
69
+
70
+ end
71
+
@@ -0,0 +1,9 @@
1
+ Feature: Upload Service Interface
2
+
3
+ Scenario: Upload a course
4
+
5
+ When I upload a package
6
+ Then the package files should be available
7
+
8
+ When I delete the package
9
+ Then the package files should not be available
@@ -0,0 +1,36 @@
1
+ require 'rexml/document'
2
+ require 'digest/md5'
3
+ require 'net/http'
4
+ require 'net/http/post/multipart'
5
+ require 'uri'
6
+ require 'scorm_cloud/base'
7
+
8
+ require 'scorm_cloud/base_object'
9
+ require 'scorm_cloud/course'
10
+ require 'scorm_cloud/registration'
11
+
12
+ require 'scorm_cloud/base_service'
13
+ require 'scorm_cloud/debug_service'
14
+ require 'scorm_cloud/upload_service'
15
+ require 'scorm_cloud/course_service'
16
+ require 'scorm_cloud/registration_service'
17
+ require 'scorm_cloud/tagging_service'
18
+ require 'scorm_cloud/reporting_service'
19
+ require 'scorm_cloud/dispatch_service'
20
+ require 'scorm_cloud/export_service'
21
+
22
+ # Rails 3 Integration
23
+ require 'scorm_cloud/railtie' if defined?(Rails::Railtie)
24
+
25
+ module ScormCloud
26
+ class ScormCloud < Base
27
+ add_service :debug => DebugService
28
+ add_service :upload => UploadService
29
+ add_service :course => CourseService
30
+ add_service :registration => RegistrationService
31
+ add_service :tagging => TaggingService
32
+ add_service :reporting => ReportingService
33
+ add_service :dispatch => DispatchService
34
+ add_service :export => ExportService
35
+ end
36
+ end
@@ -0,0 +1,110 @@
1
+ module ScormCloud
2
+ class Base
3
+
4
+ attr_reader :appid
5
+
6
+ def initialize(appid, secret)
7
+ @appid = appid
8
+ @secret = secret
9
+ end
10
+
11
+ def call(method, params = {})
12
+ url = prepare_url(method, params)
13
+ execute_call_xml(url)
14
+ end
15
+
16
+ def call_raw(method, params = {})
17
+ url = prepare_url(method, params)
18
+ execute_call_plain(url)
19
+ end
20
+
21
+ def call_url(url)
22
+ execute_call_plain(url)
23
+ end
24
+
25
+ def post(method, path, params = {})
26
+ url = URI.parse(prepare_url(method, params))
27
+ body = nil
28
+ File.open(path) do |f|
29
+ req = Net::HTTP::Post::Multipart.new "#{url.path}?#{url.query}",
30
+ "file" => UploadIO.new(f, "application/zip", "scorm.zip")
31
+ res = Net::HTTP.start(url.host, url.port) do |http|
32
+ http.request(req)
33
+ end
34
+ body = res.body
35
+ end
36
+ REXML::Document.new(body)
37
+ end
38
+
39
+ def launch_url(method, params = {})
40
+ prepare_url(method, params)
41
+ end
42
+
43
+
44
+ private
45
+
46
+ # Get the URL for the call
47
+ def prepare_url(method, params = {})
48
+ timestamp = Time.now.utc.strftime('%Y%m%d%H%M%S')
49
+ params[:method] = method
50
+ params[:appid] = @appid
51
+ params[:ts] = timestamp
52
+ html_params = params.map { |k,v| "#{k.to_s}=#{v}" }.join("&")
53
+
54
+ raw = @secret + params.keys.
55
+ sort{ |a,b| a.to_s.downcase <=> b.to_s.downcase }.
56
+ map{ |k| "#{k.to_s}#{params[k]}" }.
57
+ join
58
+
59
+ sig = Digest::MD5.hexdigest(raw)
60
+ "http://cloud.scorm.com/api?#{html_params}&sig=#{sig}"
61
+ end
62
+
63
+
64
+ # Get plain response body and parse the XML doc
65
+ def execute_call_xml(url)
66
+ doc = REXML::Document.new(execute_call_plain(url))
67
+ raise create_error(doc, url) unless doc.elements["rsp"].attributes["stat"] == "ok"
68
+ doc
69
+ end
70
+
71
+ # Execute the call - returns response body or redirect url
72
+ def execute_call_plain(url)
73
+ res = Net::HTTP.get_response(URI.parse(url))
74
+ case res
75
+ when Net::HTTPRedirection
76
+ # Return the new URL
77
+ res['location']
78
+ when Net::HTTPSuccess
79
+ res.body
80
+ else
81
+ raise "HTTP Error connecting to scorm cloud: #{res.inspect}"
82
+ end
83
+ end
84
+
85
+
86
+ # Create an exception with code & message
87
+ def create_error(doc, url)
88
+ err = doc.elements["rsp"].elements["err"]
89
+ code = err.attributes["code"]
90
+ msg = err.attributes["msg"]
91
+ "Error In Scorm Cloud: Error=#{code} Message=#{msg} URL=#{url}"
92
+ end
93
+
94
+
95
+ # Add services
96
+ def self.add_service(hash)
97
+ hash.each do |name, klass|
98
+ define_method(name) do
99
+ service = instance_variable_get("@#{name.to_s}")
100
+ unless service
101
+ service = instance_variable_set("@#{name.to_s}", klass.new(self))
102
+ end
103
+ service
104
+ end
105
+ end
106
+ end
107
+
108
+
109
+ end
110
+ end