pupilfirst_xapi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module PupilfirstXapi
4
+ module Objects
5
+ RSpec.describe Builder do
6
+ it do
7
+ xapi = Builder.new(id: 'any-uri', name: 'Something', description: 'from nothing', type: 'type-uri').call
8
+
9
+ expect(xapi).to be_a Xapi::Activity
10
+ expect(xapi.id).to eq 'any-uri'
11
+ expect(xapi.definition.name).to eq({'en-US' => 'Something'})
12
+ expect(xapi.definition.description).to eq({'en-US' => 'from nothing'})
13
+ expect(xapi.definition.type).to eq 'type-uri'
14
+ expect(xapi.definition.extensions).to eq nil
15
+ expect(xapi.object_type).to eq 'Activity'
16
+ end
17
+
18
+ it do
19
+ xapi = Builder.new(id: 'any-uri', name: 'Something', description: 'from nothing', type: 'type-uri')
20
+ .with_extension('ext-type-1', 'any-value-1')
21
+ .with_extension('ext-type-2', 'any-value-2')
22
+ .call
23
+
24
+ expect(xapi).to be_a Xapi::Activity
25
+ expect(xapi.id).to eq 'any-uri'
26
+ expect(xapi.definition.name).to eq({'en-US' => 'Something'})
27
+ expect(xapi.definition.description).to eq({'en-US' => 'from nothing'})
28
+ expect(xapi.definition.type).to eq 'type-uri'
29
+ expect(xapi.definition.extensions).to eq({
30
+ 'ext-type-1' => 'any-value-1',
31
+ 'ext-type-2' => 'any-value-2',
32
+ })
33
+ expect(xapi.object_type).to eq 'Activity'
34
+ end
35
+
36
+ it do
37
+ xapi = Builder.new(id: 'any-uri', name: 'Something', description: 'from nothing', type: 'type-uri')
38
+ .with_extension('ext-type', 'any-value-1')
39
+ .with_extension('ext-type', 'any-value-2')
40
+ .call
41
+
42
+ expect(xapi).to be_a Xapi::Activity
43
+ expect(xapi.id).to eq 'any-uri'
44
+ expect(xapi.definition.name).to eq({'en-US' => 'Something'})
45
+ expect(xapi.definition.description).to eq({'en-US' => 'from nothing'})
46
+ expect(xapi.definition.type).to eq 'type-uri'
47
+ expect(xapi.definition.extensions).to eq({
48
+ 'ext-type' => 'any-value-2',
49
+ })
50
+ expect(xapi.object_type).to eq 'Activity'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails_helper'
2
+
3
+ module PupilfirstXapi
4
+ RSpec.describe Outbox::Job, type: :job do
5
+ let(:id) { SecureRandom.uuid }
6
+ let(:timestamp) { Time.now }
7
+
8
+ it 'schedules new xapi statement push' do
9
+ payload = {actor_id: 123, resource_id: 456, id: id, timestamp: timestamp, event_type: 'any.event'}
10
+ expect {
11
+ Outbox << payload
12
+ }.to have_enqueued_job(Outbox::Job).with(payload)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module PupilfirstXapi
4
+ RSpec.describe Outbox do
5
+ let(:timestamp) { Time.now }
6
+ let(:john) { double(:john, name: 'John Doe', email: 'john@doe.com') }
7
+ let(:course) { double(:course, name: 'Rails for Begginers', description: 'Seems easy', created_at: 1.week.ago, ends_at: nil) }
8
+ let(:target) { double(:target, title: '1st target', description: 'Seems easy') }
9
+ let(:good_one) { double(:timeline_event, target: target, passed?: true) }
10
+ let(:bad_one) { double(:timeline_event, target: target, passed?: false) }
11
+
12
+ let(:data) {
13
+ {
14
+ course: { 456 => course },
15
+ timeline_event: {
16
+ 456 => good_one,
17
+ 123 => bad_one,
18
+ },
19
+ user: { 123 => john },
20
+ }
21
+ }
22
+ let(:repository) { ->(klass, resource_id) { data.dig(klass, resource_id) } }
23
+ let(:uri_for) do
24
+ ->(obj) do
25
+ case obj
26
+ when course
27
+ 'rails-for-begginers'
28
+ when target
29
+ 'target-1'
30
+ else
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'post xapi statements to provided lrs' do
37
+ [
38
+ [:course_completed, Verbs::COMPLETED, 'rails-for-begginers'],
39
+ [:student_added, Verbs::REGISTERED, 'rails-for-begginers'],
40
+ [:submission_graded, Verbs::COMPLETED_ASSIGNMENT, 'target-1'],
41
+ [:submission_automatically_verified, Verbs::COMPLETED_ASSIGNMENT, 'target-1'],
42
+ ].each do |event_type, expected_verb, expected_object_id|
43
+ lrs = FakeLrs.new
44
+ Outbox.new(lrs: lrs, repository: repository, uri_for: uri_for)
45
+ .call(actor_id: 123, resource_id: 456, timestamp: timestamp, event_type: event_type)
46
+ expect(lrs.statements.count).to eq(1)
47
+ xapi = lrs.statements.first
48
+ expect(xapi).to be_a Xapi::Statement
49
+ expect(xapi.id).to eq(nil)
50
+ expect(xapi.timestamp).to eq(timestamp)
51
+ expect_actor(xapi.actor, name: 'John Doe', email: 'john@doe.com')
52
+ expect(xapi.verb).to eq(expected_verb)
53
+ expect(xapi.object).to be_a Xapi::Activity
54
+ expect(xapi.object.id).to eq(expected_object_id)
55
+ end
56
+ end
57
+
58
+ it 'no-op when there is no xapi statement generated' do
59
+ [
60
+ :submission_graded,
61
+ :submission_automatically_verified,
62
+ ].each do |event_type|
63
+ lrs = FakeLrs.new
64
+ Outbox.new(lrs: lrs, repository: repository, uri_for: uri_for)
65
+ .call(actor_id: 123, resource_id: 123, timestamp: timestamp, event_type: event_type)
66
+ expect(lrs.statements.count).to eq(0)
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ ENV['RAILS_ENV'] ||= 'test'
3
+ abort("The Rails environment is running in production mode!") if Rails.env.production?
4
+ require 'rspec/rails'
5
+ require 'webmock/rspec'
6
+ WebMock.disable_net_connect!
7
+
8
+ RSpec.configure do |config|
9
+ config.use_active_record = false
10
+ config.filter_rails_from_backtrace!
11
+ end
@@ -0,0 +1,56 @@
1
+ require_relative "../lib/pupilfirst_xapi"
2
+ require 'dotenv'
3
+ Dotenv.load('.env.test')
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+ config.mock_with :rspec do |mocks|
10
+ mocks.verify_partial_doubles = true
11
+ end
12
+ config.shared_context_metadata_behavior = :apply_to_host_groups
13
+ config.disable_monkey_patching!
14
+ if config.files_to_run.one?
15
+ config.default_formatter = "doc"
16
+ end
17
+ config.order = :random
18
+ Kernel.srand config.seed
19
+
20
+ config.around(:each, type: :job) do |example|
21
+ ActiveJob::Base.queue_adapter = :test
22
+ example.run
23
+ ensure
24
+ ActiveJob::Base.queue_adapter.enqueued_jobs = []
25
+ ActiveJob::Base.queue_adapter.performed_jobs = []
26
+ end
27
+
28
+ config.around(:each, perform_jobs: true) do |example|
29
+ old_perform_enqueued_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_jobs
30
+ old_perform_enqueued_at_jobs = ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs
31
+ ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
32
+ ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
33
+ example.run
34
+ ensure
35
+ ActiveJob::Base.queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
36
+ ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
37
+ end
38
+ end
39
+
40
+ def expect_actor(xapi, name:, email:)
41
+ expect(xapi).to be_a Xapi::Agent
42
+ expect(xapi.mbox).to eq "mailto:#{email}"
43
+ expect(xapi.name).to eq name
44
+ expect(xapi.object_type).to eq 'Agent'
45
+ end
46
+
47
+ class FakeLrs
48
+ def initialize
49
+ @statements = []
50
+ end
51
+ attr_reader :statements
52
+
53
+ def save_statement(statement)
54
+ @statements << statement
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module PupilfirstXapi
4
+ module Statements
5
+ RSpec.describe CourseCompleted do
6
+ it do
7
+ john = double(:john, name: 'John Doe', email: 'john@doe.com')
8
+ course = double(:course, name: 'Rails for Begginers', description: 'Seems easy', created_at: 1.week.ago, ends_at: nil)
9
+ data = {
10
+ course: { 456 => course },
11
+ user: { 123 => john },
12
+ }
13
+ repository = ->(klass, resource_id) { data.dig(klass, resource_id) }
14
+ uri_for = ->(obj) { obj == course ? 'rails-for-begginers' : nil }
15
+
16
+ xapi = CourseCompleted.new(repository, uri_for).call(actor_id: 123, resource_id: 456)
17
+
18
+ expect(xapi).to be_a Xapi::Statement
19
+ expect_actor(xapi.actor, name: 'John Doe', email: 'john@doe.com')
20
+ expect(xapi.verb).to eq Verbs::COMPLETED
21
+
22
+ expect(xapi.object).to be_a Xapi::Activity
23
+ expect(xapi.object.object_type).to eq 'Activity'
24
+ expect(xapi.object.id).to eq 'rails-for-begginers'
25
+ expect(xapi.object.definition.type).to eq 'http://adlnet.gov/expapi/activities/product'
26
+ expect(xapi.object.definition.name).to eq({'en-US' => 'Rails for Begginers'})
27
+ expect(xapi.object.definition.description).to eq({'en-US' => 'Seems easy'})
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ module PupilfirstXapi
4
+ module Statements
5
+ RSpec.describe CourseRegistered do
6
+ it do
7
+ john = double(:john, name: 'John Doe', email: 'john@doe.com')
8
+ course = double(:course, name: 'Rails for Begginers', description: 'Seems easy', created_at: 1.week.ago, ends_at: nil)
9
+ data = {
10
+ course: { 456 => course },
11
+ user: { 123 => john },
12
+ }
13
+ repository = ->(klass, resource_id) { data.dig(klass, resource_id) }
14
+ uri_for = ->(obj) { obj == course ? 'rails-for-begginers' : nil }
15
+
16
+ xapi = CourseRegistered.new(repository, uri_for).call(actor_id: 123, resource_id: 456)
17
+
18
+ expect(xapi).to be_a Xapi::Statement
19
+ expect_actor(xapi.actor, name: 'John Doe', email: 'john@doe.com')
20
+ expect(xapi.verb).to eq Verbs::REGISTERED
21
+
22
+ expect(xapi.object).to be_a Xapi::Activity
23
+ expect(xapi.object.object_type).to eq 'Activity'
24
+ expect(xapi.object.id).to eq 'rails-for-begginers'
25
+ expect(xapi.object.definition.type).to eq 'http://adlnet.gov/expapi/activities/product'
26
+ expect(xapi.object.definition.name).to eq({'en-US' => 'Rails for Begginers'})
27
+ expect(xapi.object.definition.description).to eq({'en-US' => 'Seems easy'})
28
+ expect(xapi.object.definition.extensions).to eq nil
29
+ end
30
+
31
+ it do
32
+ starts_at = 1.week.ago
33
+ ends_at = 3.days.after
34
+ duration = ActiveSupport::Duration.build(ends_at - starts_at).iso8601
35
+
36
+ john = double(:john, name: 'John Doe', email: 'john@doe.com')
37
+ course = double(:course, name: 'Rails for Begginers', description: 'Seems easy', created_at: starts_at, ends_at: ends_at)
38
+ data = {
39
+ course: { 456 => course },
40
+ user: { 123 => john },
41
+ }
42
+ repository = ->(klass, resource_id) { data.dig(klass, resource_id) }
43
+ uri_for = ->(obj) { obj == course ? 'rails-for-begginers' : nil }
44
+
45
+ xapi = CourseRegistered.new(repository, uri_for).call(actor_id: 123, resource_id: 456)
46
+
47
+ expect(xapi).to be_a Xapi::Statement
48
+ expect_actor(xapi.actor, name: 'John Doe', email: 'john@doe.com')
49
+ expect(xapi.verb).to eq Verbs::REGISTERED
50
+
51
+ expect(xapi.object).to be_a Xapi::Activity
52
+ expect(xapi.object.object_type).to eq 'Activity'
53
+ expect(xapi.object.id).to eq 'rails-for-begginers'
54
+ expect(xapi.object.definition.type).to eq 'http://adlnet.gov/expapi/activities/product'
55
+ expect(xapi.object.definition.name).to eq({'en-US' => 'Rails for Begginers'})
56
+ expect(xapi.object.definition.description).to eq({'en-US' => 'Seems easy'})
57
+ expect(xapi.object.definition.extensions).to eq({"http://id.tincanapi.com/extension/planned-duration" => duration})
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module PupilfirstXapi
4
+ module Statements
5
+ RSpec.describe TargetCompleted do
6
+ it do
7
+ target = double(:target, title: '1st target', description: 'Seems easy')
8
+ submission = double(:timeline_event, target: target, passed?: true)
9
+ john = double(:john, name: 'John Doe', email: 'john@doe.com')
10
+ data = {
11
+ timeline_event: { 456 => submission },
12
+ user: { 123 => john },
13
+ }
14
+ repository = ->(klass, resource_id) { data.dig(klass, resource_id) }
15
+ uri_for = ->(obj) { obj == target ? 'target-1' : nil }
16
+
17
+ xapi = TargetCompleted.new(repository, uri_for).call(actor_id: 123, resource_id: 456)
18
+
19
+ expect(xapi).to be_a Xapi::Statement
20
+ expect_actor(xapi.actor, name: 'John Doe', email: 'john@doe.com')
21
+ expect(xapi.verb).to eq Verbs::COMPLETED_ASSIGNMENT
22
+
23
+ expect(xapi.object).to be_a Xapi::Activity
24
+ expect(xapi.object.object_type).to eq 'Activity'
25
+ expect(xapi.object.id).to eq 'target-1'
26
+ expect(xapi.object.definition.type).to eq 'http://activitystrea.ms/schema/1.0/task'
27
+ expect(xapi.object.definition.name).to eq({'en-US' => '1st target'})
28
+ expect(xapi.object.definition.description).to eq({'en-US' => 'Seems easy'})
29
+ end
30
+
31
+ it 'no-op when submission is not passed' do
32
+ target = double(:target, title: '1st target', description: 'Seems easy')
33
+ submission = double(:timeline_event, target: target, passed?: false)
34
+ john = double(:john, name: 'John Doe', email: 'john@doe.com')
35
+ data = {
36
+ timeline_event: { 456 => submission },
37
+ user: { 123 => john },
38
+ }
39
+ repository = ->(klass, resource_id) { data.dig(klass, resource_id) }
40
+ uri_for = ->(obj) { obj == target ? 'target-1' : nil }
41
+
42
+ xapi = TargetCompleted.new(repository, uri_for).call(actor_id: 123, resource_id: 456)
43
+ expect(xapi).to eq nil
44
+ end
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pupilfirst_xapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - GrowthTribe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.3
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.3.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.3
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.3.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: growthtribe_xapi
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.0.1
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.0.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: dotenv-rails
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description:
90
+ email:
91
+ - devs@growthtribe.io
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - LICENSE
97
+ - README.md
98
+ - Rakefile
99
+ - lib/pupilfirst_xapi.rb
100
+ - lib/pupilfirst_xapi/actors.rb
101
+ - lib/pupilfirst_xapi/objects.rb
102
+ - lib/pupilfirst_xapi/objects/builder.rb
103
+ - lib/pupilfirst_xapi/objects/course.rb
104
+ - lib/pupilfirst_xapi/objects/target.rb
105
+ - lib/pupilfirst_xapi/outbox.rb
106
+ - lib/pupilfirst_xapi/statements.rb
107
+ - lib/pupilfirst_xapi/statements/course_completed.rb
108
+ - lib/pupilfirst_xapi/statements/course_registered.rb
109
+ - lib/pupilfirst_xapi/statements/target_completed.rb
110
+ - lib/pupilfirst_xapi/verbs.rb
111
+ - lib/pupilfirst_xapi/version.rb
112
+ - spec/integration_spec.rb
113
+ - spec/objects/builder_spec.rb
114
+ - spec/outbox_job_spec.rb
115
+ - spec/outbox_spec.rb
116
+ - spec/rails_helper.rb
117
+ - spec/spec_helper.rb
118
+ - spec/statements/course_completed_spec.rb
119
+ - spec/statements/course_redistered_spec.rb
120
+ - spec/statements/target_completed_spec.rb
121
+ homepage: https://github.com/growthtribeacademy/pupilfirst-xapi
122
+ licenses:
123
+ - GPL-3.0-or-later
124
+ metadata:
125
+ homepage_uri: https://github.com/growthtribeacademy/pupilfirst-xapi
126
+ source_code_uri: https://github.com/growthtribeacademy/pupilfirst-xapi/pupilfirst_xapi
127
+ changelog_uri: https://github.com/growthtribeacademy/pupilfirst-xapi/pupilfirst_xapi/CHANGELOG.md
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubygems_version: 3.2.3
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: XAPI statements generator and publisher to LRS for Pupilfirst
147
+ test_files:
148
+ - spec/integration_spec.rb
149
+ - spec/objects/builder_spec.rb
150
+ - spec/outbox_job_spec.rb
151
+ - spec/outbox_spec.rb
152
+ - spec/rails_helper.rb
153
+ - spec/spec_helper.rb
154
+ - spec/statements/course_completed_spec.rb
155
+ - spec/statements/course_redistered_spec.rb
156
+ - spec/statements/target_completed_spec.rb