From e9d1a26c379b43ca90db4e915c1190a826f199a3 Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 13:03:24 -0600 Subject: [PATCH 1/7] add SirenClient::Action shared example --- lib/siren_client/action.rb | 2 +- spec/helper/spec_helper.rb | 1 + .../siren_client_action_spec.rb | 109 ++++++++++++ spec/unit/action_spec.rb | 161 ++++++------------ 4 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 spec/support/shared_examples/siren_client_action_spec.rb diff --git a/lib/siren_client/action.rb b/lib/siren_client/action.rb index a0b7871..e400454 100644 --- a/lib/siren_client/action.rb +++ b/lib/siren_client/action.rb @@ -10,7 +10,7 @@ def initialize(data, config={}) if data.class != Hash raise ArgumentError, "You must pass in a Hash to SirenClient::Action.new" end - @payload = data + @payload = data.deep_stringify_keys @config = { format: :json }.merge config @name = @payload['name'] || '' diff --git a/spec/helper/spec_helper.rb b/spec/helper/spec_helper.rb index 5dc95cd..04cb36b 100644 --- a/spec/helper/spec_helper.rb +++ b/spec/helper/spec_helper.rb @@ -2,6 +2,7 @@ require 'siren_client' require 'coveralls' Coveralls.wear! +Dir["./spec/support/shared_examples/*.rb"].sort.each {|f| require f} RSpec.configure do |config| # Ensure we only use `expect` and not `should`. diff --git a/spec/support/shared_examples/siren_client_action_spec.rb b/spec/support/shared_examples/siren_client_action_spec.rb new file mode 100644 index 0000000..a5c2b9a --- /dev/null +++ b/spec/support/shared_examples/siren_client_action_spec.rb @@ -0,0 +1,109 @@ +shared_examples 'a SirenClient::Action' do + describe '.config' do + it 'is a hash' do + expect(action.config).to be_a Hash + end + it 'can access a property of the config' do + expect(action.config[:headers]['Accept']).to eq('application/json') + end + end + + describe '.payload' do + it 'is a hash' do + expect(action.payload).to be_a Hash + end + it 'is NOT overwritten with SirenClient::Field classes' do + expect(action.payload['fields'][0]).to be_a Hash + end + end + + describe '.name' do + it 'is a string' do + expect(action.name).to be_a String + end + end + + describe '.classes' do + it 'is an array' do + expect(action.classes).to be_a Array + end + end + + describe '.method' do + it 'is a string' do + expect(action.method).to be_a String + end + it 'defaults to GET' do + expect(subject.new({ }).method).to eq('get') + end + end + + describe '.href' do + it 'is a string' do + expect(action.href).to be_a String + end + it 'can change .href as needed' do + action.href = action.href + '?query=test' + expect(/query=test/).to match(action.href) + end + end + + describe '.title' do + it 'is a string' do + expect(action.title).to be_a String + end + end + + describe '.type' do + it 'is a string' do + expect(action.type).to be_a String + end + it 'defaults to \'application/x-www-form-urlencoded\'' do + expect(action.type).to eq('application/x-www-form-urlencoded') + end + end + + describe '.fields' do + it 'is an array' do + expect(action.fields).to be_a Array + end + it 'is an array of SirenClient::Field\'s' do + action.fields.each do |field| + expect(field).to be_a SirenClient::Field + end + end + end + + describe '.where(params)' do + it 'executes the GET action' do + # I'm expecting an error here, all I want to see is that the url it being traversed. + expect { action.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError + end + + it 'GET action does not send the Content-Type header' do + expect { action.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError + expect(output_buffer.string).not_to include('Content-Type: application/x-www-form-urlencoded') + end + + it 'executes the POST action' do + # I'm expecting an error here, all I want to see is that the url it being traversed. + expect { action_post.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError + end + + it 'POST action does send the Content-Type header' do + expect { action_post.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError + expect(output_buffer.string).to include('Content-Type: application/x-www-form-urlencoded') + end + + it 'executes the DELETE action' do + expect { action_delete.submit }.to raise_error SirenClient::InvalidResponseError + end + # The rest will be tested in the live specs. + end + + describe '.submit' do + it 'executes the action without any parameters' do + expect { action.submit }.to raise_error SirenClient::InvalidResponseError + end + end +end diff --git a/spec/unit/action_spec.rb b/spec/unit/action_spec.rb index b84c2fa..3113386 100644 --- a/spec/unit/action_spec.rb +++ b/spec/unit/action_spec.rb @@ -5,125 +5,62 @@ let (:action_data_post) { {"name"=>"search","method"=>"POST","href"=>"http://example.com/products","fields"=>[{"name"=>"search","type"=>"text"}]} } let (:action_data_delete) { {"name"=>"delete","method"=>"DELETE","href"=>"http://example.com/products"} } + subject { SirenClient::Action } + describe '.new(data)' do - it 'raise an error if wrong type is provided' do - expect { SirenClient::Action.new([]) }.to raise_error(ArgumentError) - end - it 'can be instanciated with a hash' do - expect(SirenClient::Action.new(action_data)).to be_a SirenClient::Action - end + it 'raise an error if wrong type is provided' do + expect { subject.new([]) }.to raise_error(ArgumentError) + end + it 'can be instantiated with a hash' do + expect(subject.new(action_data)).to be_a SirenClient::Action + end end let (:output_buffer) { StringIO.new } - let (:action) { - SirenClient::Action.new(action_data, { - headers: { "Accept" => "application/json" }, - debug_output: output_buffer - }) - } - let (:action_post) { - SirenClient::Action.new(action_data_post, { - debug_output: output_buffer - }) - } - let (:action_delete) { - SirenClient::Action.new(action_data_delete, { - debug_output: output_buffer - }) - } - describe '.config' do - it 'is a hash' do - expect(action.config).to be_a Hash - end - it 'can access a property of the config' do - expect(action.config[:headers]['Accept']).to eq('application/json') - end - end - describe '.payload' do - it 'is a hash' do - expect(action.payload).to be_a Hash - end - it 'is NOT overwritten with SirenClient::Field classes' do - expect(action.payload['fields'][0]).to be_a Hash - end - end - describe '.name' do - it 'is a string' do - expect(action.name).to be_a String - end - end - describe '.classes' do - it 'is an array' do - expect(action.classes).to be_a Array - end - end - describe '.method' do - it 'is a string' do - expect(action.method).to be_a String - end - it 'defaults to GET' do - expect(SirenClient::Action.new({ }).method).to eq('get') - end - end - describe '.href' do - it 'is a string' do - expect(action.href).to be_a String - end - it 'can change .href as needed' do - action.href = action.href + '?query=test' - expect(/query=test/).to match(action.href) - end - end - describe '.title' do - it 'is a string' do - expect(action.title).to be_a String - end - end - describe '.type' do - it 'is a string' do - expect(action.type).to be_a String - end - it 'defaults to \'application/x-www-form-urlencoded\'' do - expect(action.type).to eq('application/x-www-form-urlencoded') - end - end - describe '.fields' do - it 'is an array' do - expect(action.fields).to be_a Array + + describe 'a SirenClient::Action instance' do + context 'when initialized with stringified keys' do + let (:action) { + subject.new(action_data, { + headers: { "Accept" => "application/json" }, + debug_output: output_buffer + }) + } + let (:action_post) { + subject.new(action_data_post, { + debug_output: output_buffer + }) + } + let (:action_delete) { + subject.new(action_data_delete, { + debug_output: output_buffer + }) + } + + it_behaves_like 'a SirenClient::Action' end - it 'is an array of SirenClient::Field\'s' do - action.fields.each do |field| - expect(field).to be_a SirenClient::Field + + context 'when initialized with symbolized keys' do + let (:action) { + subject.new(action_data.deep_symbolize_keys, { + headers: { "Accept" => "application/json" }, + debug_output: output_buffer + }) + } + let (:action_post) { + subject.new(action_data_post.deep_symbolize_keys, { + debug_output: output_buffer + }) + } + let (:action_delete) { + subject.new(action_data_delete.deep_symbolize_keys, { + debug_output: output_buffer + }) + } + + it_behaves_like 'a SirenClient::Action' end end end - describe '.where(params)' do - it 'executes the GET action' do - # I'm expecting an error here, all I want to see is that the url it being traversed. - expect { action.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError - end - it 'GET action does not send the Content-Type header' do - expect { action.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError - expect(output_buffer.string).not_to include('Content-Type: application/x-www-form-urlencoded') - end - it 'executes the POST action' do - # I'm expecting an error here, all I want to see is that the url it being traversed. - expect { action_post.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError - end - it 'POST action does send the Content-Type header' do - expect { action_post.where(test: 'hi') }.to raise_error SirenClient::InvalidResponseError - expect(output_buffer.string).to include('Content-Type: application/x-www-form-urlencoded') - end - it 'executes the DELETE action' do - expect { action_delete.submit }.to raise_error SirenClient::InvalidResponseError - end - # The rest will be tested in the live specs. - end - describe '.submit' do - it 'executes the action without any parameters' do - expect { action.submit }.to raise_error SirenClient::InvalidResponseError - end - end -end From 960cdb3ed57b78e36267f48bbe392316010f10db Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 13:24:52 -0600 Subject: [PATCH 2/7] add shared example for SirenClient::Entity --- lib/siren_client/entity.rb | 2 +- .../siren_client_entity_spec.rb | 393 ++++++++++++++++++ spec/unit/entity_spec.rb | 369 ++-------------- 3 files changed, 424 insertions(+), 340 deletions(-) create mode 100644 spec/support/shared_examples/siren_client_entity_spec.rb diff --git a/lib/siren_client/entity.rb b/lib/siren_client/entity.rb index 1d622ab..baf914a 100644 --- a/lib/siren_client/entity.rb +++ b/lib/siren_client/entity.rb @@ -27,7 +27,7 @@ def initialize(data, config={}) raise InvalidResponseError, e.message end elsif data.class == Hash - @payload = data + @payload = data.deep_stringify_keys else raise ArgumentError, "You must pass in either a url(String) or an entity(Hash) to SirenClient::Entity.new" end diff --git a/spec/support/shared_examples/siren_client_entity_spec.rb b/spec/support/shared_examples/siren_client_entity_spec.rb new file mode 100644 index 0000000..cd8313a --- /dev/null +++ b/spec/support/shared_examples/siren_client_entity_spec.rb @@ -0,0 +1,393 @@ +shared_examples 'a SirenClient::Entity' do + describe '.config' do + it 'is a hash' do + expect(entity.config).to be_a Hash + end + + it 'can access property in the config' do + expect(entity.config[:headers]['Accept']).to eq('application/json') + end + end + + describe '.payload' do + it 'is a hash' do + expect(entity.payload).to be_a Hash + end + + it 'is NOT overwritten with SirenClient classes' do + expect(entity.payload['entities'][0]).to be_a Hash + expect(entity.payload['actions'][0]).to be_a Hash + expect(entity.payload['links'][0]).to be_a Hash + end + end + + describe '.classes' do + it 'is an array' do + expect(entity.classes).to be_a Array + end + end + + describe '.properties' do + it 'is a hash' do + expect(entity.properties).to be_a Hash + end + + it 'can access a property' do + expect(entity.properties['page']).to eq(1) + end + + it 'can access a property directly on the entity' do + expect(entity.page).to eq(1) + end + end + + describe '.entities' do + it 'is an array' do + expect(entity.entities).to be_a Array + end + + it 'is an array of SirenClient::Entity\'s' do + entity.entities.each do |ent| + expect(ent).to be_a SirenClient::Entity + end + end + end + + describe '.rels' do + it 'is an array' do + expect(entity.rels).to be_a Array + end + end + + describe '.links' do + it 'is a hash' do + expect(entity.links).to be_a Hash + end + + it 'is a hash of { key => SirenClient::Link }\'s' do + expect { + entity.links.each do |key, link| + expect(key).to be_a String + expect(link).to be_a SirenClient::Link + end + }.to_not raise_error + end + + it 'can access a link' do + expect(entity.links['self']).to be_a SirenClient::Link + end + end + + describe '.actions' do + it 'is a hash' do + expect(entity.actions).to be_a Hash + end + + it 'is a hash of { key => SirenClient::Action }\'s' do + expect { + entity.actions.each do |name, action| + expect(name).to be_a String + expect(action).to be_a SirenClient::Action + end + }.to_not raise_error + end + + it 'can access an action' do + expect(entity.actions['filter_concepts']).to be_a SirenClient::Action + end + end + + # This will be empty unless it's an entity sub-link. + describe '.href' do + it 'is a string' do + expect(entity.href).to be_a String + end + + it 'should be empty' do + expect(entity.href).to eq('') + end + + it 'can change .href as needed' do + entity.href = 'http://example.com?query=test' + expect(/query=test/).to match(entity.href) + end + end + + # Similar to SirenClient::Link.go this function will create a + # new entity from the .href method. For entity sub-links only. + describe '.go' do + let (:graph) { entity[0] } + it 'return nil if it\'s NOT an entity sub-link' do + expect(entity.go).to eq(nil) + end + + it 'initiate a request if it IS an entity sub-link' do + expect { graph.entities[0].go }.to raise_error SystemCallError + end + end + + describe '.invalidkey' do + it 'will throw a NoMethodError' do + expect { entity.thisdoesntexist }.to raise_error(NoMethodError) + end + + it 'prints .invalidkey used to NoMethodError message' do + begin + entity.thisdoesntexist + rescue NoMethodError => e + expect( e.to_s ).to match(/thisdoesntexist/) + end + end + end + + describe '.validkey' do + let (:graph) { entity[0] } + it 'can access an entity sub-link within the entity' do + expect { graph.messages }.to raise_error SystemCallError + end + + it 'can access a link directly on the entity' do + expect { entity.next }.to raise_error SystemCallError + end + + it 'can access an action directly on the entity' do + expect(entity.filter_concepts).to be_a SirenClient::Action + end + end + + describe '.title' do + it 'is a string' do + expect(entity.title).to be_a String + end + end + + describe '.type' do + it 'is a string' do + expect(entity.type).to be_a String + end + end + + describe '.length' do + it 'can return the size of @entities' do + expect(entity.length).to eq(1) + end + end + + describe '[x]' do + it 'can get the first element' do + expect(entity[0]).to be_a SirenClient::Entity + end + + it 'can get the last element' do + expect(entity[-1]).to be_a SirenClient::Entity + end + + it 'causes entity sub-links to be traversed' do + expect(entity[0]).to be_a SirenClient::Entity + end + end + + describe '.search("messages")' do + it 'returns an Array' do + expect(graph.search('messages')).to be_a Array + end + + it 'returns an Array of the right size' do + expect(graph.search('messages').length).to eq(1) + end + + it 'the first element is a SirenClient::Entity' do + expect(graph.search('messages')[0]).to be_a SirenClient::Entity + end + + it 'the first element\'s classes include "messages"' do + expect(graph.search('messages')[0].classes.include?('messages')).to eq(true) + end + end + + describe '.string(/test1/)' do + it 'returns an Array' do + expect(graph.search(/test1/)).to be_a Array + end + + it 'returns an Array of the right size' do + expect(graph.search(/test1/).length).to eq(1) + end + + it 'the second element is a SirenClient::Entity' do + expect(graph.search(/test1/)[0]).to be_a SirenClient::Entity + end + + it 'the second element\'s classes include "concepts"' do + expect(graph.search(/test1/)[0].classes.include?('messages')).to eq(true) + end + end + + describe '.string(/test[0-9]/)' do + it 'returns an Array' do + expect(graph.search(/test[0-9]/)).to be_a Array + end + + it 'returns an Array of the right size' do + expect(graph.search(/test[0-9]/).length).to eq(2) + end + + it 'the second element is a SirenClient::Entity' do + expect(graph.search(/test[0-9]/)[1]).to be_a SirenClient::Entity + end + + it 'the second element\'s classes include "concepts"' do + expect(graph.search(/test[0-9]/)[1].classes.include?('concepts')).to eq(true) + end + end + + describe 'underscore support' do + it 'can access entity sub-links' do + # Since this will trigger the sub-link. We expect an error + expect { graph.user_preferences }.to raise_error SystemCallError + end + + it 'can access actions' do + expect(entity.filter_messages).to be_a SirenClient::Action + end + + it 'can access links' do + # Since this will trigger the link. We expect an error + expect { entity.prev_page }.to raise_error SystemCallError + end + end + + describe '.with_raw_response' do + it 'returns the entity' do + expect(entity.with_raw_response).to eq(entity) + end + + it 'sets the entity to return a raw response for the `next` request' do + entity.with_raw_response + expect(entity.next_response_is_raw?).to eq(true) + end + + it 'should still allow a network request to be made' do + entity.with_raw_response + expect { entity.with_raw_response.prev_page }.to raise_error SystemCallError + end + end + + # Entities enumerable support + describe "enumerable support" do + describe '.each' do + it 'can iterate over all the entities' do + expect { + graph.each do |ent| + expect(ent).to be_a SirenClient::Entity + end + }.to_not raise_error + end + end + + # Useful enumerable methods + describe '.all?' do + it 'matches .entities.all?' do + expect( + graph.all? do |ent| + ent == SirenClient::Entity + end + ).to eq( + graph.entities.all? do |ent| + ent == SirenClient::Entity + end + ) + end + end + + describe '.find' do + it 'matches .entities.find' do + expect( + graph.find do |ent| + ent.rels.include?('/rels/messages') + end + ).to eq( + graph.entities.find do |ent| + ent.rels.include?('/rels/messages') + end + ) + end + end + + describe '.find_all' do + it 'matches .entities.find_all' do + expect( + graph.find_all do |ent| + ent.classes.include?('collection') + end + ).to eq( + graph.entities.find_all do |ent| + ent.classes.include?('collection') + end + ) + end + end + + describe '.first' do + it 'matches .entities.first' do + expect(graph.first).to eq(graph.entities.first) + end + end + + describe '.map' do + it 'matches .entities.map' do + expect( + graph.map do |ent| + ent.classes.include?('concepts') + end + ).to eq( + graph.entities.map do |ent| + ent.classes.include?('concepts') + end + ) + end + end + + describe '.reject' do + it 'matches .entities.reject' do + expect( + graph.reject do |ent| + ent.classes.include?('messages') + end + ).to eq( + graph.entities.reject do |ent| + ent.classes.include?('messages') + end + ) + end + end + + describe '.select' do + it 'matches .entities.select' do + expect( + graph.select do |ent| + ent.rels.include?('/rels/concepts') + end + ).to eq( + graph.entities.select do |ent| + ent.rels.include?('/rels/concepts') + end + ) + end + end + + describe '.sort' do + it 'matches .entities.sort' do + expect( + graph.sort do |ent_a, ent_b| + ent_b.classes[0] <=> ent_a.classes[0] + end + ).to eq( + graph.entities.sort do |ent_a, ent_b| + ent_b.classes[0] <=> ent_a.classes[0] + end + ) + end + end + end +end diff --git a/spec/unit/entity_spec.rb b/spec/unit/entity_spec.rb index 5954895..4b703c4 100644 --- a/spec/unit/entity_spec.rb +++ b/spec/unit/entity_spec.rb @@ -1,361 +1,52 @@ require 'helper/spec_helper' describe SirenClient::Entity do + subject { SirenClient::Entity } describe '.new(data)' do it 'raise an error if no param is provided' do - expect { SirenClient::Entity.new }.to raise_error(ArgumentError) + expect { subject.new }.to raise_error(ArgumentError) end + it 'raise an error if an invalid type is provided' do - expect { SirenClient::Entity.new([]) }.to raise_error(ArgumentError) + expect { subject.new([]) }.to raise_error(ArgumentError) end + it 'raise an error if an invalid url is provided' do - expect { SirenClient::Entity.new('error me') }.to raise_error(SirenClient::InvalidURIError) + expect { subject.new('error me') }.to raise_error(SirenClient::InvalidURIError) end + it 'raise an error if the url does not return json' do - expect { SirenClient::Entity.new('http://www.google.com') }.to raise_error(SirenClient::InvalidResponseError) + expect { subject.new('http://www.google.com') }.to raise_error(SirenClient::InvalidResponseError) end + it 'can be instanciated with a hash of data' do - expect(SirenClient::Entity.new(siren_body)).to be_a SirenClient::Entity + expect(subject.new(siren_body)).to be_a SirenClient::Entity end end - let (:entity) { - SirenClient::Entity.new(siren_body, { - headers: { "Accept" => "application/json" } - }) - } - describe '.config' do - it 'is a hash' do - expect(entity.config).to be_a Hash - end - it 'can access property in the config' do - expect(entity.config[:headers]['Accept']).to eq('application/json') - end - end - describe '.payload' do - it 'is a hash' do - expect(entity.payload).to be_a Hash - end - it 'is NOT overwritten with SirenClient classes' do - expect(entity.payload['entities'][0]).to be_a Hash - expect(entity.payload['actions'][0]).to be_a Hash - expect(entity.payload['links'][0]).to be_a Hash - end - end - describe '.classes' do - it 'is an array' do - expect(entity.classes).to be_a Array - end - end - describe '.properties' do - it 'is a hash' do - expect(entity.properties).to be_a Hash - end - it 'can access a property' do - expect(entity.properties['page']).to eq(1) - end - it 'can access a property directly on the entity' do - expect(entity.page).to eq(1) - end - end - describe '.entities' do - it 'is an array' do - expect(entity.entities).to be_a Array - end - it 'is an array of SirenClient::Entity\'s' do - entity.entities.each do |ent| - expect(ent).to be_a SirenClient::Entity - end - end - end - describe '.rels' do - it 'is an array' do - expect(entity.rels).to be_a Array - end - end - describe '.links' do - it 'is a hash' do - expect(entity.links).to be_a Hash - end - it 'is a hash of { key => SirenClient::Link }\'s' do - expect { - entity.links.each do |key, link| - expect(key).to be_a String - expect(link).to be_a SirenClient::Link - end - }.to_not raise_error - end - it 'can access a link' do - expect(entity.links['self']).to be_a SirenClient::Link - end - end - describe '.actions' do - it 'is a hash' do - expect(entity.actions).to be_a Hash - end - it 'is a hash of { key => SirenClient::Action }\'s' do - expect { - entity.actions.each do |name, action| - expect(name).to be_a String - expect(action).to be_a SirenClient::Action - end - }.to_not raise_error - end - it 'can access an action' do - expect(entity.actions['filter_concepts']).to be_a SirenClient::Action - end - end - # This will be empty unless it's an entity sub-link. - describe '.href' do - it 'is a string' do - expect(entity.href).to be_a String - end - it 'should be empty' do - expect(entity.href).to eq('') - end - it 'can change .href as needed' do - entity.href = 'http://example.com?query=test' - expect(/query=test/).to match(entity.href) - end - end - # Similar to SirenClient::Link.go this function will create a - # new entity from the .href method. For entity sub-links only. - describe '.go' do + describe 'a SirenClient::Entity instance' do + let (:entity) { + subject.new(siren_body, { + headers: { "Accept" => "application/json" } + }) + } let (:graph) { entity[0] } - it 'return nil if it\'s NOT an entity sub-link' do - expect(entity.go).to eq(nil) - end - it 'initiate a request if it IS an entity sub-link' do - expect { graph.entities[0].go }.to raise_error SystemCallError - end - end - describe '.invalidkey' do - it 'will throw a NoMethodError' do - expect { entity.thisdoesntexist }.to raise_error(NoMethodError) - end - it 'prints .invalidkey used to NoMethodError message' do - begin - entity.thisdoesntexist - rescue NoMethodError => e - expect( e.to_s ).to match(/thisdoesntexist/) - end - end - end - describe '.validkey' do - let (:graph) { entity[0] } - it 'can access an entity sub-link within the entity' do - expect { graph.messages }.to raise_error SystemCallError - end - it 'can access a link directly on the entity' do - expect { entity.next }.to raise_error SystemCallError - end - it 'can access an action directly on the entity' do - expect(entity.filter_concepts).to be_a SirenClient::Action - end - end - describe '.title' do - it 'is a string' do - expect(entity.title).to be_a String - end - end - describe '.type' do - it 'is a string' do - expect(entity.type).to be_a String - end - end - describe '.length' do - it 'can return the size of @entities' do - expect(entity.length).to eq(1) - end - end - describe '[x]' do - it 'can get the first element' do - expect(entity[0]).to be_a SirenClient::Entity - end - it 'can get the last element' do - expect(entity[-1]).to be_a SirenClient::Entity - end - it 'causes entity sub-links to be traversed' do - expect(entity[0]).to be_a SirenClient::Entity - end - end - let (:graph) { entity[0] } - describe '.search("messages")' do - it 'returns an Array' do - expect(graph.search('messages')).to be_a Array - end - it 'returns an Array of the right size' do - expect(graph.search('messages').length).to eq(1) - end - it 'the first element is a SirenClient::Entity' do - expect(graph.search('messages')[0]).to be_a SirenClient::Entity - end - it 'the first element\'s classes include "messages"' do - expect(graph.search('messages')[0].classes.include?('messages')).to eq(true) - end - end - describe '.string(/test1/)' do - it 'returns an Array' do - expect(graph.search(/test1/)).to be_a Array - end - it 'returns an Array of the right size' do - expect(graph.search(/test1/).length).to eq(1) - end - it 'the second element is a SirenClient::Entity' do - expect(graph.search(/test1/)[0]).to be_a SirenClient::Entity - end - it 'the second element\'s classes include "concepts"' do - expect(graph.search(/test1/)[0].classes.include?('messages')).to eq(true) - end - end - describe '.string(/test[0-9]/)' do - it 'returns an Array' do - expect(graph.search(/test[0-9]/)).to be_a Array - end - it 'returns an Array of the right size' do - expect(graph.search(/test[0-9]/).length).to eq(2) - end - it 'the second element is a SirenClient::Entity' do - expect(graph.search(/test[0-9]/)[1]).to be_a SirenClient::Entity - end - it 'the second element\'s classes include "concepts"' do - expect(graph.search(/test[0-9]/)[1].classes.include?('concepts')).to eq(true) - end - end - describe 'underscore support' do - it 'can access entity sub-links' do - # Since this will trigger the sub-link. We expect an error - expect { graph.user_preferences }.to raise_error SystemCallError - end - it 'can access actions' do - expect(entity.filter_messages).to be_a SirenClient::Action - end - it 'can access links' do - # Since this will trigger the link. We expect an error - expect { entity.prev_page }.to raise_error SystemCallError - end - end - describe '.with_raw_response' do - it 'returns the entity' do - expect(entity.with_raw_response).to eq(entity) - end - it 'sets the entity to return a raw response for the `next` request' do - entity.with_raw_response - expect(entity.next_response_is_raw?).to eq(true) - end - it 'should still allow a network request to be made' do - entity.with_raw_response - expect { entity.with_raw_response.prev_page }.to raise_error SystemCallError - end - end - # Entities enumerable support - describe "enumerable support" do - describe '.each' do - it 'can iterate over all the entities' do - expect { - graph.each do |ent| - expect(ent).to be_a SirenClient::Entity - end - }.to_not raise_error - end - end - # Useful enumerable methods - describe '.all?' do - it 'matches .entities.all?' do - expect( - graph.all? do |ent| - ent == SirenClient::Entity - end - ).to eq( - graph.entities.all? do |ent| - ent == SirenClient::Entity - end - ) - end - end - describe '.find' do - it 'matches .entities.find' do - expect( - graph.find do |ent| - ent.rels.include?('/rels/messages') - end - ).to eq( - graph.entities.find do |ent| - ent.rels.include?('/rels/messages') - end - ) - end - end - describe '.find_all' do - it 'matches .entities.find_all' do - expect( - graph.find_all do |ent| - ent.classes.include?('collection') - end - ).to eq( - graph.entities.find_all do |ent| - ent.classes.include?('collection') - end - ) - end - end - describe '.first' do - it 'matches .entities.first' do - expect(graph.first).to eq(graph.entities.first) - end - end - describe '.map' do - it 'matches .entities.map' do - expect( - graph.map do |ent| - ent.classes.include?('concepts') - end - ).to eq( - graph.entities.map do |ent| - ent.classes.include?('concepts') - end - ) - end - end - describe '.reject' do - it 'matches .entities.reject' do - expect( - graph.reject do |ent| - ent.classes.include?('messages') - end - ).to eq( - graph.entities.reject do |ent| - ent.classes.include?('messages') - end - ) - end - end - describe '.select' do - it 'matches .entities.select' do - expect( - graph.select do |ent| - ent.rels.include?('/rels/concepts') - end - ).to eq( - graph.entities.select do |ent| - ent.rels.include?('/rels/concepts') - end - ) - end + + context 'when initialized with stringified keys' do + it_behaves_like 'a SirenClient::Entity' end - describe '.sort' do - it 'matches .entities.sort' do - expect( - graph.sort do |ent_a, ent_b| - ent_b.classes[0] <=> ent_a.classes[0] - end - ).to eq( - graph.entities.sort do |ent_a, ent_b| - ent_b.classes[0] <=> ent_a.classes[0] - end - ) - end + + context 'when initialized with symbolized keys' do + let (:entity) { + subject.new(siren_body.deep_symbolize_keys, { + headers: { "Accept" => "application/json" } + }) + } + let (:graph) { entity[0] } + + it_behaves_like 'a SirenClient::Entity' end end + end From 1e3b34b0676d89fbfebf69fc295056f460432bac Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 13:38:40 -0600 Subject: [PATCH 3/7] add shared_examples for SirenClient::Field --- lib/siren_client/field.rb | 2 +- .../siren_client_field_spec.rb | 33 ++++++++ spec/unit/field_spec.rb | 76 ++++++++++++------- 3 files changed, 82 insertions(+), 29 deletions(-) create mode 100644 spec/support/shared_examples/siren_client_field_spec.rb diff --git a/lib/siren_client/field.rb b/lib/siren_client/field.rb index 2eef9f2..5bb4488 100644 --- a/lib/siren_client/field.rb +++ b/lib/siren_client/field.rb @@ -6,7 +6,7 @@ def initialize(data) if data.class != Hash raise ArgumentError, "You must pass in a Hash to SirenClient::Action.new" end - @payload = data + @payload = data.deep_stringify_keys @name = @payload['name'] || '' @type = @payload['type'] || 'text' diff --git a/spec/support/shared_examples/siren_client_field_spec.rb b/spec/support/shared_examples/siren_client_field_spec.rb new file mode 100644 index 0000000..0f87dda --- /dev/null +++ b/spec/support/shared_examples/siren_client_field_spec.rb @@ -0,0 +1,33 @@ +shared_examples 'a SirenClient::Field' do + describe '.payload' do + it 'is a hash' do + expect(field.payload).to be_a Hash + end + end + + context 'when payload contains data' do + describe '.name' do + it 'is a string' do + expect(field.name).to eq field_data['name'] + end + end + + describe '.type' do + it 'is a string' do + expect(field.type).to eq field_data['type'] + end + end + + describe '.value' do + it 'is a string' do + expect(field.value).to eq field_data['value'] + end + end + + describe '.title' do + it 'is a string' do + expect(field.title).to eq field_data['title'] + end + end + end +end diff --git a/spec/unit/field_spec.rb b/spec/unit/field_spec.rb index 1d196de..2be3a2f 100644 --- a/spec/unit/field_spec.rb +++ b/spec/unit/field_spec.rb @@ -1,44 +1,64 @@ require 'helper/spec_helper' describe SirenClient::Field do - let (:field_data) { {"name"=>"search","type"=>"text"} } + let (:field_data) { {"name"=>"search","type"=>"text", "value" => "test", "title" => "test"} } + + subject { SirenClient::Field } describe '.new(data)' do - it 'raise an error if wrong type is provided' do - expect { SirenClient::Field.new([]) }.to raise_error(ArgumentError) - end - it 'can be instanciated with a hash' do - expect(SirenClient::Field.new(field_data)).to be_a SirenClient::Field - end - end + it 'raise an error if wrong type is provided' do + expect { subject.new([]) }.to raise_error(ArgumentError) + end - let (:field) { SirenClient::Field.new(field_data) } - describe '.payload' do - it 'is a hash' do - expect(field.payload).to be_a Hash + it 'can be instanciated with a hash' do + expect(subject.new(field_data)).to be_a SirenClient::Field end end - describe '.name' do - it 'is a string' do - expect(field.name).to be_a String + + describe 'a SirenClient::Field instance' do + context 'when initialized with stringified keys' do + let (:field) { subject.new(field_data) } + + it_behaves_like 'a SirenClient::Field' + end + + context 'when initialized with symbolized keys' do + let (:field) { subject.new(field_data.deep_symbolize_keys) } + + it_behaves_like 'a SirenClient::Field' end end - describe '.type' do - it 'is a string' do - expect(field.type).to be_a String + + context 'when payload contains no data' do + let(:field) { subject.new({}) } + + describe '.name' do + it 'is a string' do + expect(field.name).to be_a String + end end - it 'default to "text"' do - expect(SirenClient::Field.new({ }).type).to eq('text') + + describe '.type' do + it 'is a string' do + expect(field.type).to be_a String + end + + it 'default to "text"' do + expect(subject.new({ }).type).to eq('text') + end end - end - describe '.value' do - it 'is a string' do - expect(field.value).to be_a String + + describe '.value' do + it 'is a string' do + expect(field.value).to be_a String + end end - end - describe '.title' do - it 'is a string' do - expect(field.title).to be_a String + + describe '.title' do + it 'is a string' do + expect(field.title).to be_a String + end end end + end From 892ae1230f412dd5966b4bdddda3bce987255220 Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 14:02:14 -0600 Subject: [PATCH 4/7] add shared_example for SirenClient::Field --- lib/siren_client/link.rb | 2 +- .../shared_examples/siren_client_link_spec.rb | 45 ++++++++++ spec/unit/link_spec.rb | 83 ++++++++++++------- 3 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 spec/support/shared_examples/siren_client_link_spec.rb diff --git a/lib/siren_client/link.rb b/lib/siren_client/link.rb index e5a6377..01c2941 100644 --- a/lib/siren_client/link.rb +++ b/lib/siren_client/link.rb @@ -10,7 +10,7 @@ def initialize(data, config={}) if data.class != Hash raise ArgumentError, "You must pass in a Hash to SirenClient::Link.new" end - @payload = data + @payload = data.deep_stringify_keys @config = { format: :json }.merge config @rels = @payload['rel'] || [] diff --git a/spec/support/shared_examples/siren_client_link_spec.rb b/spec/support/shared_examples/siren_client_link_spec.rb new file mode 100644 index 0000000..f2eef18 --- /dev/null +++ b/spec/support/shared_examples/siren_client_link_spec.rb @@ -0,0 +1,45 @@ +shared_examples 'a SirenClient::Link' do + describe '.go' do + it 'follows the link\'s href' do + # We just need to know that the link will make the request. + expect { link.go }.to raise_error(SirenClient::InvalidResponseError) + end + end + + describe 'when initialized with data' do + describe '.payload' do + it 'is a hash' do + expect(link.payload).to be_a Hash + end + end + + describe '.rels' do + it 'is an array' do + expect(link.rels).to match_array link_data['rel'] + end + end + + describe '.href' do + it 'is a string' do + expect(link.href).to eq link_data['href'] + end + + it 'can change .href as needed' do + link.href = link.href + '?query=test' + expect(/query=test/).to match(link.href) + end + end + + describe '.title' do + it 'is a string' do + expect(link.title).to eq link_data['title'] + end + end + + describe '.type' do + it 'is a string' do + expect(link.type).to eq link_data['type'] + end + end + end +end diff --git a/spec/unit/link_spec.rb b/spec/unit/link_spec.rb index b23d5ea..511f35c 100644 --- a/spec/unit/link_spec.rb +++ b/spec/unit/link_spec.rb @@ -1,51 +1,70 @@ require 'helper/spec_helper' describe SirenClient::Link do - let (:link_data) { {"rel"=>["self"],"href"=>"http://example.com/products/03283378000P"} } + let (:link_data) { {"rel"=>["self"],"href"=>"http://example.com/products/03283378000P", "title"=>"test", "type"=>"test"} } + + subject { SirenClient::Link } describe '.new(data)' do - it 'raise an error if wrong type is provided' do - expect { SirenClient::Link.new([]) }.to raise_error(ArgumentError) - end - it 'can be instanciated with a hash' do - expect(SirenClient::Link.new(link_data)).to be_a SirenClient::Link - end - end + it 'raise an error if wrong type is provided' do + expect { subject.new([]) }.to raise_error(ArgumentError) + end - let (:link) { SirenClient::Link.new(link_data) } - describe '.go' do - it 'follows the link\'s href' do - # We just need to know that the link will make the request. - expect { link.go }.to raise_error(SirenClient::InvalidResponseError) + it 'can be instanciated with a hash' do + expect(subject.new(link_data)).to be_a SirenClient::Link end end - describe '.payload' do - it 'is a hash' do - expect(link.payload).to be_a Hash + + describe 'a SirenClient::Link instance' do + context 'when initialized with stringified keys' do + let (:link) { subject.new(link_data) } + + it_behaves_like 'a SirenClient::Link' end - end - describe '.rels' do - it 'is an array' do - expect(link.rels).to be_a Array + + context 'when initialized with symbolized keys' do + let (:link) { subject.new(link_data.deep_symbolize_keys) } + + it_behaves_like 'a SirenClient::Link' end end - describe '.href' do - it 'is a string' do - expect(link.href).to be_a String + + describe 'when initialized with no data' do + let (:link) { subject.new({}) } + + describe '.payload' do + it 'is a hash' do + expect(link.payload).to be_a Hash + end end - it 'can change .href as needed' do + + describe '.rels' do + it 'is an array' do + expect(link.rels).to be_a Array + end + end + + describe '.href' do + it 'is a string' do + expect(link.href).to be_a String + end + + it 'can change .href as needed' do link.href = link.href + '?query=test' expect(/query=test/).to match(link.href) + end end - end - describe '.title' do - it 'is a string' do - expect(link.title).to be_a String + + describe '.title' do + it 'is a string' do + expect(link.title).to be_a String + end end - end - describe '.type' do - it 'is a string' do - expect(link.type).to be_a String + + describe '.type' do + it 'is a string' do + expect(link.type).to be_a String + end end end end From c1f9e8e93e7c3568d96550a46a3e026d4cc97593 Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 14:31:04 -0600 Subject: [PATCH 5/7] add specs to cover default values and check for set values --- .../siren_client_action_spec.rb | 97 +++++++++---------- spec/unit/action_spec.rb | 77 ++++++++++++++- 2 files changed, 124 insertions(+), 50 deletions(-) diff --git a/spec/support/shared_examples/siren_client_action_spec.rb b/spec/support/shared_examples/siren_client_action_spec.rb index a5c2b9a..2f914d0 100644 --- a/spec/support/shared_examples/siren_client_action_spec.rb +++ b/spec/support/shared_examples/siren_client_action_spec.rb @@ -8,72 +8,71 @@ end end - describe '.payload' do - it 'is a hash' do - expect(action.payload).to be_a Hash - end - it 'is NOT overwritten with SirenClient::Field classes' do - expect(action.payload['fields'][0]).to be_a Hash + describe 'when initialized with data' do + describe '.payload' do + it 'is a hash' do + expect(action.payload).to be_a Hash + end + it 'is NOT overwritten with SirenClient::Field classes' do + expect(action.payload['fields'][0]).to be_a Hash + end end - end - describe '.name' do - it 'is a string' do - expect(action.name).to be_a String + describe '.name' do + it 'is a string' do + expect(action.name).to eq action_data['name'] + end end - end - describe '.classes' do - it 'is an array' do - expect(action.classes).to be_a Array + describe '.classes' do + it 'is an array' do + expect(action.classes).to match_array action_data['class'] + end end - end - describe '.method' do - it 'is a string' do - expect(action.method).to be_a String - end - it 'defaults to GET' do - expect(subject.new({ }).method).to eq('get') + describe '.method' do + it 'is a string' do + expect(action.method).to eq 'get' + end end - end - describe '.href' do - it 'is a string' do - expect(action.href).to be_a String - end - it 'can change .href as needed' do - action.href = action.href + '?query=test' - expect(/query=test/).to match(action.href) - end - end + describe '.href' do + it 'is a string' do + expect(action.href).to eq action_data['href'] + end - describe '.title' do - it 'is a string' do - expect(action.title).to be_a String + it 'can change .href as needed' do + action.href = action.href + '?query=test' + expect(/query=test/).to match(action.href) + end end - end - describe '.type' do - it 'is a string' do - expect(action.type).to be_a String - end - it 'defaults to \'application/x-www-form-urlencoded\'' do - expect(action.type).to eq('application/x-www-form-urlencoded') + describe '.title' do + it 'is a string' do + expect(action.title).to eq action_data['title'] + end end - end - describe '.fields' do - it 'is an array' do - expect(action.fields).to be_a Array + describe '.type' do + it 'is a string' do + expect(action.type).to eq action_data['type'] + end end - it 'is an array of SirenClient::Field\'s' do - action.fields.each do |field| - expect(field).to be_a SirenClient::Field + + describe '.fields' do + it 'is an array' do + expect(action.fields).to be_a Array + end + + it 'is an array of SirenClient::Field\'s' do + action.fields.each do |field| + expect(field).to be_a SirenClient::Field + end end end - end + + end describe '.where(params)' do it 'executes the GET action' do # I'm expecting an error here, all I want to see is that the url it being traversed. diff --git a/spec/unit/action_spec.rb b/spec/unit/action_spec.rb index 3113386..4bb3e5a 100644 --- a/spec/unit/action_spec.rb +++ b/spec/unit/action_spec.rb @@ -1,7 +1,7 @@ require 'helper/spec_helper' describe SirenClient::Action do - let (:action_data) { {"name"=>"search","method"=>"GET","href"=>"http://example.com/products","fields"=>[{"name"=>"search","type"=>"text"}]} } + let (:action_data) { {"name"=>"search","method"=>"GET","href"=>"http://example.com/products", "class"=>["test"], "title"=>"test", "type"=>"text/html", "fields"=>[{"name"=>"search"}]} } let (:action_data_post) { {"name"=>"search","method"=>"POST","href"=>"http://example.com/products","fields"=>[{"name"=>"search","type"=>"text"}]} } let (:action_data_delete) { {"name"=>"delete","method"=>"DELETE","href"=>"http://example.com/products"} } @@ -62,5 +62,80 @@ it_behaves_like 'a SirenClient::Action' end + end + + describe 'when initialized with no data' do + let (:action) { + subject.new({ "fields" => [{}] }, { + headers: {}, + debug_output: output_buffer + }) + } + + describe '.payload' do + it 'is a hash' do + expect(action.payload).to be_a Hash + end + it 'is NOT overwritten with SirenClient::Field classes' do + expect(action.payload['fields'][0]).to be_a Hash + end + end + + describe '.name' do + it 'is a string' do + expect(action.name).to be_a String + end + end + + describe '.classes' do + it 'is an array' do + expect(action.classes).to be_a Array + end + end + + describe '.method' do + it 'is a string' do + expect(action.method).to be_a String + end + it 'defaults to GET' do + expect(subject.new({ }).method).to eq('get') + end + end + + describe '.href' do + it 'is a string' do + expect(action.href).to be_a String + end + it 'can change .href as needed' do + action.href = action.href + '?query=test' + expect(/query=test/).to match(action.href) + end + end + + describe '.title' do + it 'is a string' do + expect(action.title).to be_a String + end + end + + describe '.type' do + it 'is a string' do + expect(action.type).to be_a String + end + it 'defaults to \'application/x-www-form-urlencoded\'' do + expect(action.type).to eq('application/x-www-form-urlencoded') + end + end + + describe '.fields' do + it 'is an array' do + expect(action.fields).to be_a Array + end + it 'is an array of SirenClient::Field\'s' do + action.fields.each do |field| + expect(field).to be_a SirenClient::Field + end + end end end +end From b7809a98be177da514a0184556e29fcf66cda531 Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Sat, 16 Dec 2017 14:39:24 -0600 Subject: [PATCH 6/7] shared example file naes shouldn't end with _spec --- .../{siren_client_action_spec.rb => siren_client_action.rb} | 0 .../{siren_client_entity_spec.rb => siren_client_entity.rb} | 0 .../{siren_client_field_spec.rb => siren_client_field.rb} | 0 .../{siren_client_link_spec.rb => siren_client_link.rb} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename spec/support/shared_examples/{siren_client_action_spec.rb => siren_client_action.rb} (100%) rename spec/support/shared_examples/{siren_client_entity_spec.rb => siren_client_entity.rb} (100%) rename spec/support/shared_examples/{siren_client_field_spec.rb => siren_client_field.rb} (100%) rename spec/support/shared_examples/{siren_client_link_spec.rb => siren_client_link.rb} (100%) diff --git a/spec/support/shared_examples/siren_client_action_spec.rb b/spec/support/shared_examples/siren_client_action.rb similarity index 100% rename from spec/support/shared_examples/siren_client_action_spec.rb rename to spec/support/shared_examples/siren_client_action.rb diff --git a/spec/support/shared_examples/siren_client_entity_spec.rb b/spec/support/shared_examples/siren_client_entity.rb similarity index 100% rename from spec/support/shared_examples/siren_client_entity_spec.rb rename to spec/support/shared_examples/siren_client_entity.rb diff --git a/spec/support/shared_examples/siren_client_field_spec.rb b/spec/support/shared_examples/siren_client_field.rb similarity index 100% rename from spec/support/shared_examples/siren_client_field_spec.rb rename to spec/support/shared_examples/siren_client_field.rb diff --git a/spec/support/shared_examples/siren_client_link_spec.rb b/spec/support/shared_examples/siren_client_link.rb similarity index 100% rename from spec/support/shared_examples/siren_client_link_spec.rb rename to spec/support/shared_examples/siren_client_link.rb From dbaaa4e73120402c4e4ea2ed4ecf407cebab55fd Mon Sep 17 00:00:00 2001 From: Bryan Finlayson Date: Fri, 22 Dec 2017 09:50:47 -0600 Subject: [PATCH 7/7] fix spacing --- spec/support/shared_examples/siren_client_action.rb | 6 +++--- spec/support/shared_examples/siren_client_entity.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/support/shared_examples/siren_client_action.rb b/spec/support/shared_examples/siren_client_action.rb index 2f914d0..31b6b81 100644 --- a/spec/support/shared_examples/siren_client_action.rb +++ b/spec/support/shared_examples/siren_client_action.rb @@ -3,6 +3,7 @@ it 'is a hash' do expect(action.config).to be_a Hash end + it 'can access a property of the config' do expect(action.config[:headers]['Accept']).to eq('application/json') end @@ -13,6 +14,7 @@ it 'is a hash' do expect(action.payload).to be_a Hash end + it 'is NOT overwritten with SirenClient::Field classes' do expect(action.payload['fields'][0]).to be_a Hash end @@ -70,9 +72,8 @@ end end end - - end + describe '.where(params)' do it 'executes the GET action' do # I'm expecting an error here, all I want to see is that the url it being traversed. @@ -97,7 +98,6 @@ it 'executes the DELETE action' do expect { action_delete.submit }.to raise_error SirenClient::InvalidResponseError end - # The rest will be tested in the live specs. end describe '.submit' do diff --git a/spec/support/shared_examples/siren_client_entity.rb b/spec/support/shared_examples/siren_client_entity.rb index cd8313a..8dec863 100644 --- a/spec/support/shared_examples/siren_client_entity.rb +++ b/spec/support/shared_examples/siren_client_entity.rb @@ -108,8 +108,8 @@ end it 'can change .href as needed' do - entity.href = 'http://example.com?query=test' - expect(/query=test/).to match(entity.href) + entity.href = 'http://example.com?query=test' + expect(/query=test/).to match(entity.href) end end