Skip to content

Commit

Permalink
initial rspec support, tests for Messages
Browse files Browse the repository at this point in the history
  • Loading branch information
emiltin committed Jul 11, 2019
1 parent 06b649a commit a287e48
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 8 deletions.
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "cucumber"
gem "rspec"
gem "rspec-expectations"
gem "timecop"
gem "colorize"
14 changes: 13 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GEM
connection: https://rubygems.org/
remote: https://rubygems.org/
specs:
backports (3.15.0)
builder (3.2.3)
Expand All @@ -24,18 +24,30 @@ GEM
gherkin (5.1.0)
multi_json (1.13.1)
multi_test (0.1.2)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
timecop (0.9.1)

PLATFORMS
ruby

DEPENDENCIES
colorize
cucumber
rspec
rspec-expectations
timecop

BUNDLED WITH
2.0.1
1 change: 1 addition & 0 deletions connector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ def wait_for_acknowledgement original, timeout, options={}
end

def wait_for_not_acknowledged original, timeout
p :not
wait_for_acknowledgement original, timeout, type: :not_acknowledged
end

Expand Down
14 changes: 7 additions & 7 deletions message.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# rsmp messages

require 'json'
require 'securerandom'
require'securerandom'
require_relative 'error'
require_relative 'rsmp'

module RSMP
class Message
Expand All @@ -10,6 +12,7 @@ class Message
attr_accessor :json, :direction,

def self.parse_attributes packet
raise ArgumentError unless packet
JSON.parse packet
rescue JSON::ParserError
raise InvalidPacket, bin_to_chars(packet)
Expand Down Expand Up @@ -53,9 +56,6 @@ def self.build attributes, packet
message
end

def validate
end

def type
@attributes["type"]
end
Expand Down Expand Up @@ -106,16 +106,16 @@ def initialize attributes = {}
end

def validate
validate_type &&
validate_id
validate_type == true &&
validate_id == true
end

def validate_type
@attributes["mType"] == "rSMsg"
end

def validate_id
@attributes["mId"] =~ /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/i
(@attributes["mId"] =~ /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/i) != nil
end

def valid?
Expand Down
180 changes: 180 additions & 0 deletions spec/message_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
require_relative '../message'
require 'timecop'

def build packet
attributes = RSMP::Message.parse_attributes(packet)
RSMP::Message.build(attributes,packet)
end

describe RSMP::Message do
let(:version_str) { '{"mType":"rSMsg","type":"Version","RSMP":[{"vers":"3.1.1"},{"vers":"3.1.2"},{"vers":"3.1.3"},{"vers":"3.1.4"}],"siteId":[{"sId":"RN+SI0001"}],"SXL":"1.1","mId":"8db00f0a-4124-406f-b3f9-ceb0dbe4aeb6"}' }
let(:ack_str) { '{"mType":"rSMsg","type":"MessageAck","oMId":"a54dc38b-7ddb-42a6-b6e8-95b0d00dad19","mId":"561c15c9-e050-4ee7-9cf4-8643c6769dcb"}' }
let(:not_ack_str) { '{"mType":"rSMsg","type":"MessageNotAck","rea":"since we are a rsmp::siteconnector","oMId":"24b5e2d1-fd32-4f12-80cf-f32f8b2772af","mId":"808b957d-6e93-408b-b5e3-ce7f64dc3c61"}' }
let(:watchdog_str) { '{"mType":"rSMsg","type":"Watchdog","wTs":"2019-07-11 06:37:55 UTC","mId":"a8cafa58-31bc-40bb-b335-645b5ac985cd"}' }
let(:command_request_str) { '{"mType":"rSMsg","type":"CommandRequest","ntsOId":"","xNId":"","cId":"AA+BBCCC=DDDEE002","arg":[{"cCI":"MA104","n":"message","cO":"","v":"Rainbbows!"}],"mId":"1a913af3-82ba-489b-8895-54c2fb56d728"}' }
let(:command_response_str) { '{"mType":"rSMsg","type":"CommandResponse","cId":"AA+BBCCC=DDDEE002","cTS":"2019-07-11T06:37:55.914Z","rvs":[{"cCI":"MA104","n":"message","v":"Rainbbows!","age":"recent"}],"mId":"f0f38584-e3ff-46f8-88a1-598e7de0e671"}' }
let(:aggregated_status_str) { ' {"mType":"rSMsg","type":"AggregatedStatus","aSTS":"2019-07-11T06:37:55.913Z","fP":null,"fS":null,"se":[false,false,false,false,false,false,false,false],"mId":"d9a904cc-b39d-4b72-ad67-f7d634552d36"}' }
let(:status_request_str) { '{"mType":"rSMsg","type":"StatusRequest","ntsOId":"","xNId":"","cId":"AA+BBCCC=DDDEE002","sS":[{"cCI":"S001","n":"number"}],"mId":"859e189e-c973-4b40-90c4-45a7a25f2dda"}' }
let(:status_response_str) { '{"mType":"rSMsg","type":"StatusResponse","cId":"AA+BBCCC=DDDEE002","sTs":"2019-07-11T06:37:56.096Z","sS":[{"cCI":"S001","n":"number","s":90,"q":"recent"}],"mId":"0872f9f4-caee-4495-96ef-68a5cf56c993"}' }
let(:status_subscribe_str) { '{"mType":"rSMsg","type":"StatusSubscribe","ntsOId":"","xNId":"","cId":"AA+BBCCC=DDDEE002","sS":[{"sCI":"S001","n":"number","uRt":"0.1"}],"mId":"6aee9e40-c6cb-4cd8-8b7a-3ee8906043c9"}' }
let(:status_unsubscribe_str) { '{"mType":"rSMsg","type":"StatusUnsubscribe","ntsOId":"","xNId":"","cId":"AA+BBCCC=DDDEE002","sS":[{"sCI":"S001","n":"number"}],"mId":"bae361e1-7b26-48f3-9776-5aac815544da"}' }
let(:status_update_str) { '{"mType":"rSMsg","type":"StatusUpdate","cId":"AA+BBCCC=DDDEE002","sTs":"2019-07-11T06:37:56.103Z","sS":[{"sCI":"S001","n":"number","s":98,"q":"recent"}],"mId":"e0694101-4b8c-4832-9bd4-7ed598b247bd"}' }
let(:unkown_str) { '{"mType":"rSMsg","type":"SomeNonExistingMessage","mId":"c014bd2d-5671-4a19-b37e-50deef301b82"}' }
let(:malformed_str) { '{"mType":"rSMsg",mId":"c014bd2d-5671-4a19-b37e-50deef301b82"}' }

