spec/mqtt_client_spec.rb in mqtt-0.3.1 vs spec/mqtt_client_spec.rb in mqtt-0.4.0

- old
+ new

@@ -166,18 +166,54 @@ client.cert_file = fixture_path('client.pem') expect(client.ssl_context.cert).to be_a(OpenSSL::X509::Certificate) end end + describe "setting a client certificate directly" do + it "should add a certificate to the SSL context" do + expect(client.ssl_context.cert).to be_nil + client.cert = File.read(fixture_path('client.pem')) + expect(client.ssl_context.cert).to be_a(OpenSSL::X509::Certificate) + end + end + describe "setting a client private key file path" do it "should add a certificate to the SSL context" do expect(client.ssl_context.key).to be_nil client.key_file = fixture_path('client.key') expect(client.ssl_context.key).to be_a(OpenSSL::PKey::RSA) end end + describe "setting a client private key directly" do + it "should add a certificate to the SSL context" do + expect(client.ssl_context.key).to be_nil + client.key = File.read(fixture_path('client.key')) + expect(client.ssl_context.key).to be_a(OpenSSL::PKey::RSA) + end + end + + describe "setting an encrypted client private key, w/the correct passphrase" do + let(:key_pass) { 'mqtt' } + + it "should add the decrypted certificate to the SSL context" do + expect(client.ssl_context.key).to be_nil + client.key_file = [fixture_path('client.pass.key'), key_pass] + expect(client.ssl_context.key).to be_a(OpenSSL::PKey::RSA) + end + end + + describe "setting an encrypted client private key, w/an incorrect passphrase" do + let(:key_pass) { 'ttqm' } + + it "should raise an OpenSSL::PKey::RSAError exception" do + expect(client.ssl_context.key).to be_nil + expect { client.key_file = [fixture_path('client.pass.key'), key_pass] }.to( + raise_error(OpenSSL::PKey::RSAError, /Neither PUB key nor PRIV key/)) + end + end + describe "setting a Certificate Authority file path" do it "should add a CA file path to the SSL context" do expect(client.ssl_context.ca_file).to be_nil client.ca_file = fixture_path('root-ca.pem') expect(client.ssl_context.ca_file).to eq(fixture_path('root-ca.pem')) @@ -251,22 +287,32 @@ it "should try and read an acknowledgement packet to the socket if not connected" do expect(client).to receive(:receive_connack).once client.connect('myclient') end - it "should throw an exception if no host is configured" do + it "should raise an exception if no host is configured" do expect { client = MQTT::Client.new client.connect }.to raise_error( 'No MQTT server host set when attempting to connect' ) end - it "should disconnect after connecting, if a block is given" do - expect(client).to receive(:disconnect).once - client.connect('myclient') { nil } + context "if a block is given" do + it "should disconnect after connecting" do + expect(client).to receive(:disconnect).once + client.connect('myclient') { nil } + end + + it "should disconnect even if the block raises an exception" do + expect(client).to receive(:disconnect).once + begin + client.connect('myclient') { raise StandardError } + rescue StandardError + end + end end it "should not disconnect after connecting, if no block is given" do expect(client).to receive(:disconnect).never client.connect('myclient') @@ -285,11 +331,11 @@ "\x00\x08password" ) end context "no client id is given" do - it "should throw an exception if the clean session flag is false" do + it "should raise an exception if the clean session flag is false" do expect { client.client_id = nil client.clean_session = false client.connect }.to raise_error( @@ -374,11 +420,11 @@ it "should have set the Will's retain flag to true" do expect(client.will_retain).to be_falsey end - it "should have set the Will's retain QOS value to 1" do + it "should have set the Will's retain QoS value to 1" do expect(client.will_qos).to eq(1) end it "should include the will in the CONNECT message" do client.connect('myclient') @@ -421,41 +467,41 @@ before(:each) do client.instance_variable_set('@socket', socket) allow(IO).to receive(:select).and_return([[socket], [], []]) end - it "should not throw an exception for a successful CONNACK packet" do + it "should not raise an exception for a successful CONNACK packet" do socket.write("\x20\x02\x00\x00") socket.rewind expect { client.send(:receive_connack) }.not_to raise_error end - it "should throw an exception if the packet type isn't CONNACK" do + it "should raise an exception if the packet type isn't CONNACK" do socket.write("\xD0\x00") socket.rewind expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException) end - it "should throw an exception if the CONNACK packet return code is 'unacceptable protocol version'" do + it "should raise an exception if the CONNACK packet return code is 'unacceptable protocol version'" do socket.write("\x20\x02\x00\x01") socket.rewind expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /unacceptable protocol version/i) end - it "should throw an exception if the CONNACK packet return code is 'client identifier rejected'" do + it "should raise an exception if the CONNACK packet return code is 'client identifier rejected'" do socket.write("\x20\x02\x00\x02") socket.rewind expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /client identifier rejected/i) end - it "should throw an exception if the CONNACK packet return code is 'server unavailable'" do + it "should raise an exception if the CONNACK packet return code is 'server unavailable'" do socket.write("\x20\x02\x00\x03") socket.rewind expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /server unavailable/i) end - it "should throw an exception if the CONNACK packet return code is an unknown" do + it "should raise an exception if the CONNACK packet return code is an unknown" do socket.write("\x20\x02\x00\xAA") socket.rewind expect { client.send(:receive_connack) }.to raise_error(MQTT::ProtocolException, /connection refused/i) end end @@ -489,27 +535,10 @@ expect(socket).to receive(:close) client.disconnect end end - describe "when calling the 'ping' method" do - before(:each) do - client.instance_variable_set('@socket', socket) - end - - it "should write a valid PINGREQ packet to the socket" do - client.ping - expect(socket.string).to eq("\xC0\x00") - end - - it "should update the time a ping was last sent" do - client.instance_variable_set('@last_pingreq', 0) - client.ping - expect(client.instance_variable_get('@last_pingreq')).not_to eq(0) - end - end - describe "when calling the 'publish' method" do before(:each) do client.instance_variable_set('@socket', socket) end @@ -521,42 +550,59 @@ it "should write a valid PUBLISH packet to the socket with the retain flag set" do client.publish('topic','payload', true, 0) expect(socket.string).to eq("\x31\x0e\x00\x05topicpayload") end - it "should write a valid PUBLISH packet to the socket with the QOS set to 1" do + it "should write a valid PUBLISH packet to the socket with the QoS set to 1" do + inject_puback(1) client.publish('topic','payload', false, 1) expect(socket.string).to eq("\x32\x10\x00\x05topic\x00\x01payload") end - it "should write a valid PUBLISH packet to the socket with the QOS set to 2" do + it "should write a valid PUBLISH packet to the socket with the QoS set to 2" do + inject_puback(1) client.publish('topic','payload', false, 2) expect(socket.string).to eq("\x34\x10\x00\x05topic\x00\x01payload") end it "should write a valid PUBLISH packet with no payload" do client.publish('test') expect(socket.string).to eq("\x30\x06\x00\x04test") end - it "should throw an ArgumentError exception, if the topic is nil" do + it "should write a valid PUBLISH packet with frozen payload" do + client.publish('topic', 'payload'.freeze, false, 0) + expect(socket.string).to eq("\x30\x0e\x00\x05topicpayload") + end + + it "should raise an ArgumentError exception, if the topic is nil" do expect { client.publish(nil) }.to raise_error( ArgumentError, 'Topic name cannot be nil' ) end - it "should throw an ArgumentError exception, if the topic is empty" do + it "should raise an ArgumentError exception, if the topic is empty" do expect { client.publish("") }.to raise_error( ArgumentError, 'Topic name cannot be empty' ) end + + it "correctly assigns consecutive ids to packets with QoS 1" do + inject_puback(1) + inject_puback(2) + + expect(client).to receive(:send_packet) { |packet| expect(packet.id).to eq(1) } + client.publish "topic", "message", false, 1 + expect(client).to receive(:send_packet) { |packet| expect(packet.id).to eq(2) } + client.publish "topic", "message", false, 1 + end end describe "when calling the 'subscribe' method" do before(:each) do client.instance_variable_set('@socket', socket) @@ -614,66 +660,89 @@ describe "when calling the 'get' method" do before(:each) do client.instance_variable_set('@socket', socket) end - it "should successfull receive a valid PUBLISH packet with a QoS 0" do + it "should successfully receive a valid PUBLISH packet with a QoS 0" do inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0) topic,payload = client.get expect(topic).to eq('topic0') expect(payload).to eq('payload0') end - it "should successfull receive a valid PUBLISH packet with a QoS 1" do + it "should successfully receive a valid PUBLISH packet with a QoS 1" do inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1) topic,payload = client.get expect(topic).to eq('topic1') expect(payload).to eq('payload1') expect(client.queue_empty?).to be_truthy end + it "acks calling #get_packet and qos=1" do + inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1) + expect(client).to receive(:send_packet).with(an_instance_of(MQTT::Packet::Puback)) + client.get_packet + end + + it "acks calling #get and qos=1" do + inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1) + expect(client).to receive(:send_packet).with(an_instance_of(MQTT::Packet::Puback)) + client.get + end + context "with a block" do - it "should successfull receive a more than 1 message" do + it "should successfully receive more than 1 message" do inject_packet(:topic => 'topic0', :payload => 'payload0') inject_packet(:topic => 'topic1', :payload => 'payload1') payloads = [] client.get do |topic,payload| payloads << payload break if payloads.size > 1 end expect(payloads.size).to eq(2) expect(payloads).to eq(['payload0', 'payload1']) end + + it "acks when qos > 1 after running the block" do + inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1) + inject_packet(:topic => 'topic2', :payload => 'payload1') + expect(client).to receive(:send_packet).with(an_instance_of(MQTT::Packet::Puback)) + payloads = [] + client.get do |topic,payload| + payloads << payload + break if payloads.size > 1 + end + end end end describe "when calling the 'get_packet' method" do before(:each) do client.instance_variable_set('@socket', socket) end - it "should successfull receive a valid PUBLISH packet with a QoS 0" do + it "should successfully receive a valid PUBLISH packet with a QoS 0" do inject_packet(:topic => 'topic0', :payload => 'payload0', :qos => 0) packet = client.get_packet expect(packet.class).to eq(MQTT::Packet::Publish) expect(packet.qos).to eq(0) expect(packet.topic).to eq('topic0') expect(packet.payload).to eq('payload0') end - it "should successfull receive a valid PUBLISH packet with a QoS 1" do + it "should successfully receive a valid PUBLISH packet with a QoS 1" do inject_packet(:topic => 'topic1', :payload => 'payload1', :qos => 1) packet = client.get_packet expect(packet.class).to eq(MQTT::Packet::Publish) expect(packet.qos).to eq(1) expect(packet.topic).to eq('topic1') expect(packet.payload).to eq('payload1') expect(client.queue_empty?).to be_truthy end context "with a block" do - it "should successfull receive a more than 1 packet" do + it "should successfully receive more than 1 packet" do inject_packet(:topic => 'topic0', :payload => 'payload0') inject_packet(:topic => 'topic1', :payload => 'payload1') packets = [] client.get_packet do |packet| packets << packet @@ -727,35 +796,57 @@ socket.rewind client.send(:receive_packet) expect(@read_queue.size).to eq(0) end - it "should send a ping packet if one is due" do - expect(IO).to receive(:select).and_return(nil) - client.instance_variable_set('@last_pingreq', Time.at(0)) - expect(client).to receive(:ping).once + it "should close the socket if there is an exception" do + expect(socket).to receive(:close).once + allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception) client.send(:receive_packet) end + it "should pass exceptions up to parent thread" do + expect(@parent_thread).to receive(:raise).once + allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception) + client.send(:receive_packet) + end + it "should update last_ping_response when receiving a Pingresp" do allow(MQTT::Packet).to receive(:read).and_return MQTT::Packet::Pingresp.new client.instance_variable_set '@last_ping_response', Time.at(0) client.send :receive_packet expect(client.last_ping_response).to be_within(1).of Time.now end + end - it "should close the socket if there is an exception" do - expect(socket).to receive(:close).once - allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception) - client.send(:receive_packet) + describe "when calling the 'keep_alive!' method" do + before(:each) do + client.instance_variable_set('@socket', socket) end - it "should pass exceptions up to parent thread" do - expect(@parent_thread).to receive(:raise).once - allow(MQTT::Packet).to receive(:read).and_raise(MQTT::Exception) - client.send(:receive_packet) + it "should send a ping packet if one is due" do + client.instance_variable_set('@last_ping_request', Time.at(0)) + client.send('keep_alive!') + expect(socket.string).to eq("\xC0\x00") end + + it "should update the time a ping was last sent" do + client.instance_variable_set('@last_ping_request', Time.at(0)) + client.send('keep_alive!') + expect(client.instance_variable_get('@last_ping_request')).not_to eq(0) + end + + it "should raise an exception if no ping response has been received" do + client.instance_variable_set('@last_ping_request', Time.now) + client.instance_variable_set('@last_ping_response', Time.at(0)) + expect { + client.send('keep_alive!') + }.to raise_error( + MQTT::ProtocolException, + /No Ping Response received for \d+ seconds/ + ) + end end describe "generating a client identifier" do context "with default parameters" do let(:client_id) { MQTT::Client.generate_client_id } @@ -793,8 +884,13 @@ private def inject_packet(opts={}) packet = MQTT::Packet::Publish.new(opts) client.instance_variable_get('@read_queue').push(packet) + end + + def inject_puback(packet_id) + packet = MQTT::Packet::Puback.new(:id => packet_id) + client.instance_variable_get('@pubacks')[packet_id] = packet end end