kosapi_client 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/bin/console +15 -0
  3. data/bin/setup +23 -0
  4. data/lib/kosapi_client.rb +22 -0
  5. data/lib/kosapi_client/api_client.rb +43 -0
  6. data/lib/kosapi_client/configuration.rb +23 -0
  7. data/lib/kosapi_client/entity.rb +19 -0
  8. data/lib/kosapi_client/entity/author.rb +17 -0
  9. data/lib/kosapi_client/entity/base_entity.rb +16 -0
  10. data/lib/kosapi_client/entity/base_person.rb +20 -0
  11. data/lib/kosapi_client/entity/boolean.rb +14 -0
  12. data/lib/kosapi_client/entity/course.rb +34 -0
  13. data/lib/kosapi_client/entity/course_event.rb +20 -0
  14. data/lib/kosapi_client/entity/data_mappings.rb +122 -0
  15. data/lib/kosapi_client/entity/enum.rb +11 -0
  16. data/lib/kosapi_client/entity/exam.rb +25 -0
  17. data/lib/kosapi_client/entity/id.rb +13 -0
  18. data/lib/kosapi_client/entity/link.rb +54 -0
  19. data/lib/kosapi_client/entity/ml_string.rb +42 -0
  20. data/lib/kosapi_client/entity/parallel.rb +20 -0
  21. data/lib/kosapi_client/entity/person.rb +10 -0
  22. data/lib/kosapi_client/entity/result_page.rb +46 -0
  23. data/lib/kosapi_client/entity/student.rb +25 -0
  24. data/lib/kosapi_client/entity/teacher.rb +14 -0
  25. data/lib/kosapi_client/entity/teacher_timetable_slot.rb +16 -0
  26. data/lib/kosapi_client/entity/timetable_slot.rb +16 -0
  27. data/lib/kosapi_client/hash_utils.rb +17 -0
  28. data/lib/kosapi_client/http_client.rb +36 -0
  29. data/lib/kosapi_client/kosapi_client.rb +45 -0
  30. data/lib/kosapi_client/kosapi_response.rb +32 -0
  31. data/lib/kosapi_client/oauth2_http_adapter.rb +36 -0
  32. data/lib/kosapi_client/request_builder.rb +59 -0
  33. data/lib/kosapi_client/request_builder_delegator.rb +52 -0
  34. data/lib/kosapi_client/resource.rb +12 -0
  35. data/lib/kosapi_client/resource/course_events_builder.rb +13 -0
  36. data/lib/kosapi_client/resource/courses_builder.rb +12 -0
  37. data/lib/kosapi_client/resource/exams_builder.rb +13 -0
  38. data/lib/kosapi_client/resource/parallels_builder.rb +20 -0
  39. data/lib/kosapi_client/resource/teachers_builder.rb +16 -0
  40. data/lib/kosapi_client/resource_mapper.rb +20 -0
  41. data/lib/kosapi_client/response_converter.rb +65 -0
  42. data/lib/kosapi_client/response_links.rb +40 -0
  43. data/lib/kosapi_client/response_preprocessor.rb +48 -0
  44. data/lib/kosapi_client/url_builder.rb +24 -0
  45. data/lib/kosapi_client/version.rb +3 -0
  46. data/spec/integration/course_events_spec.rb +13 -0
  47. data/spec/integration/courses_spec.rb +28 -0
  48. data/spec/integration/exams_spec.rb +20 -0
  49. data/spec/integration/parallels_spec.rb +68 -0
  50. data/spec/kosapi_client/api_client_spec.rb +22 -0
  51. data/spec/kosapi_client/configuration_spec.rb +30 -0
  52. data/spec/kosapi_client/entity/base_entity_spec.rb +20 -0
  53. data/spec/kosapi_client/entity/base_person_spec.rb +19 -0
  54. data/spec/kosapi_client/entity/boolean_spec.rb +23 -0
  55. data/spec/kosapi_client/entity/course_event_spec.rb +30 -0
  56. data/spec/kosapi_client/entity/data_mappings_spec.rb +154 -0
  57. data/spec/kosapi_client/entity/enum_spec.rb +20 -0
  58. data/spec/kosapi_client/entity/id_spec.rb +13 -0
  59. data/spec/kosapi_client/entity/link_spec.rb +89 -0
  60. data/spec/kosapi_client/entity/ml_string_spec.rb +52 -0
  61. data/spec/kosapi_client/entity/parallel_spec.rb +18 -0
  62. data/spec/kosapi_client/entity/result_page_spec.rb +42 -0
  63. data/spec/kosapi_client/entity/teacher_timetable_slot_spec.rb +19 -0
  64. data/spec/kosapi_client/entity/timetable_slot_spec.rb +22 -0
  65. data/spec/kosapi_client/hash_utils_spec.rb +20 -0
  66. data/spec/kosapi_client/http_client_spec.rb +30 -0
  67. data/spec/kosapi_client/kosapi_client_spec.rb +57 -0
  68. data/spec/kosapi_client/kosapi_response_spec.rb +96 -0
  69. data/spec/kosapi_client/oauth2_http_adapter_spec.rb +25 -0
  70. data/spec/kosapi_client/request_builder_delegator_spec.rb +72 -0
  71. data/spec/kosapi_client/request_builder_spec.rb +80 -0
  72. data/spec/kosapi_client/resource/courses_builder_spec.rb +20 -0
  73. data/spec/kosapi_client/resource/parallels_builder_spec.rb +49 -0
  74. data/spec/kosapi_client/resource_mapper_spec.rb +33 -0
  75. data/spec/kosapi_client/response_converter_spec.rb +58 -0
  76. data/spec/kosapi_client/response_links_spec.rb +52 -0
  77. data/spec/kosapi_client/response_preprocessor_spec.rb +51 -0
  78. data/spec/kosapi_client/url_builder_spec.rb +44 -0
  79. data/spec/spec_helper.rb +40 -0
  80. data/spec/support/client_helpers.rb +11 -0
  81. data/spec/support/helpers.rb +7 -0
  82. data/spec/support/shared_examples_for_fluent_api.rb +7 -0
  83. metadata +401 -0
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::Entity::Parallel do
4
+ let(:attributes) { { code: '42',
5
+ capacity_overfill: 'DENIED',
6
+ teacher: [{ xlink_href: 'teachers/smitkdan/', __content__: 'Ing. arch. Daniel Smitka Ph.D.' }]
7
+ } }
8
+
9
+
10
+ it 'parses parallel attributes' do
11
+ parallel = KOSapiClient::Entity::Parallel.parse(attributes)
12
+ expect(parallel.code).to eq 42
13
+ expect(parallel.capacity_overfill).to eq :denied
14
+ expect(parallel.teachers.first).to be_an_instance_of KOSapiClient::Entity::Link
15
+ expect(parallel.teachers.first.link_href).to eq 'teachers/smitkdan/'
16
+ expect(parallel.teachers.first.link_title).to eq 'Ing. arch. Daniel Smitka Ph.D.'
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::Entity::ResultPage do
4
+
5
+ ResultPage = KOSapiClient::Entity::ResultPage
6
+
7
+ subject(:result_page) { ResultPage.new([item], links) }
8
+ let(:item) { double(:item) }
9
+ let(:item2) { double(:second_item) }
10
+ let(:links) { instance_double(KOSapiClient::ResponseLinks, next: next_link) }
11
+ let(:next_page) { ResultPage.new([item2], instance_double(KOSapiClient::ResponseLinks, next: nil)) }
12
+ let(:next_link) { instance_double(KOSapiClient::Entity::Link, follow: next_page) }
13
+
14
+ describe '#each' do
15
+
16
+ it 'is auto-paginated by default' do
17
+ [item, item2].each { |it| expect(it).to receive(:foo) }
18
+ result_page.each { |it| it.foo }
19
+ end
20
+
21
+ context 'with auto-pagination disabled' do
22
+
23
+ subject(:result_page) { ResultPage.new([item], links, false) }
24
+
25
+ it 'is not auto-paginated' do
26
+ expect(item).to receive(:foo)
27
+ expect(item2).not_to receive(:foo)
28
+ result_page.each { |it| it.foo }
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ describe '#count' do
35
+
36
+ it 'returns item count' do
37
+ expect(result_page.count).to eq 1
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::Entity::TeacherTimetableSlot do
4
+
5
+ describe '.parse' do
6
+
7
+ it 'can be parsed from a hash' do
8
+ slot = described_class.parse({
9
+ id: '42', day: '1', first_hour: '3', duration: '2', parity: 'BOTH', title: 'Sleep'
10
+ })
11
+ expect(slot.id).to eq 42
12
+ expect(slot.day).to eq 1
13
+ expect(slot.first_hour).to eq 3
14
+ expect(slot.duration).to eq 2
15
+ expect(slot.parity).to eq :both
16
+ expect(slot.title).to eq 'Sleep'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::Entity::TimetableSlot do
4
+
5
+ TimetableSlot = KOSapiClient::Entity::TimetableSlot
6
+
7
+ describe '.parse' do
8
+
9
+ it 'can be parsed from a hash' do
10
+ slot = TimetableSlot.parse({id: '42', day: '1', first_hour: '3', duration: '2', parity: 'BOTH', room: { __contents__: 'no-title', xlink_href: 'rooms/10010205/' }})
11
+ expect(slot.id).to eq 42
12
+ expect(slot.day).to eq 1
13
+ expect(slot.first_hour).to eq 3
14
+ expect(slot.duration).to eq 2
15
+ expect(slot.parity).to eq :both
16
+ expect(slot.room).to be_an_instance_of(Link)
17
+ expect(slot.room.link_href).to eq 'rooms/10010205/'
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::HashUtils do
4
+
5
+ let(:k1) {double(visited: :k1)}
6
+ let(:k2) {double(visited: :k2)}
7
+ let(:source_hash) { {k1 => [{k2 => :foo}]} }
8
+
9
+ it 'iterates over all keys' do
10
+ expect(k1).to receive(:visited)
11
+ expect(k2).to receive(:visited)
12
+ KOSapiClient::HashUtils.deep_transform_hash_keys(source_hash) { |key| key.visited }
13
+ end
14
+
15
+ it 'returns hash with modified keys' do
16
+ result = KOSapiClient::HashUtils.deep_transform_hash_keys(source_hash) { |key| key.visited }
17
+ expect(result).to eq({k1: [{k2: :foo}]})
18
+ end
19
+
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::HTTPClient do
4
+
5
+ let(:response) { double(parsed: {}) }
6
+ let(:http_adapter) { instance_double(KOSapiClient::OAuth2HttpAdapter) }
7
+ let(:preprocessed_hash) { {} }
8
+ let(:preprocessor) { instance_double(KOSapiClient::ResponsePreprocessor, preprocess: preprocessed_hash) }
9
+ let(:converter) { instance_double(KOSapiClient::ResponseConverter, convert: nil) }
10
+ subject(:client) { KOSapiClient::HTTPClient.new(http_adapter, preprocessor, converter) }
11
+
12
+ describe '#send_request' do
13
+
14
+ it 'calls http adapter' do
15
+ expect(http_adapter).to receive(:send_request).and_return(response)
16
+ client.send_request(:get, 'http://example.com')
17
+ end
18
+
19
+ end
20
+
21
+ describe '#process_response' do
22
+
23
+ it 'converts items with converter' do
24
+ expect(converter).to receive(:convert).and_return(:foo)
25
+ expect(client.process_response(nil)).to eq(:foo)
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient do
4
+
5
+ before do
6
+ KOSapiClient.configure do |c|
7
+ c.client_id = 'foo'
8
+ c.client_secret = 'bar'
9
+ end
10
+ @client = KOSapiClient.client
11
+ end
12
+
13
+ describe '.configure' do
14
+
15
+ it 'can be configured via block' do
16
+ expect(@client).not_to be_nil
17
+ end
18
+
19
+ end
20
+
21
+ describe '.reset' do
22
+
23
+ it 'cleans stored ApiClient instance' do
24
+ KOSapiClient.reset
25
+ expect(KOSapiClient.client).not_to be @client
26
+ end
27
+
28
+ end
29
+
30
+ describe '.client' do
31
+
32
+ it 'provides client instance via reader' do
33
+ expect(KOSapiClient.client).to eq @client
34
+ end
35
+
36
+ end
37
+
38
+ it 'delegates missing methods to stored client instance' do
39
+ expect(@client).to receive(:course_events)
40
+ KOSapiClient.course_events
41
+ end
42
+
43
+ it 'handles missing methods not defined on client' do
44
+ expect { KOSapiClient.foo }.to raise_error NoMethodError
45
+ end
46
+
47
+ it 'throws error when sending request with no configured credentials' do
48
+ KOSapiClient.reset
49
+ expect { KOSapiClient.course_events.items }.to raise_error(RuntimeError)
50
+ end
51
+
52
+ it 'responds to client methods' do
53
+ allow(@client).to receive(:foo)
54
+ expect(KOSapiClient).to respond_to(:foo)
55
+ end
56
+
57
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::KOSapiResponse do
4
+
5
+ subject(:response) { KOSapiClient::KOSapiResponse.new(result) }
6
+ let(:client) { instance_double(KOSapiClient::HTTPClient) }
7
+
8
+ context 'with paginated response' do
9
+ let(:result) { {atom_feed: {atom_entry: [:entry1, :entry2], atom_link: [{rel: 'prev', href: 'courses/?offset=0&limit=10'}, {rel: 'next', href: 'courses/?offset=20&limit=10'}]}} }
10
+
11
+ describe '#is_paginated?' do
12
+
13
+ it 'returns true when result contains atom feed' do
14
+ expect(response.is_paginated?).to be
15
+ end
16
+
17
+ end
18
+
19
+ describe '#items' do
20
+
21
+ it 'returns all entries' do
22
+ expect(response.items).to eq [:entry1, :entry2]
23
+ end
24
+
25
+ end
26
+
27
+ describe '#item' do
28
+
29
+ it 'returns first entry' do
30
+ expect(response.item).to eq :entry1
31
+ end
32
+
33
+ end
34
+
35
+ describe '#links_hash' do
36
+
37
+ it 'extracts links from feed' do
38
+ expect(response.links_hash).to eq [{rel: 'prev', href: 'courses/?offset=0&limit=10'}, {rel: 'next', href: 'courses/?offset=20&limit=10'}]
39
+ end
40
+
41
+
42
+ context 'with single link' do
43
+ let(:result) { {atom_feed: {atom_link: {rel: 'next', href: 'courses/?offset=10&limit=10'}}} }
44
+
45
+ it 'extracts next link from feed' do
46
+ expect(response.links_hash).to eq({rel: 'next', href: 'courses/?offset=10&limit=10'})
47
+ end
48
+ end
49
+
50
+ context 'with no links' do
51
+ let(:result) { {atom_feed: {}} }
52
+
53
+ it 'returns null' do
54
+ expect(response.links_hash).not_to be
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ context 'with not-paginated response' do
63
+ let(:result) { { atom_entry: :entry} }
64
+
65
+ describe '#is_paginated?' do
66
+
67
+ it 'returns false when result contains atom feed' do
68
+ expect(response.is_paginated?).not_to be
69
+ end
70
+
71
+ end
72
+
73
+ describe '#items' do
74
+
75
+ it 'returns single entry wrapped in array' do
76
+ expect(response.items).to eq [:entry]
77
+ end
78
+
79
+ end
80
+
81
+ describe '#item' do
82
+
83
+ it 'returns first entry' do
84
+ expect(response.item).to eq :entry
85
+ end
86
+
87
+ end
88
+
89
+ describe '#links_hash' do
90
+ it 'returns null' do
91
+ expect(response.links_hash).not_to be
92
+ end
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::OAuth2HttpAdapter, :vcr do
4
+ KOSAPI_ROOT_URL = 'https://kosapi.fit.cvut.cz/api/3/'
5
+ subject(:client) { KOSapiClient::OAuth2HttpAdapter.new(credentials, KOSAPI_ROOT_URL) }
6
+
7
+
8
+ context 'with invalid OAUTH credentials' do
9
+ let(:credentials) { { client_id: 'invalid_client_id', client_secret: 'invalid_secret' } }
10
+
11
+ it 'throws authentication error when fetching resource' do
12
+ expect { client.send_request(:get, 'courses') }.to raise_error(OAuth2::Error)
13
+ end
14
+ end
15
+
16
+ context 'with valid OAUTH credentials' do
17
+ let(:credentials) { { client_id: ENV['KOSAPI_OAUTH_CLIENT_ID'], client_secret: ENV['KOSAPI_OAUTH_CLIENT_SECRET'] } }
18
+
19
+ it 'fetches response from a REST resource' do
20
+ response = client.send_request(:get, 'courses')
21
+ expect(response).not_to be_nil
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::RequestBuilderDelegator do
4
+
5
+ let(:response) { double(:response) }
6
+ let(:builder) { double(:builder, response: response, finalize: nil) }
7
+ let(:block) { -> { 1 + 1 } }
8
+ subject(:delegator) { KOSapiClient::RequestBuilderDelegator.new(builder) }
9
+
10
+ describe '#method_missing' do
11
+
12
+ it 'delegates methods which builder responds to, to the builder' do
13
+ expect(builder).to receive(:foo)
14
+ delegator.foo
15
+ end
16
+
17
+ it 'evaluates builder when builder is not responding to a delegated method' do
18
+ expect(builder).to receive(:finalize)
19
+ expect(response).to receive(:bar)
20
+ delegator.bar
21
+ end
22
+
23
+ it 'delegates all methods to response once RequestBuilder is evaluated' do
24
+ allow(response).to receive(:bar)
25
+ expect(builder).not_to receive(:foo)
26
+ delegator.bar
27
+ expect { delegator.foo }.to raise_error(NoMethodError)
28
+ end
29
+
30
+ it 'throws error when method is not defined on builder or response' do
31
+ expect { delegator.foo }.to raise_error(NoMethodError)
32
+ end
33
+
34
+ it 'replaces builder.self with self' do
35
+ allow(builder).to receive(:foo).and_return(builder)
36
+ expect(delegator.foo).to be delegator
37
+ end
38
+
39
+ it 'delegates call with block to builder' do
40
+ allow(builder).to receive(:foo).with(:x) do |arg, &block|
41
+ expect(block).not_to be_nil
42
+ end
43
+
44
+ delegator.foo(:x, &block)
45
+ end
46
+
47
+ it 'delegates call with block to response' do
48
+ allow(response).to receive(:foo).with(:x) do |arg, &block|
49
+ expect(block).not_to be_nil
50
+ end
51
+
52
+ delegator.foo(:x, &block)
53
+ end
54
+
55
+ end
56
+
57
+ describe '#respond_to_missing?' do
58
+
59
+ it 'responds to methods on builder' do
60
+ allow(builder).to receive(:foo)
61
+ expect(delegator).to respond_to(:foo)
62
+ end
63
+
64
+ it 'responds to methods on response when evaluated' do
65
+ allow(response).to receive(:foo)
66
+ delegator.foo
67
+ expect(delegator).to respond_to(:foo)
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe KOSapiClient::RequestBuilder do
4
+
5
+ let(:url_builder) { instance_double(KOSapiClient::URLBuilder, url: 'http://example.com', set_path: nil, set_query_param: nil) }
6
+ let(:response) { double(foo: :bar) }
7
+ let(:http_client) { double(send_request: response) }
8
+ subject(:builder) { KOSapiClient::RequestBuilder.new('http://example.com', http_client, url_builder) }
9
+
10
+
11
+
12
+ describe '#finalize' do
13
+
14
+ it 'sends http request' do
15
+ expect(http_client).to receive(:send_request)
16
+ builder.finalize
17
+ end
18
+
19
+ it 'stores response' do
20
+ builder.finalize
21
+ expect(builder.response).to eq response
22
+ end
23
+
24
+ end
25
+
26
+ describe '#find' do
27
+
28
+ it_behaves_like 'fluent api command', method_name, 10
29
+
30
+ it 'sets path in URL' do
31
+ expect(url_builder).to receive(:set_path).with(10)
32
+ builder.find(10)
33
+ end
34
+
35
+ end
36
+
37
+ describe '#offset' do
38
+
39
+ it_behaves_like 'fluent api command', method_name, 10
40
+
41
+ it 'sets query param' do
42
+ expect(url_builder).to receive(:set_query_param).with(:offset, 10)
43
+ builder.offset(10)
44
+ end
45
+
46
+ end
47
+
48
+ describe '#limit' do
49
+
50
+ it_behaves_like 'fluent api command', method_name, 10
51
+
52
+ it 'sets query param' do
53
+ expect(url_builder).to receive(:set_query_param).with(:limit, 10)
54
+ builder.limit(10)
55
+ end
56
+
57
+ end
58
+
59
+ describe '#query' do
60
+
61
+ it_behaves_like 'fluent api command', method_name, foo: 10
62
+
63
+ it 'sets query param' do
64
+ expect(url_builder).to receive(:set_query_param).with(:query, 'foo==10;bar==20')
65
+ builder.query(foo: 10, bar: 20)
66
+ end
67
+
68
+ it 'throws error when params are empty' do
69
+ expect { builder.query({}) }.to raise_error(RuntimeError)
70
+ end
71
+
72
+ it 'accepts string instead of a hash' do
73
+ expect(url_builder).to receive(:set_query_param).with(:query, 'foo==10;bar==20')
74
+ builder.query('foo==10;bar==20')
75
+ end
76
+
77
+ end
78
+
79
+
80
+ end