d2l-valence 0.0.1

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +22 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +13 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +71 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +266 -0
  9. data/Rakefile +6 -0
  10. data/d2l-valence.gemspec +30 -0
  11. data/lib/d2l/valence.rb +15 -0
  12. data/lib/d2l/valence/app_context.rb +49 -0
  13. data/lib/d2l/valence/auth_tokens.rb +60 -0
  14. data/lib/d2l/valence/encrypt.rb +34 -0
  15. data/lib/d2l/valence/host.rb +43 -0
  16. data/lib/d2l/valence/request.rb +104 -0
  17. data/lib/d2l/valence/response.rb +66 -0
  18. data/lib/d2l/valence/timestamp_error.rb +42 -0
  19. data/lib/d2l/valence/user_context.rb +58 -0
  20. data/lib/d2l/valence/version.rb +5 -0
  21. data/spec/d2l/valence/app_context_spec.rb +14 -0
  22. data/spec/d2l/valence/auth_tokens_spec.rb +30 -0
  23. data/spec/d2l/valence/host_spec.rb +64 -0
  24. data/spec/d2l/valence/request/authenticated_uri_spec.rb +75 -0
  25. data/spec/d2l/valence/request/execute/invalid_time_stamp_spec.rb +40 -0
  26. data/spec/d2l/valence/request/execute/lti_links/create_spec.rb +66 -0
  27. data/spec/d2l/valence/request/execute/lti_links/delete_spec.rb +39 -0
  28. data/spec/d2l/valence/request/execute/lti_links/list_spec.rb +41 -0
  29. data/spec/d2l/valence/request/execute/lti_links/update_spec.rb +73 -0
  30. data/spec/d2l/valence/request/execute/lti_links/view_spec.rb +41 -0
  31. data/spec/d2l/valence/request/execute/version_spec.rb +41 -0
  32. data/spec/d2l/valence/request/execute/whoami_spec.rb +40 -0
  33. data/spec/d2l/valence/response/code_spec.rb +34 -0
  34. data/spec/d2l/valence/response/server_skew_spec.rb +33 -0
  35. data/spec/d2l/valence/timestamp_error_spec.rb +21 -0
  36. data/spec/d2l/valence/user_context_spec.rb +6 -0
  37. data/spec/fixtures/vcr_cassettes/request/execute/create_lti_link.yml +71 -0
  38. data/spec/fixtures/vcr_cassettes/request/execute/delete_lti_link.yml +48 -0
  39. data/spec/fixtures/vcr_cassettes/request/execute/get_a_lti_link.yml +66 -0
  40. data/spec/fixtures/vcr_cassettes/request/execute/get_lti_links.yml +70 -0
  41. data/spec/fixtures/vcr_cassettes/request/execute/get_version.yml +66 -0
  42. data/spec/fixtures/vcr_cassettes/request/execute/get_whoami.yml +61 -0
  43. data/spec/fixtures/vcr_cassettes/request/execute/invalid_timestamp.yml +112 -0
  44. data/spec/fixtures/vcr_cassettes/request/execute/put_lti_link.yml +139 -0
  45. data/spec/spec_helper.rb +20 -0
  46. data/spec/support/common_context.rb +37 -0
  47. metadata +228 -0
