pupilfirst_xapi 0.1.0

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.
@@ -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