diff --git a/README.md b/README.md index ba2b5d3..b9e0e38 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A simple Ruby client library for [KOSapi RESTful service](https://kosapi.fit.cvu Add this line to your application's Gemfile: - gem 'kosapi_client', github: 'flexik/kosapi_client' + gem 'kosapi_client', github: 'flexik/kosapi_client.rb' And then execute: @@ -46,7 +46,7 @@ KOSapiClient can be created and configured in two ways. The simple way is to call `KOSapiClient.new`, which returns ApiClient instance. ```ruby -client = KOSapiClient.new(OAUTH_CLIENT_ID, OAUTH_SECRET) +client = KOSapiClient.new({client_id: OAUTH_CLIENT_ID, client_secret: OAUTH_SECRET}) client.parallels.find(42) ``` @@ -63,6 +63,43 @@ end KOSapiClient.parallels.find(42) ``` +## How to extend API functionality + +### Manualy explore KOSapi + +```ruby +KOSapiClient.configure do |c| + c.client_id = ENV['KOSAPI_OAUTH_CLIENT_ID'] + c.client_secret = ENV['KOSAPI_OAUTH_CLIENT_SECRET'] +end + +puts KOSapiClient.http_client.send_debug_request(:get, '/courses/MI-PAA/instances/B141').to_yaml +``` + +### Add entity + +### Add resource + +1. Add concrete resource builder to `lib/kosapi_client/resource/`. Use current resources as an inspiration. +2. Register resource in `lib/kosapi_client/api_client.rb` like: + +```ruby +module KOSapiClient + + class ApiClient + include ResourceMapper + + # accessible resources definition + resource :courses + resource :course_events + resource :parallels + resource :exams + resource :semesters + resource :new_resource + + attr_reader :http_client + ... +``` ## Contributing diff --git a/kosapi_client.gemspec b/kosapi_client.gemspec index 5e737be..185f8c8 100644 --- a/kosapi_client.gemspec +++ b/kosapi_client.gemspec @@ -23,14 +23,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec' spec.add_development_dependency 'rspec-given' spec.add_development_dependency 'dotenv' - spec.add_development_dependency 'vcr' spec.add_development_dependency 'codeclimate-test-reporter' spec.add_development_dependency 'guard-rspec' spec.add_development_dependency 'coveralls' spec.add_development_dependency 'fuubar', '~> 2.0.0.rc1' spec.add_runtime_dependency 'oauth2' - spec.add_runtime_dependency 'faraday', '~> 0.8.9' # VCR does not work with newer versions yet + spec.add_runtime_dependency 'faraday', '~> 0.9.0' spec.add_runtime_dependency 'activesupport' spec.add_runtime_dependency 'escape_utils' unless RUBY_PLATFORM == 'java' # used for uri_template spec.add_runtime_dependency 'uri_template' diff --git a/lib/kosapi_client/api_client.rb b/lib/kosapi_client/api_client.rb index 29f4880..1a98d0e 100644 --- a/lib/kosapi_client/api_client.rb +++ b/lib/kosapi_client/api_client.rb @@ -8,7 +8,8 @@ class ApiClient resource :course_events resource :parallels resource :exams - + resource :semesters + attr_reader :http_client ## diff --git a/lib/kosapi_client/entity.rb b/lib/kosapi_client/entity.rb index 34c7ff1..05b8545 100644 --- a/lib/kosapi_client/entity.rb +++ b/lib/kosapi_client/entity.rb @@ -7,6 +7,8 @@ require 'kosapi_client/entity/data_mappings' require 'kosapi_client/entity/base_entity' require 'kosapi_client/entity/result_page' +require 'kosapi_client/entity/semester' +require 'kosapi_client/entity/coursin' require 'kosapi_client/entity/course_event' require 'kosapi_client/entity/course' require 'kosapi_client/entity/timetable_slot' @@ -16,3 +18,4 @@ require 'kosapi_client/entity/teacher' require 'kosapi_client/entity/student' require 'kosapi_client/entity/exam' + diff --git a/lib/kosapi_client/entity/author.rb b/lib/kosapi_client/entity/author.rb index d5f9ef6..b3042d4 100644 --- a/lib/kosapi_client/entity/author.rb +++ b/lib/kosapi_client/entity/author.rb @@ -8,7 +8,7 @@ def initialize(name) @name = name end - def self.parse(contents) + def self.parse(contents, context = {}) new(contents[:atom_name]) end end diff --git a/lib/kosapi_client/entity/boolean.rb b/lib/kosapi_client/entity/boolean.rb index 741a6fd..aa417cc 100644 --- a/lib/kosapi_client/entity/boolean.rb +++ b/lib/kosapi_client/entity/boolean.rb @@ -2,7 +2,7 @@ module KOSapiClient module Entity class Boolean - def self.parse(str) + def self.parse(str, context = {}) return true if str == 'true' return false if str == 'false' raise "Boolean parsing failed, invalid string: #{str}" diff --git a/lib/kosapi_client/entity/course.rb b/lib/kosapi_client/entity/course.rb index 5e14142..a19317a 100644 --- a/lib/kosapi_client/entity/course.rb +++ b/lib/kosapi_client/entity/course.rb @@ -27,8 +27,7 @@ class Course < BaseEntity map_data :superior_course, Link map_data :subcourses, Link map_data :tutorials_contents, MLString - map_data :instance #todo - + map_data :instance, Coursin end end end diff --git a/lib/kosapi_client/entity/coursin.rb b/lib/kosapi_client/entity/coursin.rb new file mode 100644 index 0000000..f0855ac --- /dev/null +++ b/lib/kosapi_client/entity/coursin.rb @@ -0,0 +1,17 @@ +module KOSapiClient + module Entity + class Coursin < BaseEntity + + map_data :capacity + map_data :capacity_overfill, Integer + map_data :course # TODO: fix circular reference. map_data :course, Course + map_data :occupied, Integer + map_data :semester + map_data :tutorial_capacity, Integer + map_data :examiners, [Link], array_wrapper_element: :teacher + map_data :guarantors, [Link], array_wrapper_element: :teacher + map_data :instructors, [Link], array_wrapper_element: :teacher + map_data :lecturers, [Link], array_wrapper_element: :teacher + end + end +end diff --git a/lib/kosapi_client/entity/data_mappings.rb b/lib/kosapi_client/entity/data_mappings.rb index e843466..1fd338b 100644 --- a/lib/kosapi_client/entity/data_mappings.rb +++ b/lib/kosapi_client/entity/data_mappings.rb @@ -12,6 +12,10 @@ def to_hash result end + def dump + self.to_hash.to_yaml + end + private def convert_value(val) if val.respond_to? :to_hash @@ -44,34 +48,39 @@ def attr_mappings # @param [Hash] content hash structure from API response corresponding to single domain object # @return [BaseEntity] parsed domain object def parse(content, context = {}) - instance = new() - set_mapped_attributes(instance, content) + instance = new + set_mapped_attributes(instance, content, context) instance end # Creates new domain object instance and sets values # of mapped domain object attributes from source hash. # Attributes are mapped by .map_data method. - def set_mapped_attributes(instance, source_hash) + def set_mapped_attributes(instance, source_hash, context) if self.superclass.respond_to? :set_mapped_attributes - self.superclass.set_mapped_attributes(instance, source_hash) + self.superclass.set_mapped_attributes(instance, source_hash, context) end raise "Missing data mappings for entity #{self}" unless @data_mappings @data_mappings.each do |name, options| - set_mapped_attribute(instance, name, source_hash, options) + set_mapped_attribute(instance, name, source_hash, options, context) end end private - def set_mapped_attribute(instance, name, source_hash, mapping_options) + def set_mapped_attribute(instance, name, source_hash, mapping_options, context) namespace = mapping_options[:namespace] src_element = mapping_options[:element] || name + if namespace key = "#{namespace}_#{src_element}".to_sym else key = src_element end + value = source_hash[key] + + value = value[mapping_options[:array_wrapper_element]] if mapping_options.key? :array_wrapper_element + if value.nil? raise "Missing value for attribute #{name}" if mapping_options[:required] if mapping_options[:type].is_a?(Array) @@ -80,17 +89,18 @@ def set_mapped_attribute(instance, name, source_hash, mapping_options) return end else - value = convert_type(value, mapping_options[:type]) + value = convert_type(value, mapping_options[:type], context) end instance.send("#{name}=".to_sym, value) end - def convert_type(value, type) + def convert_type(value, type, context = {}) return value.to_i if type == Integer return value if type == String - return convert_array(value, type.first) if type.is_a?(Array) + return convert_array(value, type.first, context) if type.is_a?(Array) + + return type.parse value, context if type.respond_to? :parse - return type.parse(value) if type.respond_to? :parse raise "Unknown type #{type} to convert value #{value} to." end @@ -98,14 +108,13 @@ def convert_type(value, type) # It checks whether the value is really an array, because # when API returns a single value it does not get parsed # into an array. - def convert_array(values, type) + def convert_array(values, type, context) if values.is_a?(Array) - values.map { |it| convert_type(it, type) } + values.map { |it| convert_type(it, type, context) } else - [ convert_type(values, type) ] + [ convert_type(values, type, context) ] end end - end end end diff --git a/lib/kosapi_client/entity/enum.rb b/lib/kosapi_client/entity/enum.rb index 09aa3b4..4a80b35 100644 --- a/lib/kosapi_client/entity/enum.rb +++ b/lib/kosapi_client/entity/enum.rb @@ -2,7 +2,7 @@ module KOSapiClient module Entity class Enum - def self.parse(contents) + def self.parse(contents, context = {}) contents.downcase.to_sym end diff --git a/lib/kosapi_client/entity/id.rb b/lib/kosapi_client/entity/id.rb index 54fcb27..3113007 100644 --- a/lib/kosapi_client/entity/id.rb +++ b/lib/kosapi_client/entity/id.rb @@ -2,7 +2,7 @@ module KOSapiClient module Entity class Id < String - def self.parse(str) + def self.parse(str, context = {}) id = str.split(':').last new(id) end diff --git a/lib/kosapi_client/entity/link.rb b/lib/kosapi_client/entity/link.rb index 1439e92..f6f975c 100644 --- a/lib/kosapi_client/entity/link.rb +++ b/lib/kosapi_client/entity/link.rb @@ -4,16 +4,16 @@ class Link attr_reader :link_title, :link_href, :link_rel - def initialize(title, href, rel, client = nil) + def initialize(title, href, rel, client) @link_title = title @link_href = escape_url(href) @link_rel = rel @client = client end - def self.parse(contents) + def self.parse(contents, context) href = contents[:xlink_href] || contents[:href] - new(contents[:__content__], href, contents[:rel]) + new(contents[:__content__], href, contents[:rel], context[:client]) end def link_id diff --git a/lib/kosapi_client/entity/ml_string.rb b/lib/kosapi_client/entity/ml_string.rb index 3a9fd6a..1f97fcc 100644 --- a/lib/kosapi_client/entity/ml_string.rb +++ b/lib/kosapi_client/entity/ml_string.rb @@ -17,7 +17,7 @@ def to_s(lang = :implicit) @translations[lang] end - def self.parse(item) + def self.parse(item, context = {}) unless item.is_a?(Array) item = [item] end diff --git a/lib/kosapi_client/entity/semester.rb b/lib/kosapi_client/entity/semester.rb new file mode 100644 index 0000000..5ece603 --- /dev/null +++ b/lib/kosapi_client/entity/semester.rb @@ -0,0 +1,11 @@ +module KOSapiClient + module Entity + class Semester < BaseEntity + + map_data :name, MLString + map_data :end_date, Time + map_data :start_date, Time + + end + end +end diff --git a/lib/kosapi_client/http_client.rb b/lib/kosapi_client/http_client.rb index 4885971..be46d33 100644 --- a/lib/kosapi_client/http_client.rb +++ b/lib/kosapi_client/http_client.rb @@ -1,22 +1,32 @@ module KOSapiClient class HTTPClient - def initialize(http_adapter, preprocessor = ResponsePreprocessor.new, converter = ResponseConverter.new(self)) + def initialize(http_adapter, preprocessor = ResponsePreprocessor.new, converter = ResponseConverter.new) @http_adapter = http_adapter @preprocessor = preprocessor @converter = converter end - def send_request(verb, url, options = {}) + def send_debug_request(verb, url, options = {}) + send_request verb, url, options, true + end + + def send_request(verb, url, options = {}, debug_request = false) absolute_url = get_absolute_url(url) + + p ">> #{absolute_url}" if debug_request + result = @http_adapter.send_request(verb, absolute_url, options) - process_response(result) + process_response(result, debug_request) end - def process_response(result) + def process_response(result, debug_request = false) preprocessed = @preprocessor.preprocess(result) + + return preprocessed if debug_request + response = KOSapiClient::KOSapiResponse.new(preprocessed) - @converter.convert(response) + @converter.convert(response, create_context) end def get_absolute_url(url) @@ -27,6 +37,12 @@ def get_absolute_url(url) end end + def create_context + { + client: self + } + end + private def is_absolute(url) url.start_with?('http') diff --git a/lib/kosapi_client/request_builder.rb b/lib/kosapi_client/request_builder.rb index 2fa046d..8446924 100644 --- a/lib/kosapi_client/request_builder.rb +++ b/lib/kosapi_client/request_builder.rb @@ -19,6 +19,11 @@ def limit(num) self end + def sem(code) + @url_builder.set_query_param(:sem, code) + self + end + def query(params = {}) raise 'Empty parameters to query are not allowed' if params.empty? if params.instance_of?(String) diff --git a/lib/kosapi_client/resource.rb b/lib/kosapi_client/resource.rb index 4e3e1d2..b90cdd1 100644 --- a/lib/kosapi_client/resource.rb +++ b/lib/kosapi_client/resource.rb @@ -3,6 +3,7 @@ require 'kosapi_client/resource/parallels_builder' require 'kosapi_client/resource/exams_builder' require 'kosapi_client/resource/course_events_builder' +require 'kosapi_client/resource/semesters_builder' module KOSapiClient module Resource diff --git a/lib/kosapi_client/resource/courses_builder.rb b/lib/kosapi_client/resource/courses_builder.rb index 54854c5..532c9d7 100644 --- a/lib/kosapi_client/resource/courses_builder.rb +++ b/lib/kosapi_client/resource/courses_builder.rb @@ -7,6 +7,85 @@ def detail(level = 1) self end + ### + # GET /courses/{code}/parallels + # Vrátí paralelky (instance) předmětu + # a) v aktuálním semestru, + # b) ve zvolených semestrech parametrem +sem+, + # c) nebo pro všechny semestry (parametr sem=none). + # + # URI: https://kosapi.fit.cvut.cz/api/3/courses/{code}/parallels/ + # + # Typ obsahu: Parallel + # Parametry: sem, fields, lang, limit, locEnums, multilang, offset, orderBy, query + # Rozsah dat: omezený / záznamy pro danou fakultu /?/ + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Courses#GET-coursescodeparallels + ## + # Example: + # client.courses.find({course_code}).parallels + # client.courses.find({course_code}).sem({semester_code}).parallels + ### + def parallels + raise 'Call #find before asking for parallels' unless id_set? + url_builder.set_path(id, 'parallels') + self + end + + ### + # GET /courses/{code}/students + # Vrátí studenty zapsané na (instanci) předmětu + # a) v aktuálním semestru, + # b) ve zvolených semestrech parametrem +sem+, + # c) nebo pro všechny semestry (parametr sem=none). + # + # URI: https://kosapi.fit.cvut.cz/api/3/courses/{code}/students/ + # + # Typ obsahu: Student + # Parametry: sem, fields, lang, limit, locEnums, multilang, offset, orderBy, query + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Courses#GET-coursescodestudents + ## + # Example: + # client.courses.find({course_code}).students + # client.courses.find({course_code}).sem({semester_code}).students + ### + def students + raise 'Call #find before asking for students' unless id_set? + url_builder.set_path(id, 'students') + self + end + + ### + # GET /courses/{code}/instances + # Vrátí všechny instance daného předmětu – pouze atributy instance, nikoli obecné předmětu. + # Ve většině případech budete chtít použít spíše zdroj /courses/{code} s parametrem +sem+. + # + # URI: https://kosapi.fit.cvut.cz/api/3/courses/{code}/instances/ + # + # Typ obsahu: Coursin + # Proměnné: {code} kód předmětu + # Parametry: fields, lang, multilang, locEnums + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Courses#GET-coursescodeinstances + ## + # Example: + # client.courses.find({course_code}).instances + ### + def instances + raise 'Call #find before asking for course instances' unless id_set? + url_builder.set_path(id, 'instances') + self + end + + ### + # GET /courses/{code}/instances/{semester} + ### + def instance(semester) + raise 'Call #find before asking for course instance' unless id_set? + url_builder.set_path(id, 'instances', semester) + finalize + end end end end diff --git a/lib/kosapi_client/resource/parallels_builder.rb b/lib/kosapi_client/resource/parallels_builder.rb index 76cc60a..229d487 100644 --- a/lib/kosapi_client/resource/parallels_builder.rb +++ b/lib/kosapi_client/resource/parallels_builder.rb @@ -13,7 +13,6 @@ def students url_builder.set_path(id, 'students') self end - end end end diff --git a/lib/kosapi_client/resource/semesters_builder.rb b/lib/kosapi_client/resource/semesters_builder.rb new file mode 100644 index 0000000..46734fa --- /dev/null +++ b/lib/kosapi_client/resource/semesters_builder.rb @@ -0,0 +1,116 @@ +module KOSapiClient + module Resource + class SemestersBuilder < RequestBuilder + ### + # Semestry: + # GET /semesters => client.semesters.items + # GET /semesters/{code} => client.semesters.find({code}) + # GET /semesters/current => client.semesters.current + # GET /semesters/next => client.semesters.next + # + # TODO: + # GET /semesters/prev + # GET /semesters/scheduling + ### + + ### + # GET /semesters + # Vrátí všechny semestry. + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/ + # + # Typ obsahu: Semester + # Parametry: fields, lang, limit, locEnums, multilang, offset, orderBy, query + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semesters + ## + # Example: + # client.semesters.items + ### + + ### + # GET /semesters/{code} + # Vrátí konkrétní semestr podle jeho KOSího kódu. + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/{code} + # + # Typ obsahu: Semester + # Proměnné: {code} kód semestru + # Parametry: fields, lang, locEnums, multilang + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semesterscode + ## + # Example: + # client.semesters.find({code}) + ### + + ### + # GET /semesters/current + # Vrátí aktuální semestr, ve kterém probíhá výuka (v KOS parametr AKTSEM). + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/current + # Typ obsahu: Semester + # Parametry: fields, lang, locEnums, multilang + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semesterscurrent + ## + # Example: + # client.semesters.current + ### + def current + find 'current' + end + + ### + # GET /semesters/next + # Vrátí příští semestr, tj. následující po aktuálním. + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/next + # + # Typ obsahu: Semester + # Parametry: fields, lang, locEnums, multilang + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semestersnext + ## + # Example: + # client.semesters.next + ### + def next + find 'next' + end + + ### + # TODO + # GET /semesters/prev + # Vrátí předchozí semestr. + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/prev + # + # Typ obsahu: Semester + # Parametry: fields, lang, locEnums, multilang + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semestersprev + ## + # Example: + # + ### + + ### + # TODO + # GET /semesters/scheduling + # Vrátí semestr, ve kterém probíhají zápisy do rozvrhu (v KOS parametr SEMPROROZ). + # + # URI: https://kosapi.fit.cvut.cz/api/3/semesters/scheduling + # + # Typ obsahu: Semester + # Parametry: fields, lang, locEnums, multilang + # + # @link: https://kosapi.fit.cvut.cz/projects/kosapi/wiki/Semesters#GET-semestersscheduling + ## + # Example: + # + ### + end + end +end + diff --git a/lib/kosapi_client/response_converter.rb b/lib/kosapi_client/response_converter.rb index 6aee7fe..adf0bdf 100644 --- a/lib/kosapi_client/response_converter.rb +++ b/lib/kosapi_client/response_converter.rb @@ -7,15 +7,11 @@ module KOSapiClient class ResponseConverter - def initialize(client) - @client = client - end - - def convert(response) + def convert(response, context) if response.is_paginated? - convert_paginated(response) + convert_paginated(response, context) else - convert_single(response.item) + convert_single(response.item, context) end end @@ -24,20 +20,20 @@ def convert(response) # @param response [KOSapiResponse] Response object wrapping array of hashes corresponding to entries # @return [ResultPage] ResultPage of domain objects - def convert_paginated(response) + def convert_paginated(response, context) items = response.items || [] - converted_items = items.map{ |p| convert_single(p) } - Entity::ResultPage.new(converted_items, create_links(response)) + converted_items = items.map{ |p| convert_single(p, context) } + Entity::ResultPage.new(converted_items, create_links(response, context)) end - def convert_single(item) + def convert_single(item, context) type = detect_type(item) - convert_type(item, type) + convert_type(item, type, context) end private - def convert_type(hash, type) - type.parse(hash) + def convert_type(hash, type, context) + type.parse(hash, context) end def detect_type(hash) @@ -55,8 +51,8 @@ def extract_type(type_str) entity_type end - def create_links(response) - ResponseLinks.parse(response.links_hash, @client) + def create_links(response, context) + ResponseLinks.parse(response.links_hash, context) end end diff --git a/lib/kosapi_client/response_links.rb b/lib/kosapi_client/response_links.rb index dc7a2c1..2711dcc 100644 --- a/lib/kosapi_client/response_links.rb +++ b/lib/kosapi_client/response_links.rb @@ -12,20 +12,18 @@ def initialize(prev_link, next_link) class << self - def parse(hash, client) - prev_link = parse_link(hash, 'prev', client) - next_link = parse_link(hash, 'next', client) + def parse(hash, context) + prev_link = parse_link(hash, 'prev', context) + next_link = parse_link(hash, 'next', context) new(prev_link, next_link) end private - def parse_link(hash, rel, client) + def parse_link(hash, rel, context) return nil unless hash link_hash = extract_link_hash(hash, rel) if link_hash - link = Entity::Link.parse(link_hash) - link.inject_client(client) - link + Entity::Link.parse(link_hash, context) end end diff --git a/spec/integration/course_events_spec.rb b/spec/integration/course_events_spec.rb index 54b7c96..7abb3ec 100644 --- a/spec/integration/course_events_spec.rb +++ b/spec/integration/course_events_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Course events resource', :vcr, :integration do +describe 'Course events resource', :integration do subject(:client) { create_kosapi_client } it 'returns course events' do diff --git a/spec/integration/courses_spec.rb b/spec/integration/courses_spec.rb index c6b1cc9..a260b21 100644 --- a/spec/integration/courses_spec.rb +++ b/spec/integration/courses_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Courses resource', :vcr, :integration do +describe 'Courses resource', :integration do let(:credentials) { { client_id: ENV['KOSAPI_OAUTH_CLIENT_ID'], client_secret: ENV['KOSAPI_OAUTH_CLIENT_SECRET'] } } subject(:client) { KOSapiClient.new(credentials) } diff --git a/spec/integration/exams_spec.rb b/spec/integration/exams_spec.rb index fbdb244..45469cd 100644 --- a/spec/integration/exams_spec.rb +++ b/spec/integration/exams_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Exams resource', :vcr, :integration do +describe 'Exams resource', :integration do subject(:client) { create_kosapi_client } diff --git a/spec/integration/parallels_spec.rb b/spec/integration/parallels_spec.rb index 35117e4..38341a3 100644 --- a/spec/integration/parallels_spec.rb +++ b/spec/integration/parallels_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Parallels resource', :vcr, :integration do +describe 'Parallels resource', :integration do let(:credentials) { { client_id: ENV['KOSAPI_OAUTH_CLIENT_ID'], client_secret: ENV['KOSAPI_OAUTH_CLIENT_SECRET'] } } subject(:client) { KOSapiClient.new(credentials) } diff --git a/spec/integration/semesters_spec.rb b/spec/integration/semesters_spec.rb new file mode 100644 index 0000000..d62204c --- /dev/null +++ b/spec/integration/semesters_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'Semesters resource', :integration do + + let(:credentials) { { client_id: ENV['KOSAPI_OAUTH_CLIENT_ID'], client_secret: ENV['KOSAPI_OAUTH_CLIENT_SECRET'] } } + subject(:client) { KOSapiClient.new(credentials) } + + it 'fetches semesters list' do + page = client.semesters.offset(0).limit(2) + expect(page.items.count).to eq 2 + end + + it 'parses entry ID properly' do + page = client.semesters + semester = page.items.first + expect(semester.id).not_to be_nil + end + + it 'parses updated properly' do + page = client.semesters + semester = page.items.first + expect(semester.updated).not_to be_nil + end + + it 'parses author properly' do + page = client.semesters + semester = page.items.first + expect(semester.author.name).not_to be_nil + end + + it 'parses entry link properly' do + page = client.semesters + semester = page.items.first + expect(semester.link).not_to be_nil + expect(semester.link.link_href).not_to be_nil + expect(semester.link.link_rel).not_to be_nil + end + + it 'fetches current semester' do + semester = client.semesters.current + expect(semester.start_date).not_to be_nil + expect(semester.end_date).not_to be_nil + + expect(Time.new).to be_between(semester.start_date, semester.end_date) + end +end diff --git a/spec/kosapi_client/entity/link_spec.rb b/spec/kosapi_client/entity/link_spec.rb index ab233c3..fb1f952 100644 --- a/spec/kosapi_client/entity/link_spec.rb +++ b/spec/kosapi_client/entity/link_spec.rb @@ -5,9 +5,8 @@ Link = KOSapiClient::Entity::Link let(:client) { instance_double(KOSapiClient::HTTPClient) } - subject(:link) { Link.parse({href: 'http://example.com/foo/bar/42', __content__: 'Example Site', rel: 'next'}) } + subject(:link) { Link.parse({href: 'http://example.com/foo/bar/42', __content__: 'Example Site', rel: 'next'}, {client: client}) } let(:result) { double(:result, foo: :bar) } - before(:example) { link.inject_client(client) } describe '.parse' do @@ -23,7 +22,7 @@ it 'encodes href URL' do href = 'parallels?query=(lastUpdatedDate%3E=2014-07-01T00:00:00;lastUpdatedDate%3C=2014-07-10T00:00:00)&offset=10&limit=10' - link = Link.new(nil, href, nil) + link = Link.new(nil, href, nil, nil) expect(link.link_href).to eq 'parallels?query=(lastUpdatedDate%3E=2014-07-01T00:00:00%3BlastUpdatedDate%3C=2014-07-10T00:00:00)&offset=10&limit=10' end @@ -45,6 +44,7 @@ end it 'calls http client with href' do + link.inject_client(client) expect(client).to receive(:send_request).with(:get, 'http://example.com/foo/bar/42') link.follow end diff --git a/spec/kosapi_client/entity/result_page_spec.rb b/spec/kosapi_client/entity/result_page_spec.rb index 25edd61..7f65d9a 100644 --- a/spec/kosapi_client/entity/result_page_spec.rb +++ b/spec/kosapi_client/entity/result_page_spec.rb @@ -7,9 +7,11 @@ subject(:result_page) { ResultPage.new([item], links) } let(:item) { double(:item) } let(:item2) { double(:second_item) } - let(:links) { instance_double(KOSapiClient::ResponseLinks, next: next_link) } - let(:next_page) { ResultPage.new([item2], instance_double(KOSapiClient::ResponseLinks, next: nil)) } - let(:next_link) { instance_double(KOSapiClient::Entity::Link, follow: next_page) } + + let(:next_page) { ResultPage.new([item2], KOSapiClient::ResponseLinks.new(nil, nil)) } + + let(:next_link) { instance_double(KOSapiClient::Entity::Link).instance_variable_set(:@target, next_page)} + let(:links) { KOSapiClient::ResponseLinks.new(nil, next_link) } describe '#each' do diff --git a/spec/kosapi_client/oauth2_http_adapter_spec.rb b/spec/kosapi_client/oauth2_http_adapter_spec.rb index 5686816..96287c7 100644 --- a/spec/kosapi_client/oauth2_http_adapter_spec.rb +++ b/spec/kosapi_client/oauth2_http_adapter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe KOSapiClient::OAuth2HttpAdapter, :vcr do +describe KOSapiClient::OAuth2HttpAdapter do KOSAPI_ROOT_URL = 'https://kosapi.fit.cvut.cz/api/3/' subject(:client) { KOSapiClient::OAuth2HttpAdapter.new(credentials, KOSAPI_ROOT_URL) } diff --git a/spec/kosapi_client/response_converter_spec.rb b/spec/kosapi_client/response_converter_spec.rb index 4280b80..6226a0a 100644 --- a/spec/kosapi_client/response_converter_spec.rb +++ b/spec/kosapi_client/response_converter_spec.rb @@ -3,31 +3,32 @@ describe KOSapiClient::ResponseConverter do let(:client) { instance_double(KOSapiClient::HTTPClient) } - subject(:converter) { described_class.new(client) } + let(:converter_context) { {client: client} } + subject(:converter) { described_class.new } describe '#convert' do context 'with paginated response' do - let(:next_link) { instance_double(KOSapiClient::Entity::Link) } let(:prev_link) { instance_double(KOSapiClient::Entity::Link) } - let(:links) { instance_double(KOSapiClient::ResponseLinks, next: next_link, prev: prev_link) } + let(:next_link) { instance_double(KOSapiClient::Entity::Link) } + let(:links) { KOSapiClient::ResponseLinks.new(prev_link, next_link) } let(:api_response) { double(is_paginated?: true, items: [{xsi_type: 'courseEvent', capacity: 70}, {xsi_type: 'courseEvent', capacity: 40}], links_hash: links) } before(:each) { allow(converter).to receive(:create_links).and_return(links) } it 'processes paginated response' do - result = converter.convert(api_response) + result = converter.convert(api_response, converter_context) expect(result).to be_an_instance_of(KOSapiClient::Entity::ResultPage) end it 'creates next link' do - result = converter.convert(api_response) + result = converter.convert(api_response, converter_context) expect(result.next).to be next_link end it 'creates prev link' do - result = converter.convert(api_response) + result = converter.convert(api_response, converter_context) expect(result.prev).to be prev_link end end @@ -37,7 +38,7 @@ let(:api_response) { double(is_paginated?: false, item: {xsi_type: 'courseEvent', capacity: 70}) } it 'processes non-paginated response' do - result = converter.convert(api_response) + result = converter.convert(api_response, converter_context) expect(result).to be_an_instance_of(KOSapiClient::Entity::CourseEvent) end @@ -48,7 +49,7 @@ let(:api_response) { double(is_paginated?: false, item: {xsi_type: 'unknownType'}) } it 'raises error when type not found' do - expect { converter.convert(api_response) }.to raise_error(RuntimeError) + expect { converter.convert(api_response, converter_context) }.to raise_error(RuntimeError) end end diff --git a/spec/kosapi_client/response_links_spec.rb b/spec/kosapi_client/response_links_spec.rb index 4356bf8..9f80828 100644 --- a/spec/kosapi_client/response_links_spec.rb +++ b/spec/kosapi_client/response_links_spec.rb @@ -9,7 +9,7 @@ context 'with no links' do it 'returns instance with no links set' do - links = described_class.parse(nil, client) + links = described_class.parse(nil, client: client) expect(links.next).not_to be expect(links.prev).not_to be end @@ -17,7 +17,7 @@ context 'with both links' do it 'parses both links' do - links = described_class.parse([{rel: 'prev', href: 'courses/?offset=0&limit=10'}, {rel: 'next', href: 'courses/?offset=20&limit=10'}], client) + links = described_class.parse([{rel: 'prev', href: 'courses/?offset=0&limit=10'}, {rel: 'next', href: 'courses/?offset=20&limit=10'}], client: client) expect(links.next.link_href).to eq 'courses/?offset=20&limit=10' expect(links.prev.link_href).to eq 'courses/?offset=0&limit=10' end @@ -25,7 +25,7 @@ context 'with next link' do it 'parses next link' do - links = described_class.parse({rel: 'next', href: 'courses/?offset=20&limit=10'}, client) + links = described_class.parse({rel: 'next', href: 'courses/?offset=20&limit=10'}, client: client) expect(links.next.link_href).to eq 'courses/?offset=20&limit=10' expect(links.next.link_rel).to eq 'next' expect(links.prev).not_to be @@ -34,7 +34,7 @@ context 'with prev link' do it 'parses prev link' do - links = described_class.parse({rel: 'prev', href: 'courses/?offset=20&limit=10'}, client) + links = described_class.parse({rel: 'prev', href: 'courses/?offset=20&limit=10'}, client: client) expect(links.next).not_to be expect(links.prev.link_href).to eq 'courses/?offset=20&limit=10' expect(links.prev.link_rel).to eq 'prev' @@ -42,7 +42,7 @@ end it 'injects http client' do - links = described_class.parse({rel: 'next', href: 'courses/?offset=20&limit=10'}, client) + links = described_class.parse({rel: 'next', href: 'courses/?offset=20&limit=10'}, client: client) expect(client).to receive(:send_request) links.next.follow end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index faa4756..765ccf0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,20 +21,3 @@ config.extend Helpers config.include ClientHelpers end - -require 'vcr' - -VCR.configure do |c| - c.configure_rspec_metadata! - c.hook_into :faraday - c.default_cassette_options = { - #serialize_with: :json, - # TODO: Track down UTF-8 issue and remove - #preserve_exact_body_bytes: true, - #decode_compressed_response: true, - record: ENV['TRAVIS'] ? :none : :once - } - c.cassette_library_dir = 'spec/cassettes' -end - -VCR.turn_off! ignore_cassettes: true if ENV['TRAVIS']