quiz_api_client 4.2.0 → 4.2.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/quiz_api_client/services/items_service.rb +11 -0
  3. data/lib/quiz_api_client/version.rb +1 -1
  4. data/spec/config_spec.rb +66 -0
  5. data/spec/contracts/interaction_types_service_spec.rb +22 -0
  6. data/spec/contracts/item_analyses_service_spec.rb +59 -0
  7. data/spec/contracts/items_service_spec.rb +59 -0
  8. data/spec/contracts/qti_imports_service_spec.rb +34 -0
  9. data/spec/contracts/quiz_clone_job_service_spec.rb +20 -0
  10. data/spec/contracts/quiz_clone_jobs_service_spec.rb +21 -0
  11. data/spec/contracts/quiz_entries_service_spec.rb +125 -0
  12. data/spec/contracts/quiz_service_spec.rb +68 -0
  13. data/spec/contracts/quiz_session_events_service_spec.rb +30 -0
  14. data/spec/contracts/quiz_session_result_service_spec.rb +42 -0
  15. data/spec/contracts/quiz_session_service_spec.rb +56 -0
  16. data/spec/contracts/quiz_sessions_service_spec.rb +28 -0
  17. data/spec/contracts/quizzes_service_spec.rb +80 -0
  18. data/spec/contracts/session_item_results_service_spec.rb +60 -0
  19. data/spec/contracts/session_items_service_spec.rb +21 -0
  20. data/spec/contracts/shared_banks_spec.rb +366 -0
  21. data/spec/contracts/shared_examples/http_delete_example.rb +56 -0
  22. data/spec/contracts/shared_examples/http_get_example.rb +139 -0
  23. data/spec/contracts/shared_examples/http_patch_example.rb +60 -0
  24. data/spec/contracts/shared_examples/http_post_example.rb +60 -0
  25. data/spec/contracts/shared_examples/http_put_example.rb +60 -0
  26. data/spec/http_client_spec.rb +347 -0
  27. data/spec/json_formatter_spec.rb +32 -0
  28. data/spec/quiz_api_client/http_request/failure_spec.rb +100 -0
  29. data/spec/quiz_api_client/http_request/metrics_spec.rb +75 -0
  30. data/spec/quiz_api_client_spec.rb +117 -0
  31. data/spec/services/base_api_service_spec.rb +50 -0
  32. data/spec/services/interaction_types_service_spec.rb +25 -0
  33. data/spec/services/item_analyses_service_spec.rb +76 -0
  34. data/spec/services/items_service_spec.rb +56 -0
  35. data/spec/services/jwt_service_spec.rb +66 -0
  36. data/spec/services/qti_imports_service_spec.rb +114 -0
  37. data/spec/services/quiz_analyses_service_spec.rb +44 -0
  38. data/spec/services/quiz_clone_job_service_spec.rb +41 -0
  39. data/spec/services/quiz_clone_jobs_service_spec.rb +41 -0
  40. data/spec/services/quiz_entries_service_spec.rb +71 -0
  41. data/spec/services/quiz_service_spec.rb +49 -0
  42. data/spec/services/quiz_session_events_service_spec.rb +42 -0
  43. data/spec/services/quiz_session_result_service_spec.rb +26 -0
  44. data/spec/services/quiz_session_service_spec.rb +49 -0
  45. data/spec/services/quiz_sessions_service_spec.rb +42 -0
  46. data/spec/services/quizzes_service_spec.rb +71 -0
  47. data/spec/services/session_item_results_service_spec.rb +33 -0
  48. data/spec/services/session_items_service_spec.rb +26 -0
  49. data/spec/spec_helper.rb +42 -0
  50. data/spec/support/pact_config.rb +64 -0
  51. data/spec/support/pact_helper.rb +19 -0
  52. metadata +108 -39
  53. data/.dockerignore +0 -7
  54. data/.editorconfig +0 -16
  55. data/.gitignore +0 -13
  56. data/.rspec +0 -3
  57. data/.rubocop.yml +0 -72
  58. data/CHANGELOG.md +0 -35
  59. data/Dockerfile +0 -12
  60. data/Gemfile +0 -5
  61. data/Jenkinsfile +0 -86
  62. data/bin/console +0 -7
  63. data/bin/contracts-generate +0 -26
  64. data/bin/setup +0 -65
  65. data/docker-compose.dev.override.yml +0 -11
  66. data/docker-compose.yml +0 -10
  67. data/quiz_api_client.gemspec +0 -60
