diff --git a/device_cloud.gemspec b/device_cloud.gemspec index a9cc6de..9e9d4a6 100644 --- a/device_cloud.gemspec +++ b/device_cloud.gemspec @@ -4,22 +4,24 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'device_cloud/version' Gem::Specification.new do |spec| - spec.name = "device_cloud" + spec.name = 'device_cloud' spec.version = DeviceCloud::VERSION - spec.authors = ["Erik Straub"] - spec.email = ["erik@madgloryint.com"] + spec.authors = ['Erik Straub'] + spec.email = ['erik@madgloryint.com'] spec.description = %q{A Ruby wrapper for the Etherios Device Cloud} spec.summary = %q{A Ruby wrapper for the Etherios Device Cloud} - spec.homepage = "http://github.com/madgloryint/device_cloud" - spec.license = "MIT" + spec.homepage = 'http://github.com/madgloryint/device_cloud' + spec.license = 'MIT' spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] - spec.add_development_dependency "bundler", "~> 1.3" - spec.add_development_dependency "rake" + spec.add_dependency 'nori', '~> 2.3' + + spec.add_development_dependency 'bundler', '~> 1.3' + spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' spec.add_development_dependency 'webmock' spec.add_development_dependency 'pry' diff --git a/lib/device_cloud.rb b/lib/device_cloud.rb index 4492862..233294d 100644 --- a/lib/device_cloud.rb +++ b/lib/device_cloud.rb @@ -1,6 +1,10 @@ require 'device_cloud/configuration' require 'device_cloud/utils' require 'device_cloud/version' +require 'device_cloud/alarm' +require 'device_cloud/device_disconnect_alarm' +require 'device_cloud/alarm_template' +require 'device_cloud/group' require 'device_cloud/monitor' require 'device_cloud/push_notification' require 'device_cloud/push_notification/base_notification' @@ -11,6 +15,7 @@ require 'device_cloud/push_notification/message/file_data' require 'device_cloud/request' require 'device_cloud/response' +require 'nori' module DeviceCloud extend Configuration diff --git a/lib/device_cloud/alarm.rb b/lib/device_cloud/alarm.rb new file mode 100644 index 0000000..1652f4b --- /dev/null +++ b/lib/device_cloud/alarm.rb @@ -0,0 +1,173 @@ +module DeviceCloud + class Alarm + + attr_accessor :almId, :cstId, :almtId, :grpId, :almName, :almDescription, :almScopeConfig, :almRuleConfig + attr_reader :error, :almEnabled, :almPriority + + class << self + def all + response = DeviceCloud::Request.new(path: '/ws/Alarm').get + alarms = response.to_hash_from_xml + + return [] unless response.code == '200' && alarms['result']['resultSize'].to_i > 0 + + if alarms['result']['resultSize'].to_i == 1 + [initialize_proper_alarm_type(alarms['result']['Alarm'])] + else + alarms['result']['Alarm'].map { |alarm| + initialize_proper_alarm_type(alarm) + } + end + end + + def find(id) + response = DeviceCloud::Request.new(path: "/ws/Alarm/#{id}").get + alarms = response.to_hash_from_xml + + return nil unless response.code == '200' && alarms['result']['resultSize'].to_i > 0 + + if alarms['result']['resultSize'].to_i == 1 + initialize_proper_alarm_type(alarms['result']['Alarm']) + else + initialize_proper_alarm_type(alarms['result']['Alarm'].first) + end + end + + protected + def initialize_proper_alarm_type(alarm) + case alarm['almtId'] + when '2' then DeviceDisconnectAlarm.new(alarm) + else Alarm.new(alarm) + end + end + end + + def initialize(attributes = {}) + set_defaults + + attributes.each do |name, value| + send("#{name}=", value) + end + end + + def almEnabled=(value) + @almEnabled = !!value + end + + def almPriority=(value) + raise DeviceCloud::Error, 'almPriority must be 0 (high), 1 (medium), or 2 (low)' unless [0,1,2].include?(value.to_i) + @almPriority = value.to_i + end + + def persist! + remove_instance_variable '@error' if error + almId.nil? ? create : update + end + + def destroy! + return false if almId.nil? + + response = DeviceCloud::Request.new(path: "/ws/Alarm/#{almId}").delete + + response.code == '200' ? true : false + end + + def attributes + available_attributes.inject({}) do |memo, attr_name| + memo[attr_name] = send(attr_name) unless send(attr_name).nil? + memo + end + end + + protected + def set_defaults + # no-op + end + + def available_attributes + %w{ + almtId + almName + almDescription + almPriority + almEnabled + grpId + } + end + + def create + response = DeviceCloud::Request.new(path: '/ws/Alarm', body: to_xml).post + xml_result = response.to_hash_from_xml['result'] + + if response.code == '201' + self.almId = xml_result['location'].sub /Alarm\//, '' + return true + else + @error = xml_result['error'] + return false + end + end + + def update + response = DeviceCloud::Request.new(path: "/ws/Alarm/#{almId}", body: to_xml).put + + if response.code == '200' + return true + else + @error = error_from_response_xml(response) + return false + end + end + + def to_xml + xml = '' + attributes.each do |key,value| + xml << "<#{key}>#{value}" + end + xml << alarm_scope_config_xml + xml << alarm_rule_config_xml + xml << '' + end + + # Method to set the Alarm's scope + # + # Example: + # + # + # + # + # + def alarm_scope_config_xml + raise NotImplementedError + end + + # Method to set the Alarm's rules + # + # Example: + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + def alarm_rule_config_xml + raise NotImplementedError + end + + def error_from_response_xml(response) + response.body.match(/(.*)<\/error>/)[1] + end + end +end diff --git a/lib/device_cloud/alarm_template.rb b/lib/device_cloud/alarm_template.rb new file mode 100644 index 0000000..9d7a255 --- /dev/null +++ b/lib/device_cloud/alarm_template.rb @@ -0,0 +1,38 @@ +module DeviceCloud + class AlarmTemplate + + attr_reader :almtId, :almtName, :almtDescription, :grpId, :almtTopic, :almtScopeOptions, :almtRules, :almtResourceList + + class << self + def all + response = DeviceCloud::Request.new(path: '/ws/AlarmTemplate').get + templates = response.to_hash_from_xml + + return [] unless response.code == '200' && templates['result']['resultSize'].to_i > 0 + + if templates['result']['resultSize'].to_i == 1 + [AlarmTemplate.new(templates['result']['AlarmTemplate'])] + else + templates['result']['AlarmTemplate'].map { |template| + AlarmTemplate.new template + } + end + end + + def find(id) + response = DeviceCloud::Request.new(path: "/ws/AlarmTemplate/#{id}").get + templates = response.to_hash_from_xml + + return nil unless response.code == '200' && templates['result']['resultSize'].to_i == 1 + + AlarmTemplate.new(templates['result']['AlarmTemplate']) + end + end + + def initialize(attributes = {}) + attributes.each do |name, value| + instance_variable_set("@#{name}", value) + end + end + end +end diff --git a/lib/device_cloud/configuration.rb b/lib/device_cloud/configuration.rb index 35d255d..bfd294e 100644 --- a/lib/device_cloud/configuration.rb +++ b/lib/device_cloud/configuration.rb @@ -110,5 +110,19 @@ def password def logger @logger ||= Logger.new(STDOUT) end + + # DeviceCloud logger + # + # @return the DeviceCloud logger or set the default to stdout + def logger + @logger ||= Logger.new(STDOUT) + end + + # DeviceCloud xml_parser + # + # @return the DeviceCloud xml_parser - default is Nori using rexml + def xml_parser + Nori.new(parser: :rexml) + end end end diff --git a/lib/device_cloud/device_disconnect_alarm.rb b/lib/device_cloud/device_disconnect_alarm.rb new file mode 100644 index 0000000..43e7461 --- /dev/null +++ b/lib/device_cloud/device_disconnect_alarm.rb @@ -0,0 +1,86 @@ +module DeviceCloud + class DeviceDisconnectAlarm < Alarm + attr_accessor :reconnectWindowDuration, :reset_on_reconnect, :device_id, :group + attr_reader :group + + def group=(group_object) + raise DeviceCloud::Error, 'Must be a DeviceCloud::Group' unless group_object.is_a?(DeviceCloud::Group) + + @group = group_object + self.grpId = group.grpId + end + + def initialize(attributes = {}) + super + load_group + load_device_id + self.almtId = 2 + end + + def persist! + raise DeviceCloud::Error, 'Must specify @group or @device_id.' unless group || device_id + super + end + private + def default_create_options + { + 'reconnectWindowDuration' => '5', + 'reset_on_reconnect' => true + } + end + + def set_defaults + super + default_create_options.each do |k,v| + send("#{k}=",v) + end + end + + def alarm_scope_config_xml + return '' if group.nil? && device_id.nil? + "\n#{scope_xml}\n" + end + + def scope_xml + group.nil? ? device_scope_xml : group_scope_xml + end + + def group_scope_xml + "" + end + + def device_scope_xml + "" + end + + def alarm_rule_config_xml + xml = "\n" + xml << fire_rule_xml + xml << reset_rule_xml + xml << "\n" + end + + def reset_rule_xml + xml = '\n" + end + + def fire_rule_xml + xml = '' + xml << '\n" + end + + def load_group + return if almScopeConfig.nil? || almScopeConfig['ScopingOptions']['Scope']['@name'] != 'Group' + @group ||= DeviceCloud::Group.find(grpId) + end + + def load_device_id + return if almScopeConfig.nil? || almScopeConfig['ScopingOptions']['Scope']['@name'] != 'Device' + @device_id ||= almScopeConfig['ScopingOptions']['Scope']['@value'] + end + end +end diff --git a/lib/device_cloud/group.rb b/lib/device_cloud/group.rb new file mode 100644 index 0000000..2102f30 --- /dev/null +++ b/lib/device_cloud/group.rb @@ -0,0 +1,53 @@ +module DeviceCloud + class Group + attr_reader :grpId, :grpName, :grpDescription, :grpPath, :grpParentId + + ALLOWED_ATTRIBUTES = %w{ + grpId + grpName + grpDescription + grpPath + grpParentId + } + + class << self + def all + groups = DeviceCloud::Request.new(path: '/ws/Group/.json').get.to_hash_from_json + + return [] unless groups['resultSize'].to_i > 0 + + groups['items'].map { |group| + Group.new group + } + end + + def find(group_id) + groups = DeviceCloud::Request.new(path: "/ws/Group/#{group_id}.json").get.to_hash_from_json + + return nil unless groups['resultSize'].to_i > 0 + + Group.new groups['items'].first + end + + def find_all_by_parent_id(parent_id) + parent_id = parent_id.to_i + + path = "/ws/Group/.json?condition=grpParentId='#{parent_id}'" + + groups = DeviceCloud::Request.new(path: path).get.to_hash_from_json + + return [] unless groups['resultSize'].to_i > 0 + + groups['items'].map { |group| + Group.new group + } + end + end + + def initialize(attributes = {}) + ALLOWED_ATTRIBUTES.each do |attr_name| + instance_variable_set("@#{attr_name}", attributes[attr_name]) + end + end + end +end diff --git a/lib/device_cloud/response.rb b/lib/device_cloud/response.rb index b871023..bb47dd9 100644 --- a/lib/device_cloud/response.rb +++ b/lib/device_cloud/response.rb @@ -10,6 +10,10 @@ def initialize(http_response) @body = original_response.body end + def to_hash_from_xml + DeviceCloud.xml_parser.parse(body) + end + def to_hash_from_json JSON.parse(body) end diff --git a/spec/device_cloud/alarm_spec.rb b/spec/device_cloud/alarm_spec.rb new file mode 100644 index 0000000..1f86a87 --- /dev/null +++ b/spec/device_cloud/alarm_spec.rb @@ -0,0 +1,247 @@ +require 'spec_helper' + +describe DeviceCloud::Alarm do + its(:almId) { should be_nil } + its(:cstId) { should be_nil } + its(:almtId) { should be_nil } + its(:grpId) { should be_nil } + its(:almName) { should be_nil } + its(:almDescription) { should be_nil } + its(:almScopeConfig) { should be_nil } + its(:almRuleConfig) { should be_nil } + its(:almEnabled) { should be_nil } + its(:almPriority) { should be_nil } + its(:error) { should be_nil } + + context 'when attributes given' do + let(:attributes) do + { + almId: 'foo', + cstId: 'foo', + almtId: 'foo', + grpId: 'foo', + almName: 'foo', + almDescription: 'foo', + almScopeConfig: 'foo', + almRuleConfig: 'foo', + almEnabled: true, + almPriority: 2 + } + end + + subject { DeviceCloud::Alarm.new attributes} + + its(:almId) { should eq 'foo' } + its(:cstId) { should eq 'foo' } + its(:almtId) { should eq 'foo' } + its(:grpId) { should eq 'foo' } + its(:almName) { should eq 'foo' } + its(:almDescription) { should eq 'foo' } + its(:almScopeConfig) { should eq 'foo' } + its(:almRuleConfig) { should eq 'foo' } + its(:almEnabled) { should eq true } + its(:almPriority) { should eq 2 } + end + + describe '::all' do + let(:group_body) do + %{{"resultTotalRows": "1","requestedStartRow": "0","resultSize": "1","requestedSize": "1000","remainingSize": "0","items": [{ "grpId": "9769", "grpName": "Staging", "grpPath": "/4044_MadGlory_Interactive/Staging/", "grpParentId": "4946"}] }} + end + + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/Alarm"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => response_body, :headers => {}) + end + + subject { DeviceCloud::Alarm.all } + + context 'when results are found' do + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/Group/9769.json"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => group_body, :headers => {}) + end + + let(:response_body) do + %{ + + 1 + 0 + 1 + 1000 + 0 + + 1898 + 4044 + #{almtId} + 9769 + Staging Device Disconnect + Detects when a device disconnects from Device Cloud and fails to reconnected within the specified time + true + 1 + + + + + + + + + + + + + + + } + end + + + context 'result has a known almtId' do + let(:almtId) { 2 } + + it { should be_a(Array) } + its(:first) { should be_a(DeviceCloud::DeviceDisconnectAlarm) } + end + + context 'result has unknown almtId' do + let(:almtId) { 26 } + + it { should be_a(Array) } + its(:first) { should be_a(DeviceCloud::Alarm) } + end + end + + context 'when results are not found' do + let(:response_body) do + %{ + + 0 + 0 + 0 + 1000 + 0 + } + end + + it { should eq [] } + end + end + + describe '::find' do + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/Alarm/1898"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => response_body, :headers => {}) + end + + subject { DeviceCloud::Alarm.find 1898 } + + context 'when result is found' do + let(:group_body) do + %{{"resultTotalRows": "1","requestedStartRow": "0","resultSize": "1","requestedSize": "1000","remainingSize": "0","items": [{ "grpId": "9769", "grpName": "Staging", "grpPath": "/4044_MadGlory_Interactive/Staging/", "grpParentId": "4946"}] }} + end + + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/Group/9769.json"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => group_body, :headers => {}) + end + + let(:response_body) do + %{ + + 1 + 0 + 1 + 1000 + 0 + + 1898 + 4044 + #{almtId} + 9769 + Staging Device Disconnect + Detects when a device disconnects from Device Cloud and fails to reconnected within the specified time + true + 1 + + + + + + + + + + + + + + + } + end + + context 'with a known almtId' do + let(:almtId) { 2 } + it { should be_a(DeviceCloud::DeviceDisconnectAlarm) } + end + + context 'with an unknown almtId' do + let(:almtId) { 26 } + it { should be_a(DeviceCloud::Alarm)} + end + end + + context 'when result is not found' do + let(:response_body) do + %{ + + GET AlarmTemplate error. Error reading AlarmTemplate entity id='212' + } + end + + it { should be_nil } + end + end + + describe '#persist!' do + subject { DeviceCloud::Alarm.new } + + it 'raises NotImplementedError' do + expect { subject.persist! }.to raise_error(NotImplementedError) + end + end + + describe '#destroy!' do + context 'when almId is nil' do + its(:destroy!) { should be_false } + end + + context 'when almId is not nil' do + let(:existing_alarm) { DeviceCloud::Alarm.new almId: 12345 } + before(:each) do + stub_request(:delete, "https://foouser:barpass@my.idigi.com/ws/Alarm/12345"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => response_status, :body => response_body, :headers => {}) + end + + subject { existing_alarm.destroy! } + + context 'when successful' do + let(:response_body) { "\n\n 1 items deleted\n" } + let(:response_status) { 200 } + + it { should be_true } + end + + context 'when unsuccessful' do + let(:response_body) { "\n\n DELETE Alarm error. Invalid request. For input string: \"gigo\"\n" } + let(:response_status) { 400 } + + it { should be_false } + end + end + end +end diff --git a/spec/device_cloud/alarm_template_spec.rb b/spec/device_cloud/alarm_template_spec.rb new file mode 100644 index 0000000..3f270d9 --- /dev/null +++ b/spec/device_cloud/alarm_template_spec.rb @@ -0,0 +1,161 @@ +require 'spec_helper' + +describe DeviceCloud::AlarmTemplate do + + its(:almtId) { should be_nil } + its(:almtName) { should be_nil } + its(:almtDescription) { should be_nil } + its(:grpId) { should be_nil } + its(:almtTopic) { should be_nil } + its(:almtScopeOptions) { should be_nil } + its(:almtRules) { should be_nil } + its(:almtResourceList) { should be_nil } + + context 'when attributes given' do + let(:attributes) do + { + almtId: 'foo', + almtName: 'foo', + almtDescription: 'foo', + grpId: 'foo', + almtTopic: 'foo', + almtScopeOptions: 'foo', + almtRules: 'foo', + almtResourceList: 'foo' + } + end + + subject { DeviceCloud::AlarmTemplate.new attributes } + + its(:almtId) { should eq 'foo' } + its(:almtName) { should eq 'foo' } + its(:almtDescription) { should eq 'foo' } + its(:grpId) { should eq 'foo' } + its(:almtTopic) { should eq 'foo' } + its(:almtScopeOptions) { should eq 'foo' } + its(:almtRules) { should eq 'foo' } + its(:almtResourceList) { should eq 'foo' } + end + + describe '::all' do + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/AlarmTemplate"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => response_body, :headers => {}) + end + + subject { DeviceCloud::AlarmTemplate.all } + + context 'when results are found' do + let(:response_body) do + %{ + + 1 + 0 + 1 + 1000 + 0 + + 2 + Device Offline + Detects when a device disconnects from Device Cloud and fails to reconnected within the specified time + 1 + Alarm.DeviceOffline + + + + + + + + + + + + + + + DeviceCore,AlarmStatus + + } + end + + it { should be_a(Array) } + its(:first) { should be_a(DeviceCloud::AlarmTemplate) } + its(:size) { should eq 1 } + end + + context 'when results are not found' do + let(:response_body) do + %{ + + 0 + 0 + 0 + 1000 + 0 + } + end + + it { should eq [] } + end + end + + describe '::find' do + before(:each) do + stub_request(:get, "https://foouser:barpass@my.idigi.com/ws/AlarmTemplate/2"). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => response_body, :headers => {}) + end + + subject { DeviceCloud::AlarmTemplate.find 2 } + + context 'when result is found' do + let(:response_body) do + %{ + + 1 + 0 + 1 + 1000 + 0 + + 2 + Device Offline + Detects when a device disconnects from Device Cloud and fails to reconnected within the specified time + 1 + Alarm.DeviceOffline + + + + + + + + + + + + + + + DeviceCore,AlarmStatus + + } + end + + it { should be_a(DeviceCloud::AlarmTemplate) } + end + + context 'when result is not found' do + let(:response_body) do + %{ + + GET AlarmTemplate error. Error reading AlarmTemplate entity id='212' + } + end + + it { should be_nil } + end + end +end diff --git a/spec/device_cloud/device_disconnect_alarm_spec.rb b/spec/device_cloud/device_disconnect_alarm_spec.rb new file mode 100644 index 0000000..2c97a91 --- /dev/null +++ b/spec/device_cloud/device_disconnect_alarm_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe DeviceCloud::DeviceDisconnectAlarm do + pending 'Add specs' +end diff --git a/spec/device_cloud/group_spec.rb b/spec/device_cloud/group_spec.rb new file mode 100644 index 0000000..5107152 --- /dev/null +++ b/spec/device_cloud/group_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +describe DeviceCloud::Group do + describe '::all' do + before(:each) do + stub_request(:get, authenticated_host + '/ws/Group/.json'). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => groups_json, :headers => {}) + end + + subject { DeviceCloud::Group.all } + + context 'with results' do + let(:groups_json) do + "{\"resultTotalRows\": \"2\",\"requestedStartRow\": \"0\",\"resultSize\": \"2\",\"requestedSize\": \"1000\",\"remainingSize\": \"0\",\"items\": [{ \"grpId\": \"4946\", \"grpName\": \"Your_Company\", \"grpDescription\": \"Your_Company root group\", \"grpPath\": \"/Your_Company/\", \"grpParentId\": \"1\"},{ \"grpId\": \"9769\", \"grpName\": \"Sub_Group\", \"grpPath\": \"/Your_Company/Sub_Group/\", \"grpParentId\": \"4946\"}] }" + end + + it { should be_a(Array) } + its(:size) { should eq 2 } + its(:first) { should be_a(DeviceCloud::Group) } + end + + context 'with no results' do + let(:groups_json) do + "{\"resultTotalRows\": \"0\",\"requestedStartRow\": \"0\",\"resultSize\": \"0\",\"requestedSize\": \"1000\",\"remainingSize\": \"0\",\"items\": [] }" + end + + it { should eq [] } + end + end + + describe '::find' do + before(:each) do + stub_request(:get, authenticated_host + '/ws/Group/123.json'). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => group_json, :headers => {}) + end + + subject { DeviceCloud::Group.find 123 } + + context 'with result' do + let(:group_result) do + {"resultTotalRows"=>"1", + "requestedStartRow"=>"0", + "resultSize"=>"1", + "requestedSize"=>"1000", + "remainingSize"=>"0", + "items"=> + [{"grpId"=>"9769", + "grpDescription"=>"Describing this group.", + "grpName"=>"Your_Company", + "grpPath"=>"/Your_Company/Your_Group/", + "grpParentId"=>"4946"}]} + end + let(:group_json) { group_result.to_json } + + it { should be_a(DeviceCloud::Group) } + its(:grpId) { should eq group_result['items'].first['grpId'] } + its(:grpDescription) { should eq group_result['items'].first['grpDescription'] } + its(:grpName) { should eq group_result['items'].first['grpName'] } + its(:grpPath) { should eq group_result['items'].first['grpPath'] } + its(:grpParentId) { should eq group_result['items'].first['grpParentId'] } + end + + context 'with no results' do + let(:group_json) do + {"error"=>["GET Group error. Error reading Group entity id='12123'"]}.to_json + end + + it { should be_nil } + end + end + + describe '::find_all_by_parent_id' do + before(:each) do + stub_request(:get, authenticated_host + '/ws/Group/.json?condition=grpParentId=\'1\''). + with(:headers => {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}). + to_return(:status => 200, :body => group_json, :headers => {}) + end + + subject { DeviceCloud::Group.find_all_by_parent_id 1 } + + context 'with result' do + let(:group_result) do + {"resultTotalRows"=>"1", + "requestedStartRow"=>"0", + "resultSize"=>"1", + "requestedSize"=>"1000", + "remainingSize"=>"0", + "items"=> + [{"grpId"=>"9769", + "grpDescription"=>"Describing this group.", + "grpName"=>"Your_Company", + "grpPath"=>"/Your_Company/Your_Group/", + "grpParentId"=>"1"}]} + end + let(:group_json) { group_result.to_json } + + it { should be_a(Array) } + its(:first) { should be_a(DeviceCloud::Group) } + its(:size) { should eq 1 } + end + + context 'with no results' do + let(:group_json) do + "{\"resultTotalRows\": \"0\",\"requestedStartRow\": \"0\",\"resultSize\": \"0\",\"requestedSize\": \"1000\",\"remainingSize\": \"0\",\"items\": [] }" + end + + it { should eq [] } + end + end +end