context 'when parsing json packages' do
it 'raises ArgumentError when parsing nil' do
expect { RSMP::Message.parse_attributes(nil) }.to raise_error(ArgumentError)
end

it 'raises InvalidPacket when parsing empty string' do
expect { RSMP::Message.parse_attributes('') }.to raise_error(RSMP::InvalidPacket)
end

it 'raises InvalidPacket when parsing whitespace' do
expect { RSMP::Message.parse_attributes(' ') }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes("\t") }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes("\n") }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes("\f") }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes("\r") }.to raise_error(RSMP::InvalidPacket)
end

it 'raises InvalidPacket when parsing invalid JSON ' do
expect { RSMP::Message.parse_attributes('{"a":"1"') }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes('"a":"1"}') }.to raise_error(RSMP::InvalidPacket)
expect { RSMP::Message.parse_attributes('/') }.to raise_error(RSMP::InvalidPacket)
end

it 'parses valid JSON' do
expect(RSMP::Message.parse_attributes('"string"')).to eq("string")
expect(RSMP::Message.parse_attributes('123')).to eq(123)
expect(RSMP::Message.parse_attributes('3.14')).to eq(3.14)
expect(RSMP::Message.parse_attributes('[1,2,3]')).to eq([1,2,3])
expect(RSMP::Message.parse_attributes('{"a":"1","b":"2"}')).to eq({"a"=>"1","b"=>"2"})
end
end

context 'when creating messages' do
let(:json) {{
"RSMP" => [{"vers"=>"3.1.1"}, {"vers"=>"3.1.2"}, {"vers"=>"3.1.3"}, {"vers"=>"3.1.4"}],
"SXL" => "1.1",
"mId" => "8db00f0a-4124-406f-b3f9-ceb0dbe4aeb6",
"mType" => "rSMsg",
"siteId" => [{"sId"=>"RN+SI0001"}],
"type" => "Version",
}}

it 'builds right type of objects when parsing JSON' do
expect(build(version_str)).to be_instance_of(RSMP::Version)
expect(build(ack_str)).to be_instance_of(RSMP::MessageAck)
expect(build(not_ack_str)).to be_instance_of(RSMP::MessageNotAck)
expect(build(watchdog_str)).to be_instance_of(RSMP::Watchdog)
expect(build(command_request_str)).to be_instance_of(RSMP::CommandRequest)
expect(build(command_response_str)).to be_instance_of(RSMP::CommandResponse)
expect(build(aggregated_status_str)).to be_instance_of(RSMP::AggregatedStatus)
expect(build(status_request_str)).to be_instance_of(RSMP::StatusRequest)
expect(build(status_response_str)).to be_instance_of(RSMP::StatusResponse)
expect(build(status_subscribe_str)).to be_instance_of(RSMP::StatusSubscribe)
expect(build(status_unsubscribe_str)).to be_instance_of(RSMP::StatusUnsubscribe)
expect(build(status_update_str)).to be_instance_of(RSMP::StatusUpdate)
expect(build(unkown_str)).to be_instance_of(RSMP::Unknown)
end