@@ -0,0 +1,5 @@
1
+ module D2L
2
+ module Valence
3
+ VERSION = '0.0.1'.freeze
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::AppContext, type: :service do
4
+ include_context :common_context
5
+
6
+ context '.auth_url' do
7
+ subject { described_class.new(brightspace_host: auth_host, app_id: app_id, app_key: app_key) }
8
+ let(:expected_url) { "https://partners.brightspace.com/d2l/auth/api/token?x_a=#{app_id}&x_b=#{auth_key}&x_target=#{CGI.escape(callback_uri.to_s)}" }
9
+
10
+ it('will generate an authentication URL') do
11
+ expect(subject.auth_url(callback_uri)).to eq expected_url
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::AuthTokens, type: :service do
4
+ include_context :common_context
5
+
6
+ context '.generate' do
7
+ subject { described_class.new(request: request) }
8
+ let(:request) do
9
+ D2L::Valence::Request.new(
10
+ user_context: user_context,
11
+ http_method: 'GET',
12
+ route: '/d2l/api/lp/:version/users/whoami'
13
+ )
14
+ end
15
+
16
+ let(:signature) { "#{request.http_method}&#{CGI.unescape(request.path)}&#{subject.adjusted_timestamp}" }
17
+ let(:signature_by_app_key) { D2L::Valence::Encrypt.generate_from(app_key, signature) }
18
+ let(:signature_by_user_key) { D2L::Valence::Encrypt.generate_from(user_key, signature) }
19
+ let(:tokens) { subject.generate }
20
+
21
+ it 'will generate the right token values' do
22
+ expect(tokens[described_class::APP_ID_PARAM]).to eq app_id
23
+ expect(tokens[described_class::SIGNATURE_BY_APP_KEY_PARAM]).to eq signature_by_app_key
24
+ expect(tokens[described_class::USER_ID_PARAM]).to eq user_id
25
+ expect(tokens[described_class::SIGNATURE_BY_USER_KEY_PARAM]).to eq signature_by_user_key
26
+ expect(tokens[described_class::TIMESTAMP_PARAM]).to eq subject.adjusted_timestamp
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Host do
4
+ context '.initialize' do
5
+ let(:host) { 'www.somehost.com' }
6
+
7
+ context 'for scheme string' do
8
+ subject { described_class.new(scheme: 'HTTPS', host: host) }
9
+
10
+ its(:scheme) { is_expected.to eq :https }
11
+ its(:host) { is_expected.to eq host }
12
+ its(:port) { is_expected.to be_nil}
13
+ end
14
+
15
+ context 'for scheme symbol' do
16
+ subject { described_class.new(scheme: :http, host: host) }
17
+
18
+ its(:scheme) { is_expected.to eq :http }
19
+ its(:host) { is_expected.to eq host }
20
+ its(:port) { is_expected.to be_nil}
21
+ end
22
+
23
+ context 'for unsupported scheme' do
24
+ it('will raise an exception') do
25
+ expect { described_class.new(scheme: :sftp, host: host) }.to raise_error RuntimeError, 'sftp is an unsupported scheme. Please use either HTTP or HTTPS'
26
+ end
27
+ end
28
+ end
29
+
30
+ context '.to_uri' do
31
+ let(:host) { 'www.somehost.com' }
32
+
33
+ context 'for HTTP' do
34
+ subject { described_class.new(scheme: :http, host: host, port: port) }
35
+
36
+ context 'with no port' do
37
+ let(:port) { nil }
38
+
39
+ its('to_uri.to_s') { is_expected.to eq 'http://www.somehost.com' }
40
+ end
41
+ context 'with specific port' do
42
+ let(:port) { 8080 }
43
+
44
+ its('to_uri.to_s') { is_expected.to eq 'http://www.somehost.com:8080' }
45
+ end
46
+ end
47
+
48
+ context 'for HTTPS' do
49
+ subject { described_class.new(scheme: :https, host: host, port: port) }
50
+
51
+ context 'with no port' do
52
+ let(:port) { nil }
53
+
54
+ its('to_uri.to_s') { is_expected.to eq 'https://www.somehost.com' }
55
+ end
56
+ context 'with specific port' do
57
+ let(:port) { 4040 }
58
+
59
+ its('to_uri.to_s') { is_expected.to eq 'https://www.somehost.com:4040' }
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.authenticated_uri' do
17
+ context 'with no parameters' do
18
+ let(:http_method) { 'GET' }
19
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
20
+ let(:route_params) { {} }
21
+ let(:query_params) { {} }
22
+ let(:expected_path) { '/d2l/api/lp/1.0/users/whoami' }
23
+
24
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
25
+ it('will include the authentication query parameters') do
26
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
27
+ end
28
+ end
29
+
30
+ context 'with a specific API version' do
31
+ let(:api_version) { '1.1' }
32
+ let(:http_method) { 'GET' }
33
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
34
+ let(:route_params) { {} }
35
+ let(:query_params) { {} }
36
+ let(:expected_path) { "/d2l/api/lp/#{api_version}/users/whoami" }
37
+
38
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
39
+ end
40
+
41
+ context 'with route parameters' do
42
+ let(:http_method) { 'GET' }
43
+ let(:route) { '/d2l/api/lp/:version/:org_unit_id/groupcategories/:groupCategoryId' }
44
+ let(:route_params) do
45
+ {
46
+ org_unit_id: 4,
47
+ groupCategoryId: 23,
48
+ }
49
+ end
50
+ let(:query_params) { {} }
51
+ let(:expected_path) { "/d2l/api/lp/#{api_version}/#{route_params[:org_unit_id]}/groupcategories/#{route_params[:groupCategoryId]}" }
52
+
53
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
54
+ it('will include the authentication query parameters') do
55
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
56
+ end
57
+ end
58
+
59
+ context 'with query parameters' do
60
+ let(:http_method) { 'GET' }
61
+ let(:route) { '/d2l/api/lp/:version/users/' }
62
+ let(:query_params) { {userName: 'student123'} }
63
+ let(:route_params) { {} }
64
+ let(:expected_path) { "/d2l/api/lp/1.0/users/" }
65
+
66
+ its('authenticated_uri.path') { is_expected.to eq expected_path }
67
+ it('will include the query params') do
68
+ query_params.each { |k,v| expect(subject.authenticated_uri.query).to include "#{k}=#{v}" }
69
+ end
70
+ it('will include the authentication query parameters') do
71
+ auth_token_parameters.each { |t| expect(subject.authenticated_uri.query).to include "#{t}=" }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'with the timestamp is invalid' do
21
+ let(:http_method) { 'GET' }
22
+ let(:route) { '/d2l/api/lp/:version/users/whoami' }
23
+ let(:route_params) { {} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:skewed_start_time) { Time.at(1491940559) }
27
+ # NB: The commented line below is the needed for the regeneration of VCR cassettes
28
+ # let(:skewed_start_time) { Time.at(Time.now.to_i - 20000) }
29
+
30
+ context 'on second try', vcr: {cassette_name: 'request/execute/invalid_timestamp'} do
31
+ it 'will succeed' do
32
+ Timecop.travel skewed_start_time do
33
+ expect(subject.execute.code).to eq :INVALID_TIMESTAMP
34
+ expect(subject.execute.code).to eq :HTTP_200
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for POST', vcr: {cassette_name: 'request/execute/create_lti_link'} do
21
+ let(:http_method) { 'POST' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:orgUnitId' }
23
+ let(:route_params) { {orgUnitId: 8041} }
24
+ let(:query_params) do
25
+ {
26
+ Title: 'LTI Link',
27
+ Url: 'http://myapplication.com/tool/launch',
28
+ Description: 'Link for external tool',
29
+ Key: '2015141297208',
30
+ PlainSecret: 'a30be7c3550149b7a7daac3065f0e5e5',
31
+ IsVisible: false,
32
+ SignMessage: true,
33
+ SignWithTc: true,
34
+ SendTcInfo: true,
35
+ SendContextInfo: true,
36
+ SendUserId: true,
37
+ SendUserName: true,
38
+ SendUserEmail: true,
39
+ SendLinkTitle: true,
40
+ SendLinkDescription: true,
41
+ SendD2LUserName: true,
42
+ SendD2LOrgDefinedId: true,
43
+ SendD2LOrgRoleId: true,
44
+ UseToolProviderSecuritySettings: false,
45
+ CustomParameters: nil
46
+ }
47
+ end
48
+ let(:api_version) { '1.15' }
49
+ let(:response) { subject.execute }
50
+
51
+ before do
52
+ Timecop.freeze Time.at(1491960645)
53
+ end
54
+
55
+ after { Timecop.return }
56
+
57
+ it 'will return the version information' do
58
+ expect(response.to_hash['LtiLinkId']).to_not be_nil
59
+ query_params.each do |k, v|
60
+ expect("#{k}: #{response.to_hash[k.to_s]}").to eq "#{k}: #{v}" if not [:PlainSecret, :CustomParameters].include? k
61
+ end
62
+ expect(response.code).to eq :HTTP_200
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for DELETE an lti link', vcr: {cassette_name: 'request/execute/delete_lti_link'} do
21
+ let(:http_method) { 'DELETE' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:ltiLinkId' }
23
+ let(:route_params) { {ltiLinkId: 144931} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:response) { subject.execute }
27
+
28
+ before do
29
+ Timecop.freeze Time.at(1491960880)
30
+ end
31
+
32
+ after { Timecop.return }
33
+
34
+ it 'will delete the LTI Link' do
35
+ expect(response.code).to eq :HTTP_200
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for GET all lti links', vcr: {cassette_name: 'request/execute/get_lti_links'} do
21
+ let(:http_method) { 'GET' }
22
+ let(:route) { '/d2l/api/le/:version/lti/link/:orgUnitId/' }
23
+ let(:route_params) { {orgUnitId: 8041} }
24
+ let(:query_params) { {} }
25
+ let(:api_version) { '1.15' }
26
+ let(:response) { subject.execute }
27
+
28
+ before do
29
+ Timecop.freeze Time.at(1491960964)
30
+ end
31
+
32
+ after { Timecop.return }
33
+
34
+ its(:execute) { is_expected.to be_a D2L::Valence::Response }
35
+ it 'will return all lti links for the associated unit' do
36
+ expect(response.code).to eq :HTTP_200
37
+ response.to_hash.each { |lti_record| expect(lti_record['LtiLinkId']).to_not be_nil }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe D2L::Valence::Request, type: :service do
4
+ include_context :common_context
5
+
6
+ subject do
7
+ described_class.new(
8
+ user_context: user_context,
9
+ http_method: http_method,
10
+ route: route,
11
+ route_params: route_params,
12
+ query_params: query_params
13
+ )
14
+ end
15
+
16
+ context '.execute' do
17
+ let(:user_id) { ENV['D2L_USER_ID'] }
18
+ let(:user_key) { ENV['D2L_USER_KEY'] }
19
+
20
+ context 'for PUT with an lti link', vcr: {cassette_name: 'request/execute/put_lti_link'} do
21
+ let(:incorrect_details) { query_params.dup.merge(Title: 'The wrong title') }
22
+ let!(:existing_lit_link) do
23
+ Timecop.freeze Time.at(1491961066) do
24
+ D2L::Valence::Request.new(
25
+ user_context: user_context,
26
+ http_method: 'POST',
27
+ route: '/d2l/api/le/:version/lti/link/:orgUnitId',
28
+ route_params: {orgUnitId: 8041},
29
+ query_params: incorrect_details
30
+ ).execute.to_hash
31
+ end
32
+ end
33
+ let(:http_method) { 'PUT' }
34
+ let(:route) { '/d2l/api/le/:version/lti/link/:ltiLinkId' }
35
+ let(:route_params) { {ltiLinkId: existing_lit_link['LtiLinkId']} }
36
+ let(:query_params) do
37
+ {
38
+ Title: 'LTI Link',
39
+ Url: 'http://myapplication.com/tool/launch',
40
+ Description: 'Link for external tool',
41
+ Key: '2015141297208',
42
+ PlainSecret: 'a30be7c3550149b7a7daac3065f0e5e5',
43
+ IsVisible: false,
44
+ SignMessage: true,
45
+ SignWithTc: true,
46
+ SendTcInfo: true,
47
+ SendContextInfo: true,
48
+ SendUserId: true,
49
+ SendUserName: true,
50
+ SendUserEmail: true,
51
+ SendLinkTitle: true,
52
+ SendLinkDescription: true,
53
+ SendD2LUserName: true,
54
+ SendD2LOrgDefinedId: true,
55
+ SendD2LOrgRoleId: true,
56
+ UseToolProviderSecuritySettings: true,
57
+ CustomParameters: nil
58
+ }
59
+ end
60
+ let(:api_version) { '1.15' }
61
+ let(:response) do
62
+ Timecop.freeze Time.at(1491961067) do
63
+ subject.execute
64
+ end
65
+ end
66
+
67
+ it 'will update the LTI Link' do
68
+ expect(response.code).to eq :HTTP_200
69
+ expect(response.to_hash['Title']).to eq query_params[:Title]
70
+ end
71
+ end
72
+ end
73
+ end