@@ -0,0 +1,100 @@
1
+ require 'sentry-raven'
2
+
3
+ describe QuizApiClient::HttpRequest::Failure do
4
+ let(:config) { QuizApiClient::Config.new }
5
+ let(:method) { 'GET' }
6
+ let(:url) { 'https://something.com' }
7
+ let(:response_code) { 500 }
8
+ let(:response_body) { 'I am a response body!' }
9
+ let(:response) { instance_double('HTTParty::Response', code: response_code, body: response_body) }
10
+
11
+ subject { described_class.new(config) }
12
+
13
+ describe '#raise_error' do
14
+ it 'raises the QuizApiClient::HttpClient::RequestFailed error' do
15
+ expect do
16
+ subject.raise_error(method, url)
17
+ end.to raise_error(QuizApiClient::HttpClient::RequestFailed)
18
+ end
19
+
20
+ context 'when there is a current exception' do
21
+ let(:error_context) do
22
+ {
23
+ quiz_api_client: {
24
+ request: {
25
+ method: method,
26
+ url: url
27
+ }
28
+ }
29
+ }
30
+ end
31
+ let(:current_error_message) { 'standard error message' }
32
+ let(:current_error) { StandardError.new(current_error_message) }
33
+ let(:raise_error_call) { -> { subject.raise_error(method, url, current_error: current_error) } }
34
+
35
+ it 'sets the context on the error' do
36
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
37
+ expect(error.context).to eq error_context
38
+ end
39
+ end
40
+
41
+ it 'sets the error message' do
42
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
43
+ expect(error.message).to eq current_error_message
44
+ end
45
+ end
46
+
47
+ context 'when error_handler is set to :sentry_raven' do
48
+ before do
49
+ config.error_handler = :sentry_raven
50
+ end
51
+
52
+ it 'sets the Raven context to the context on the error' do
53
+ expect(Raven).to receive(:extra_context).with(error_context)
54
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed)
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'when there is a response' do
60
+ let(:error_context) do
61
+ {
62
+ quiz_api_client: {
63
+ request: {
64
+ method: method,
65
+ url: url
66
+ },
67
+ response: {
68
+ body: response_body,
69
+ code: response_code
70
+ }
71
+ }
72
+ }
73
+ end
74
+ let(:raise_error_call) { -> { subject.raise_error(method, url, response: response) } }
75
+
76
+ it 'sets the context on the error' do
77
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
78
+ expect(error.context).to eq error_context
79
+ end
80
+ end
81
+
82
+ it 'sets the error message' do
83
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
84
+ expect(error.message).to eq "#{url} responded #{response_body} (#{response_code})"
85
+ end
86
+ end
87
+
88
+ context 'when error_handler is set to :sentry_raven' do
89
+ before do
90
+ config.error_handler = :sentry_raven
91
+ end
92
+
93
+ it 'sets the Raven context to the context on the error' do
94
+ expect(Raven).to receive(:extra_context).with(error_context)
95
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,75 @@
1
+ require 'inst_statsd'
2
+
3
+ describe QuizApiClient::HttpRequest::Metrics do
4
+ let(:config) { QuizApiClient::Config.new }
5
+ let(:method) { 'GET' }
6
+ let(:url) { 'https://something.com' }
7
+ let(:code) { 200 }
8
+
9
+ subject { described_class.new(config, method, url, code) }
10
+
11
+ describe '#increment' do
12
+ it 'does nothing when the metrics handler is nil' do
13
+ allow(config).to receive(:metrics_handler).and_return(nil)
14
+ expect(InstStatsd::Statsd).to receive(:increment).never
15
+
16
+ subject.increment
17
+ end
18
+
19
+ it 'does nothing when the metrics namespace is nil' do
20
+ allow(config).to receive(:metrics_namespace).and_return(nil)
21
+ expect(InstStatsd::Statsd).to receive(:increment).never
22
+
23
+ subject.increment
24
+ end
25
+
26
+ it 'calls the #increment method for InstStatsd' do
27
+ config.setup_metrics(:inststatsd, 'fake-namespace')
28
+ expect(InstStatsd::Statsd).to receive(:increment).with(
29
+ 'fake-namespace.quiz_api_client.request.count',
30
+ tags: {
31
+ method: method,
32
+ status: code,
33
+ url: url
34
+ }
35
+ )
36
+
37
+ subject.increment
38
+ end
39
+ end
40
+
41
+ describe '#duration' do
42
+ let(:duration_in_secs) { 10 }
43
+ let(:start_time) { Time.parse('2021-11-09 15:11:48 -0500') }
44
+ let(:end_time) { start_time + duration_in_secs }
45
+
46
+ it 'does nothing when the metrics handler is nil' do
47
+ allow(config).to receive(:metrics_handler).and_return(nil)
48
+ expect(InstStatsd::Statsd).to receive(:timing).never
49
+
50
+ subject.duration(start_time, end_time)
51
+ end
52
+
53
+ it 'does nothing when the metrics namespace is nil' do
54
+ allow(config).to receive(:metrics_namespace).and_return(nil)
55
+ expect(InstStatsd::Statsd).to receive(:timing).never
56
+
57
+ subject.duration(start_time, end_time)
58
+ end
59
+
60
+ it 'calls the #timing method for InstStatsd' do
61
+ config.setup_metrics(:inststatsd, 'fake-namespace')
62
+ expect(InstStatsd::Statsd).to receive(:timing).with(
63
+ 'fake-namespace.quiz_api_client.request.duration_ms',
64
+ duration_in_secs * 1_000,
65
+ tags: {
66
+ method: method,
67
+ status: code,
68
+ url: url
69
+ }
70
+ )
71
+
72
+ subject.duration(start_time, end_time)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,117 @@
1
+ describe QuizApiClient do
2
+ it 'has a version number' do
3
+ expect(QuizApiClient::VERSION).not_to be nil
4
+ end
5
+
6
+ context 'service helper methods' do
7
+ let(:host) { 'api.host' }
8
+ let(:shared_secret) { 'secret' }
9
+ let(:protocol) { 'http' }
10
+ let(:consumer_key) { 'some consumer_key' }
11
+ let(:consumer_request_id) { 'guid_value' }
12
+ let(:subject) do
13
+ QuizApiClient::Client.new(
14
+ consumer_key: consumer_key,
15
+ consumer_request_id: consumer_request_id,
16
+ host: host,
17
+ shared_secret: shared_secret,
18
+ protocol: protocol
19
+ )
20
+ end
21
+
22
+ describe '#config' do
23
+ it 'should populate the values' do
24
+ config = subject.config
25
+ expect(config.consumer_key).to eq consumer_key
26
+ expect(config.consumer_request_id).to eq consumer_request_id
27
+ expect(config.host).to eq host
28
+ expect(config.shared_secret).to eq shared_secret
29
+ expect(config.protocol).to eq protocol
30
+ end
31
+
32
+ it 'should yield the config on instantiation' do
33
+ client = QuizApiClient::Client.new(
34
+ consumer_key: consumer_key,
35
+ consumer_request_id: consumer_request_id,
36
+ host: host,
37
+ shared_secret: shared_secret,
38
+ protocol: protocol
39
+ ) do |config|
40
+ config.host = 'my.hosting'
41
+ end
42
+
43
+ expect(client.config.host).to eq 'my.hosting'
44
+ end
45
+ end
46
+
47
+ describe '#jwt_service' do
48
+ it 'should create the service' do
49
+ expect(QuizApiClient::Services::JwtService).to receive(:new).with(subject.config)
50
+ subject.jwt_service
51
+ end
52
+ end
53
+
54
+ describe '#quizzes_service' do
55
+ it 'should create the service' do
56
+ expect(QuizApiClient::Services::QuizzesService).to receive(:new).with(subject.config)
57
+ subject.quizzes_service
58
+ end
59
+ end
60
+
61
+ describe '#quiz_session_service' do
62
+ it 'should create the service' do
63
+ expect(QuizApiClient::Services::QuizSessionService).to receive(:new).with(subject.config)
64
+ subject.quiz_session_service
65
+ end
66
+ end
67
+
68
+ describe '#quiz_sessions_service' do
69
+ it 'should create the service' do
70
+ expect(QuizApiClient::Services::QuizSessionsService).to receive(:new).with(subject.config)
71
+ subject.quiz_sessions_service
72
+ end
73
+ end
74
+
75
+ describe '#qti_imports_service' do
76
+ it 'should create the service' do
77
+ expect(QuizApiClient::Services::QtiImportsService).to receive(:new).with(subject.config)
78
+ subject.qti_imports_service
79
+ end
80
+ end
81
+
82
+ describe '#item_analyses_service' do
83
+ it 'should create the service' do
84
+ expect(QuizApiClient::Services::ItemAnalysesService).to receive(:new).with(subject.config)
85
+ subject.item_analyses_service
86
+ end
87
+ end
88
+
89
+ describe '#quiz_analyses_service' do
90
+ it 'should create the service' do
91
+ expect(QuizApiClient::Services::QuizAnalysesService).to receive(:new).with(subject.config)
92
+ subject.quiz_analyses_service
93
+ end
94
+ end
95
+
96
+ describe '#quiz_session_events_service' do
97
+ it 'should create the service' do
98
+ expect(QuizApiClient::Services::QuizSessionEventsService).to receive(:new).with(subject.config)
99
+ subject.quiz_session_events_service
100
+ end
101
+ end
102
+
103
+ describe '#items_service' do
104
+ it 'should create the service' do
105
+ expect(QuizApiClient::Services::ItemsService).to receive(:new).with(subject.config)
106
+ subject.items_service
107
+ end
108
+ end
109
+
110
+ describe '#interaction_types_service' do
111
+ it 'should create the service' do
112
+ expect(QuizApiClient::Services::InteractionTypesService).to receive(:new).with(subject.config)
113
+ subject.interaction_types_service
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,50 @@
1
+ describe QuizApiClient::Services::BaseApiService do
2
+ let(:consumer_key) { 'consumer key' }
3
+ let(:host) { 'api.host' }
4
+ let(:shared_secret) { 'secret' }
5
+ let(:protocol) { 'http' }
6
+ let(:service_class) do
7
+ Class.new(QuizApiClient::Services::BaseApiService) do
8
+ def test_uri
9
+ uri
10
+ end
11
+ end
12
+ end
13
+ let(:service) do
14
+ service_class.new(QuizApiClient::Config.new do |c|
15
+ c.consumer_key = consumer_key
16
+ c.host = host
17
+ c.shared_secret = shared_secret
18
+ c.protocol = protocol
19
+ end)
20
+ end
21
+
22
+ def decode_token(token, secret)
23
+ JWT.decode(
24
+ token, secret, true,
25
+ algorithm: QuizApiClient::Services::JwtService::HASHING_ALGORITHM
26
+ )
27
+ end
28
+
29
+ describe '#uri' do
30
+ it 'returns full URI' do
31
+ expect(service.test_uri).to eq 'http://api.host'
32
+ end
33
+
34
+ context 'with https protocol' do
35
+ let(:protocol) { 'https' }
36
+
37
+ it 'uses specified protocol' do
38
+ expect(service.test_uri).to eq 'https://api.host'
39
+ end
40
+ end
41
+
42
+ context 'with port specified in host' do
43
+ let(:host) { 'api.host:8443' }
44
+
45
+ it 'includes port in URI' do
46
+ expect(service.test_uri).to eq 'http://api.host:8443'
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ describe QuizApiClient::Services::InteractionTypesService do
2
+ let(:host) { 'api.host' }
3
+ let(:config) { QuizApiClient::Config.new { |c| c.host = host } }
4
+ let(:subject) { described_class.new(config) }
5
+
6
+ describe '#list' do
7
+ let(:status_code) { 200 }
8
+ let(:expected_url) { "https://#{host}/api/interaction_types" }
9
+ let(:stubbed_response) { [{ 'name' => 'Type One', 'id' => 1 }] }
10
+
11
+ before do
12
+ stub_request(:get, expected_url)
13
+ .to_return(
14
+ status: status_code,
15
+ body: stubbed_response.to_json,
16
+ headers: { 'Content-Type' => 'application/json' }
17
+ )
18
+ end
19
+
20
+ it 'gets from quiz_api/api/interaction_types and returns the response' do
21
+ result = subject.list(token: 'token')
22
+ expect(result.parsed_response).to eql(stubbed_response)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ describe QuizApiClient::Services::ItemAnalysesService do
2
+ let(:host) { 'api.host' }
3
+ let(:config) { QuizApiClient::Config.new { |c| c.host = host } }
4
+ let(:subject) { described_class.new(config) }
5
+
6
+ describe '#list' do
7
+ let(:params) { { quiz_id: 1, id: 7 } }
8
+ let(:stubbed_response) { { 'id' => '1' } }
9
+ let(:expected_url) do
10
+ "https://#{host}/api/quizzes/#{params[:quiz_id]}/stats/item_analyses"
11
+ end
12
+ let(:expected_body) { '' }
13
+ let(:status_code) { 200 }
14
+
15
+ before do
16
+ stub_request(:get, expected_url)
17
+ .with(body: expected_body)
18
+ .to_return(
19
+ status: status_code,
20
+ body: stubbed_response.to_json,
21
+ headers: { 'Content-Type' => 'application/json' }
22
+ )
23
+ end
24
+
25
+ it 'posts to /api/quizzes/1/stats/item_analyses and returns the response' do
26
+ result = subject.list(params: params, token: 'token')
27
+ expect(result.parsed_response).to eql(stubbed_response)
28
+ end
29
+
30
+ it 'raises an error when no quiz_id is specified' do
31
+ params = {}
32
+ expect do
33
+ subject.list(params: params, token: 'token')
34
+ end.to raise_error 'Quiz Id Required'
35
+ end
36
+ end
37
+
38
+ describe '#get' do
39
+ let(:params) { { quiz_id: 1, id: 7 } }
40
+ let(:stubbed_response) { { 'id' => '1' } }
41
+ let(:expected_url) do
42
+ "https://#{host}/api/quizzes/#{params[:quiz_id]}/stats/item_analyses/#{params[:id]}"
43
+ end
44
+ let(:expected_body) { '' }
45
+ let(:status_code) { 200 }
46
+
47
+ before do
48
+ stub_request(:get, expected_url)
49
+ .with(body: expected_body)
50
+ .to_return(
51
+ status: status_code,
52
+ body: stubbed_response.to_json,
53
+ headers: { 'Content-Type' => 'application/json' }
54
+ )
55
+ end
56
+
57
+ it 'posts to /api/quizzes/1/stats/item_analyses/7 and returns the response' do
58
+ result = subject.get(params: params, token: 'token')
59
+ expect(result.parsed_response).to eql(stubbed_response)
60
+ end
61
+
62
+ it 'raises an error when no quiz_id is specified' do
63
+ params = {}
64
+ expect do
65
+ subject.list(params: params, token: 'token')
66
+ end.to raise_error 'Quiz Id Required'
67
+ end
68
+
69
+ context 'response' do
70
+ it 'returns the response with the correct status code' do
71
+ response = subject.get(params: params, token: 'token')
72
+ expect(response.code).to eq(status_code)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,56 @@
1
+ describe QuizApiClient::Services::ItemsService do
2
+ let(:host) { 'api.host' }
3
+ let(:config) { QuizApiClient::Config.new { |c| c.host = host } }
4
+ let(:subject) { described_class.new(config) }
5
+
6
+ describe '#list' do
7
+ let(:params) { { quiz_id: 1 } }
8
+ let(:stubbed_response) { { 'id' => 1, 'item' => { 'id' => 1 } } }
9
+ let(:expected_url) { "https://#{host}/api/quizzes/#{params[:quiz_id]}/items" }
10
+ let(:status_code) { 200 }
11
+
12
+ before do
13
+ stub_request(:get, expected_url)
14
+ .to_return(
15
+ status: status_code,
16
+ body: stubbed_response.to_json,
17
+ headers: { 'Content-Type' => 'application/json' }
18
+ )
19
+ end
20
+
21
+ it 'list from quiz_api/api/quizzes/{id}/items and returns the response' do
22
+ result = subject.list(params: params, token: 'token')
23
+ expect(result.parsed_response).to eql(stubbed_response)
24
+ end
25
+ end
26
+
27
+ describe '#create' do
28
+ let(:params) do
29
+ {
30
+ quiz_id: 1,
31
+ item_body: 'item body',
32
+ scoring_data: { value: 'value' },
33
+ scoring_algorithm: 'Equivalence',
34
+ interaction_type_id: 'choice',
35
+ user_response_type: 'response type'
36
+ }
37
+ end
38
+ let(:stubbed_response) { { 'id' => 1, 'item' => { 'id' => 1 } } }
39
+ let(:expected_url) { "https://#{host}/api/quizzes/#{params[:quiz_id]}/items" }
40
+ let(:status_code) { 201 }
41
+
42
+ before do
43
+ stub_request(:post, expected_url)
44
+ .to_return(
45
+ status: status_code,
46
+ body: stubbed_response.to_json,
47
+ headers: { 'Content-Type' => 'application/json' }
48
+ )
49
+ end
50
+
51
+ it 'posts to quiz_api/api/quizzes/{id}/items and returns the response' do
52
+ result = subject.create(params: params, token: 'token')
53
+ expect(result.parsed_response).to eql(stubbed_response)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ describe QuizApiClient::Services::JwtService do
2
+ let(:host) { 'test.host' }
3
+ let(:shared_secret) { 'secret' }
4
+ let(:consumer_key) { 'some consumer_key' }
5
+
6
+ let(:config) do
7
+ QuizApiClient::Config.new do |c|
8
+ c.shared_secret = shared_secret
9
+ c.host = host
10
+ c.consumer_key = consumer_key
11
+ end
12
+ end
13
+
14
+ subject do
15
+ QuizApiClient::Services::JwtService.new(config)
16
+ end
17
+
18
+ describe 'grant_permission' do
19
+ it 'generates a JWT using the shared_secret' do
20
+ jwt = subject.grant_permission(scope: 'quiz.create')
21
+
22
+ payload, header = JWT.decode(jwt, shared_secret, true, algorithm: 'HS512')
23
+
24
+ expect(payload['host']).to eq(host)
25
+ expect(payload['scope']).to eq('quiz.create')
26
+ expect(payload['consumer_key']).to eq(consumer_key)
27
+ expect(payload).not_to have_key('resource_id')
28
+ expect(payload).not_to have_key('user')
29
+ expect(payload).to have_key('exp')
30
+ expect(header['alg']).to eq('HS512')
31
+ end
32
+
33
+ it 'includes user uuid in payload when provided' do
34
+ jwt = subject.grant_permission(scope: 'quiz.create', uuid: '123')
35
+
36
+ payload = JWT.decode(jwt, shared_secret, true, algorithm: 'HS512').first
37
+
38
+ expect(payload['user']).to eq('uuid' => '123')
39
+ end
40
+
41
+ it 'merges in other provided user context' do
42
+ jwt = subject.grant_permission(scope: 'quiz.create', uuid: '123', user: { email: 'test@example.com' })
43
+
44
+ payload = JWT.decode(jwt, shared_secret, true, algorithm: 'HS512').first
45
+
46
+ expect(payload['user']).to eq('uuid' => '123', 'email' => 'test@example.com')
47
+ end
48
+
49
+ it 'allows arbitrary additional params' do
50
+ jwt = subject.grant_permission(scope: 'quiz.create', foo: 'bar', monkey: 'do')
51
+ payload = JWT.decode(jwt, shared_secret, true, algorithm: 'HS512').first
52
+ expect(payload['foo']).to eq 'bar'
53
+ expect(payload['monkey']).to eq 'do'
54
+ end
55
+
56
+ context 'with port in host' do
57
+ let(:host) { 'test.host:8443' }
58
+
59
+ it 'leaves port out of payload host' do
60
+ jwt = subject.grant_permission scope: 'quiz.build'
61
+ payload, _header = JWT.decode(jwt, shared_secret, true, algorithm: 'HS512')
62
+ expect(payload['host']).to eq 'test.host'
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,114 @@
1
+ describe QuizApiClient::Services::QtiImportsService do
2
+ let(:host) { 'api.host' }
3
+ let(:config) { QuizApiClient::Config.new { |c| c.host = host } }
4
+ let(:subject) { described_class.new(config) }
5
+
6
+ describe '#create' do
7
+ let(:params) { { url: 'https://fake-s3-url.com', quiz_id: 1 } }
8
+ let(:stubbed_response) { { 'id' => '1', 'status' => 'created' } }
9
+ let(:expected_url) { "https://#{host}/api/quizzes/1/qti_imports" }
10
+ let(:expected_body) { {} }
11
+ let(:status_code) { 200 }
12
+
13
+ before do
14
+ stub_request(:post, expected_url)
15
+ .with(body: expected_body)
16
+ .to_return(
17
+ status: 200,
18
+ body: stubbed_response.to_json,
19
+ headers: { 'Content-Type' => 'application/json' }
20
+ )
21
+ end
22
+
23
+ it 'posts to quiz_api/api/qti_imports and returns the response' do
24
+ result = subject.create(params: params, token: 'token')
25
+ expect(result.parsed_response).to eql(stubbed_response)
26
+ end
27
+
28
+ context 'response' do
29
+ it 'returns the response with the correct code' do
30
+ response = subject.create(params: params, token: 'token')
31
+ expect(response.code).to eq(status_code)
32
+ end
33
+ end
34
+
35
+ context 'no quiz id provided' do
36
+ let(:params) {}
37
+ it 'raises Quiz Id Required' do
38
+ expect do
39
+ subject.create(params: params, token: 'token')
40
+ end.to raise_error('Quiz Id Required')
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#banks' do
46
+ let(:params) { { everything: false, overwrite: false, assessment_question_banks: ['abc123'] } }
47
+ let(:stubbed_response) { { 'id' => '1', 'status' => 'created' } }
48
+ let(:expected_url) { "https://#{host}/api/qti_imports/banks" }
49
+ let(:expected_body) { {} }
50
+ let(:status_code) { 200 }
51
+
52
+ before do
53
+ stub_request(:post, expected_url)
54
+ .with(body: expected_body)
55
+ .to_return(
56
+ status: 200,
57
+ body: stubbed_response.to_json,
58
+ headers: { 'Content-Type' => 'application/json' }
59
+ )
60
+ end
61
+
62
+ it 'posts to quiz_api/api/qti_imports and returns the response' do
63
+ result = subject.banks(params: params, token: 'token')
64
+ expect(result.parsed_response).to eql(stubbed_response)
65
+ end
66
+
67
+ context 'response' do
68
+ it 'returns the response with the correct code' do
69
+ response = subject.banks(params: params, token: 'token')
70
+ expect(response.code).to eq(status_code)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#get_imported_consumer_settings' do
76
+ let(:params) { { quiz_id: 1 } }
77
+ let(:stubbed_response) { { 'score_to_keep' => 'highest', 'max_attempts' => '10' } }
78
+ let(:expected_url) { "https://#{host}/api/quizzes/1/qti_imports/imported_consumer_settings" }
79
+
80
+ let(:expected_body) { {} }
81
+ let(:status_code) { 200 }
82
+
83
+ before do
84
+ stub_request(:get, expected_url)
85
+ .with(body: expected_body)
86
+ .to_return(
87
+ status: 200,
88
+ body: stubbed_response.to_json,
89
+ headers: { 'Content-Type' => 'application/json' }
90
+ )
91
+ end
92
+
93
+ it 'posts to quiz_api/api/qti_imports and returns the response' do
94
+ result = subject.get_imported_consumer_settings(params: params, token: 'token')
95
+ expect(result.parsed_response).to eql(stubbed_response)
96
+ end
97
+
98
+ context 'response' do
99
+ it 'returns the response with the correct code' do
100
+ response = subject.get_imported_consumer_settings(params: params, token: 'token')
101
+ expect(response.code).to eq(status_code)
102
+ end
103
+ end
104
+
105
+ context 'no quiz id provided' do
106
+ let(:params) {}
107
+ it 'raises Quiz Id Required' do
108
+ expect do
109
+ subject.create(params: params, token: 'token')
110
+ end.to raise_error('Quiz Id Required')
111
+ end
112
+ end
113
+ end
114
+ end