it 'parses attributes values' do
message = build(version_str)
expect(message.attributes).to eq(json)
end

it 'initializes attributes' do
message = RSMP::Version.new json
expect(message.attributes).to eq(json)
expect(message.m_id).to eq(json["mId"])
end

it 'initializes timestamp' do
time = Time.new(2019, 9, 1, 14, 24, 17)
Timecop.freeze(time) do
message = RSMP::Version.new json
expect(message.timestamp).to eq(time)
end
end

it 'randomizes message id if attributes are empty' do
message = RSMP::Version.new
expect(message.m_id).to match(/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/i)
another_message = RSMP::Version.new
expect(message.m_id).not_to eq(another_message.m_id)
end

it 'builds specific message types' do
expect(RSMP::Version.new.type).to eq("Version")
expect(RSMP::MessageAck.new.type).to eq("MessageAck")
expect(RSMP::MessageNotAck.new.type).to eq("MessageNotAck")
expect(RSMP::AggregatedStatus.new.type).to eq("AggregatedStatus")
expect(RSMP::Watchdog.new.type).to eq("Watchdog")
expect(RSMP::Alarm.new.type).to eq("Alarm")
expect(RSMP::CommandRequest.new.type).to eq("CommandRequest")
expect(RSMP::CommandResponse.new.type).to eq("CommandResponse")
expect(RSMP::StatusRequest.new.type).to eq("StatusRequest")
expect(RSMP::StatusResponse.new.type).to eq("StatusResponse")
expect(RSMP::StatusSubscribe.new.type).to eq("StatusSubscribe")
expect(RSMP::StatusUnsubscribe.new.type).to eq("StatusUnsubscribe")
expect(RSMP::StatusUpdate.new.type).to eq("StatusUpdate")
expect(RSMP::Unknown.new.type).to be_nil
expect(RSMP::Malformed.new.type).to be_nil
expect(RSMP::Unknown.new.type).to be_nil
expect(RSMP::Message.new.type).to be_nil
end

it 'generates json' do
message = RSMP::Version.new(json)
message.generate_json
str = '{"mType":"rSMsg","type":"Version","RSMP":[{"vers":"3.1.1"},{"vers":"3.1.2"},{"vers":"3.1.3"},{"vers":"3.1.4"}],"SXL":"1.1","mId":"8db00f0a-4124-406f-b3f9-ceb0dbe4aeb6","siteId":[{"sId":"RN+SI0001"}]}'
expect(message.json).to eq(str)
expect(message.out).to eq("#{str}\f")
end

it 'validates mType' do
message = RSMP::Message.new "mType"=>"rSMsg","type"=>"Version","mId"=>"c014bd2d-5671-4a19-b37e-50deef301b82"
expect(message.validate_type).to eq(true)

message = RSMP::Message.new "mType"=>"rBad","type"=>"Version","mId"=>"c014bd2d-5671-4a19-b37e-50deef301b82"
expect(message.validate_type).to eq(false)
end

it 'validates message id format' do
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"c014bd2d-5671-4a19-b37e-50deef301b82").validate_id).to eq(true)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"0c014bd2d-5671-4a19-b37e-50deef301b82").validate_id).to eq(true)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>".014bd2d-5671-4a19-b37e").validate_id).to eq(false)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"014bd2d-5671-4a19-b37e-50deef301b82").validate_id).to eq(false)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"14bd2d-5671-4a19-b37e-50deef301b82").validate_id).to eq(false)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"c014bd2d5671-4a19-b37e-50deef301b82").validate_id).to eq(false)
expect(RSMP::Message.new("mType"=>"rSMsg","type"=>"Version","mId"=>"c014bd2d5671-4a19-037e-50deef301b82").validate_id).to eq(false)
end
end

context 'when accessing attributes' do
let(:message) { build(version_str) }

it 'returns attribute values' do
expect( message.attribute("SXL") ).to eq('1.1')
end

it 'raises MissingAttribute when accessing non-existing attribute' do
expect { message.attribute("bad") }.to raise_error(RSMP::MissingAttribute, "missing attribute 'bad'")
end

it 'raises MissingAttribute when accessing attribute with wrong case' do
expect { message.attribute("sxl") }.to raise_error(RSMP::MissingAttribute, "attribute 'SXL' should be named 'sxl'")
end

it 'returns type' do
expect(message.type).to eq('Version')
end

it 'returns message id' do
expect(message.m_id).to eq('8db00f0a-4124-406f-b3f9-ceb0dbe4aeb6')
end
end

end
100 changes: 100 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end

# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end

# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
# have no way to turn it off -- the option exists only for backwards
# compatibility in RSpec 3). It causes shared context metadata to be
# inherited by the metadata hash of host groups and examples, rather than
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups

# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end

0 comments on commit a287e48

Please sign in to comment.