kosapi_client 0.5.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.
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