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.
- checksums.yaml +4 -4
- data/lib/quiz_api_client/services/items_service.rb +11 -0
- data/lib/quiz_api_client/version.rb +1 -1
- data/spec/config_spec.rb +66 -0
- data/spec/contracts/interaction_types_service_spec.rb +22 -0
- data/spec/contracts/item_analyses_service_spec.rb +59 -0
- data/spec/contracts/items_service_spec.rb +59 -0
- data/spec/contracts/qti_imports_service_spec.rb +34 -0
- data/spec/contracts/quiz_clone_job_service_spec.rb +20 -0
- data/spec/contracts/quiz_clone_jobs_service_spec.rb +21 -0
- data/spec/contracts/quiz_entries_service_spec.rb +125 -0
- data/spec/contracts/quiz_service_spec.rb +68 -0
- data/spec/contracts/quiz_session_events_service_spec.rb +30 -0
- data/spec/contracts/quiz_session_result_service_spec.rb +42 -0
- data/spec/contracts/quiz_session_service_spec.rb +56 -0
- data/spec/contracts/quiz_sessions_service_spec.rb +28 -0
- data/spec/contracts/quizzes_service_spec.rb +80 -0
- data/spec/contracts/session_item_results_service_spec.rb +60 -0
- data/spec/contracts/session_items_service_spec.rb +21 -0
- data/spec/contracts/shared_banks_spec.rb +366 -0
- data/spec/contracts/shared_examples/http_delete_example.rb +56 -0
- data/spec/contracts/shared_examples/http_get_example.rb +139 -0
- data/spec/contracts/shared_examples/http_patch_example.rb +60 -0
- data/spec/contracts/shared_examples/http_post_example.rb +60 -0
- data/spec/contracts/shared_examples/http_put_example.rb +60 -0
- data/spec/http_client_spec.rb +347 -0
- data/spec/json_formatter_spec.rb +32 -0
- data/spec/quiz_api_client/http_request/failure_spec.rb +100 -0
- data/spec/quiz_api_client/http_request/metrics_spec.rb +75 -0
- data/spec/quiz_api_client_spec.rb +117 -0
- data/spec/services/base_api_service_spec.rb +50 -0
- data/spec/services/interaction_types_service_spec.rb +25 -0
- data/spec/services/item_analyses_service_spec.rb +76 -0
- data/spec/services/items_service_spec.rb +56 -0
- data/spec/services/jwt_service_spec.rb +66 -0
- data/spec/services/qti_imports_service_spec.rb +114 -0
- data/spec/services/quiz_analyses_service_spec.rb +44 -0
- data/spec/services/quiz_clone_job_service_spec.rb +41 -0
- data/spec/services/quiz_clone_jobs_service_spec.rb +41 -0
- data/spec/services/quiz_entries_service_spec.rb +71 -0
- data/spec/services/quiz_service_spec.rb +49 -0
- data/spec/services/quiz_session_events_service_spec.rb +42 -0
- data/spec/services/quiz_session_result_service_spec.rb +26 -0
- data/spec/services/quiz_session_service_spec.rb +49 -0
- data/spec/services/quiz_sessions_service_spec.rb +42 -0
- data/spec/services/quizzes_service_spec.rb +71 -0
- data/spec/services/session_item_results_service_spec.rb +33 -0
- data/spec/services/session_items_service_spec.rb +26 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/pact_config.rb +64 -0
- data/spec/support/pact_helper.rb +19 -0
- metadata +108 -39
- data/.dockerignore +0 -7
- data/.editorconfig +0 -16
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.rubocop.yml +0 -72
- data/CHANGELOG.md +0 -35
- data/Dockerfile +0 -12
- data/Gemfile +0 -5
- data/Jenkinsfile +0 -86
- data/bin/console +0 -7
- data/bin/contracts-generate +0 -26
- data/bin/setup +0 -65
- data/docker-compose.dev.override.yml +0 -11
- data/docker-compose.yml +0 -10
- 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
|