fix_spec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +11 -0
- data/CONTRIBUTION_GUIDELINES.md +22 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +81 -0
- data/LICENSE.txt +14 -0
- data/QUICKFIX_LICENSE.txt +46 -0
- data/README.md +181 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/features/builder.feature +148 -0
- data/features/equivalence.feature +52 -0
- data/features/equivalence_data_dictionary.feature +166 -0
- data/features/memory.feature +47 -0
- data/features/message_type.feature +25 -0
- data/features/nested_builder.feature +63 -0
- data/features/paths.feature +14 -0
- data/features/step_definitions/steps.rb +3 -0
- data/features/support/FIX42.xml +2670 -0
- data/features/support/env.rb +20 -0
- data/fix_spec.gemspec +89 -0
- data/lib/fix_spec/builder.rb +99 -0
- data/lib/fix_spec/configuration.rb +16 -0
- data/lib/fix_spec/cucumber.rb +76 -0
- data/lib/fix_spec/helpers.rb +98 -0
- data/lib/fix_spec/matchers/be_fix_eql.rb +45 -0
- data/lib/fix_spec/matchers/have_fix_path.rb +29 -0
- data/lib/fix_spec/matchers.rb +18 -0
- data/lib/fix_spec.rb +10 -0
- data/spec/fix_spec/helpers_spec.rb +114 -0
- data/spec/spec_helper.rb +13 -0
- metadata +194 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
$: << File.expand_path("../../../lib", __FILE__)
|
2
|
+
|
3
|
+
require "fix_spec/cucumber"
|
4
|
+
require "fix_spec/builder"
|
5
|
+
|
6
|
+
def last_fix
|
7
|
+
@last_fix
|
8
|
+
end
|
9
|
+
|
10
|
+
Around('@with_data_dictionary') do |scenario, block|
|
11
|
+
FIXSpec::data_dictionary= quickfix.DataDictionary.new "features/support/FIX42.xml"
|
12
|
+
block.call
|
13
|
+
FIXSpec::data_dictionary= nil
|
14
|
+
end
|
15
|
+
|
16
|
+
Around('@ignore_length_and_checksum') do |scenario, block|
|
17
|
+
JsonSpec.excluded_keys=%w(CheckSum BodyLength)
|
18
|
+
block.call
|
19
|
+
JsonSpec.excluded_keys=JsonSpec::Configuration::DEFAULT_EXCLUDED_KEYS
|
20
|
+
end
|
data/fix_spec.gemspec
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "fix_spec"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Chris Busbey"]
|
12
|
+
s.date = "2013-08-30"
|
13
|
+
s.description = "Build and Inspect FIX Messages with RSpec and Cucumber steps"
|
14
|
+
s.email = "info@connamara.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
".travis.yml",
|
23
|
+
"CONTRIBUTION_GUIDELINES.md",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"QUICKFIX_LICENSE.txt",
|
28
|
+
"README.md",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"features/builder.feature",
|
32
|
+
"features/equivalence.feature",
|
33
|
+
"features/equivalence_data_dictionary.feature",
|
34
|
+
"features/memory.feature",
|
35
|
+
"features/message_type.feature",
|
36
|
+
"features/nested_builder.feature",
|
37
|
+
"features/paths.feature",
|
38
|
+
"features/step_definitions/steps.rb",
|
39
|
+
"features/support/FIX42.xml",
|
40
|
+
"features/support/env.rb",
|
41
|
+
"fix_spec.gemspec",
|
42
|
+
"lib/fix_spec.rb",
|
43
|
+
"lib/fix_spec/builder.rb",
|
44
|
+
"lib/fix_spec/configuration.rb",
|
45
|
+
"lib/fix_spec/cucumber.rb",
|
46
|
+
"lib/fix_spec/helpers.rb",
|
47
|
+
"lib/fix_spec/matchers.rb",
|
48
|
+
"lib/fix_spec/matchers/be_fix_eql.rb",
|
49
|
+
"lib/fix_spec/matchers/have_fix_path.rb",
|
50
|
+
"spec/fix_spec/helpers_spec.rb",
|
51
|
+
"spec/spec_helper.rb"
|
52
|
+
]
|
53
|
+
s.homepage = "http://github.com/connamara/fix_spec"
|
54
|
+
s.licenses = ["GPL"]
|
55
|
+
s.require_paths = ["lib"]
|
56
|
+
s.rubygems_version = "1.8.24"
|
57
|
+
s.summary = "Build and Inspect FIX Messages"
|
58
|
+
|
59
|
+
if s.respond_to? :specification_version then
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
63
|
+
s.add_runtime_dependency(%q<json_spec>, ["~> 1.1.1"])
|
64
|
+
s.add_runtime_dependency(%q<quickfix-jruby>, ["~> 1.5.3"])
|
65
|
+
s.add_runtime_dependency(%q<cuke_mem>, ["~> 0.1.1"])
|
66
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.14"])
|
67
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8"])
|
68
|
+
s.add_development_dependency(%q<cucumber>, ["~> 1.3"])
|
69
|
+
s.add_development_dependency(%q<rake>, ["~> 10.1"])
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<json_spec>, ["~> 1.1.1"])
|
72
|
+
s.add_dependency(%q<quickfix-jruby>, ["~> 1.5.3"])
|
73
|
+
s.add_dependency(%q<cuke_mem>, ["~> 0.1.1"])
|
74
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
75
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
76
|
+
s.add_dependency(%q<cucumber>, ["~> 1.3"])
|
77
|
+
s.add_dependency(%q<rake>, ["~> 10.1"])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<json_spec>, ["~> 1.1.1"])
|
81
|
+
s.add_dependency(%q<quickfix-jruby>, ["~> 1.5.3"])
|
82
|
+
s.add_dependency(%q<cuke_mem>, ["~> 0.1.1"])
|
83
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
84
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
85
|
+
s.add_dependency(%q<cucumber>, ["~> 1.3"])
|
86
|
+
s.add_dependency(%q<rake>, ["~> 10.1"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#encoding ascii
|
2
|
+
|
3
|
+
module FIXSpec
|
4
|
+
module Builder
|
5
|
+
def self.message
|
6
|
+
@message
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.message= msg
|
10
|
+
@message=msg
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^the following( unvalidated)? fix message:$/ do |unvalidated,fix_str|
|
16
|
+
factory = quickfix.DefaultMessageFactory.new
|
17
|
+
unless unvalidated
|
18
|
+
FIXSpec::Builder.message = quickfix.MessageUtils.parse(factory, FIXSpec::data_dictionary, FIXSpec::Helpers.fixify_string(fix_str) )
|
19
|
+
else
|
20
|
+
FIXSpec::Builder.message = quickfix.MessageUtils.parse(factory, nil, FIXSpec::Helpers.fixify_string(fix_str) )
|
21
|
+
end
|
22
|
+
|
23
|
+
FIXSpec::Builder.message.should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
Given /^I create a (?:fix|FIX|(.*)) message(?: of type "(.*)")?$/ do |begin_string, msg_type|
|
27
|
+
FIXSpec::Builder.message = quickfix.Message.new
|
28
|
+
|
29
|
+
unless begin_string.nil?
|
30
|
+
steps %{And I set the FIX message at "BeginString" to "#{begin_string}"}
|
31
|
+
end
|
32
|
+
|
33
|
+
unless msg_type.nil?
|
34
|
+
steps %{And I set the FIX message at "MsgType" to "#{msg_type}"}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Given /^I create the following (?:fix|FIX|(.*)) message(?: of type "(.*)")?:$/ do |begin_string, msg_type, table|
|
39
|
+
FIXSpec::Builder.message = quickfix.Message.new
|
40
|
+
|
41
|
+
unless begin_string.nil?
|
42
|
+
steps %{And I set the FIX message at "BeginString" to "#{begin_string}"}
|
43
|
+
end
|
44
|
+
|
45
|
+
unless msg_type.nil?
|
46
|
+
steps %{And I set the FIX message at "MsgType" to "#{msg_type}"}
|
47
|
+
end
|
48
|
+
|
49
|
+
table.raw.each do |tag, value|
|
50
|
+
steps %{And I set the FIX message at "#{tag}" to #{value}}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
Given /^I set the (?:FIX|fix) message at(?: tag)? "(.*?)" to (".*"|\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?|\[.*\]|%?\{.*\}|true|false|null)$/ do |fieldName, fieldValue|
|
57
|
+
FIXSpec::Builder.message.should_not be_nil
|
58
|
+
|
59
|
+
#kill quotes
|
60
|
+
if fieldValue.match(/\"(.*)\"/)
|
61
|
+
fieldValue=$1
|
62
|
+
end
|
63
|
+
|
64
|
+
tag = -1
|
65
|
+
if !FIXSpec::data_dictionary.nil?
|
66
|
+
tag = FIXSpec::data_dictionary.getFieldTag(fieldName)
|
67
|
+
|
68
|
+
msg = FIXSpec::Builder.message
|
69
|
+
|
70
|
+
if FIXSpec::data_dictionary.is_header_field(tag)
|
71
|
+
msg = msg.get_header
|
72
|
+
elsif FIXSpec::data_dictionary.is_trailer_field(tag)
|
73
|
+
msg = msg.get_trailer
|
74
|
+
end
|
75
|
+
|
76
|
+
if tag == -1 then
|
77
|
+
msg.setString(fieldName, fieldValue)
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
case FIXSpec::data_dictionary.get_field_type_enum(tag).get_name
|
82
|
+
when "INT","DAYOFMONTH" then
|
83
|
+
msg.setInt(tag, fieldValue.to_i)
|
84
|
+
when "PRICE","FLOAT","QTY" then
|
85
|
+
msg.setDouble(tag, fieldValue.to_f)
|
86
|
+
when "BOOLEAN" then
|
87
|
+
msg.setBoolean(tag, fieldValue == "true")
|
88
|
+
else
|
89
|
+
msg.setString(tag, fieldValue)
|
90
|
+
end
|
91
|
+
|
92
|
+
else
|
93
|
+
tag = fieldName.to_i
|
94
|
+
FIXSpec::Builder.message.setString(tag, fieldValue)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module FIXSpec
|
2
|
+
module Configuration
|
3
|
+
|
4
|
+
def data_dictionary=(dd)
|
5
|
+
@data_dictionary=dd
|
6
|
+
end
|
7
|
+
|
8
|
+
def data_dictionary
|
9
|
+
return @data_dictionary
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
instance_variables.each{|ivar| remove_instance_variable(ivar)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.expand_path("../../fix_spec", __FILE__)
|
2
|
+
require 'cuke_mem'
|
3
|
+
|
4
|
+
World(JsonSpec::Helpers, JsonSpec::Matchers)
|
5
|
+
World(FIXSpec::Matchers)
|
6
|
+
|
7
|
+
Then /^the FIX message type should( not)? be "(.*?)"$/ do |negative, msg_type|
|
8
|
+
|
9
|
+
unless FIXSpec::data_dictionary.nil?
|
10
|
+
msg_type = FIXSpec::data_dictionary.get_msg_type(msg_type)
|
11
|
+
end
|
12
|
+
|
13
|
+
if negative
|
14
|
+
last_fix.header.get_string(35).should_not == msg_type
|
15
|
+
else
|
16
|
+
last_fix.header.get_string(35).should == msg_type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /^the (?:fix|FIX)(?: message)? at(?: tag)? "(.*?)" should( not)? be (".*"|\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?|\[.*\]|%?\{.*\}|true|false|null)$/ do |tag, negative, exp_value|
|
21
|
+
if negative
|
22
|
+
last_fix.should_not be_fix_eql(CukeMem.remember(exp_value)).at_path(tag)
|
23
|
+
else
|
24
|
+
last_fix.should be_fix_eql(CukeMem.remember(exp_value)).at_path(tag)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Then /^the (?:fix|FIX)(?: message)?(?: at(?: tag)? "(.*?)")? should( not)? be:$/ do |tag, negative, exp_value|
|
29
|
+
|
30
|
+
# raw fix
|
31
|
+
if tag.nil? and not exp_value.match(/{*}/)
|
32
|
+
require 'fix_spec/builder'
|
33
|
+
factory = quickfix.DefaultMessageFactory.new
|
34
|
+
exp_message = FIXSpec::Builder.message = quickfix.MessageUtils.parse(factory, nil, FIXSpec::Helpers.fixify_string(exp_value) )
|
35
|
+
exp_value = FIXSpec::Helpers.message_to_unordered_json(exp_message)
|
36
|
+
end
|
37
|
+
|
38
|
+
if negative
|
39
|
+
last_fix.should_not be_fix_eql(CukeMem.remember(exp_value)).at_path(tag)
|
40
|
+
else
|
41
|
+
last_fix.should be_fix_eql(CukeMem.remember(exp_value)).at_path(tag)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
Then /^the (?:fix|FIX)(?: message)? should( not)? be (".*"|\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?|\[.*\]|%?\{.*\}|true|false|null)$/ do |negative, exp_value|
|
47
|
+
if negative
|
48
|
+
last_fix.should_not be_fix_eql(CukeMem.remember(exp_value))
|
49
|
+
else
|
50
|
+
last_fix.should be_fix_eql(CukeMem.remember(exp_value))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Then /^the (?:FIX|fix)(?: message)?(?: at "(.*)")? should have the following:$/ do |base, table|
|
55
|
+
table.raw.each do |path, value|
|
56
|
+
path = [base, path].compact.join("/")
|
57
|
+
|
58
|
+
if value
|
59
|
+
step %(the fix at "#{path}" should be:), value
|
60
|
+
else
|
61
|
+
step %(the fix should have "#{path}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Then /^the (?:fix|FIX)(?: message)? should( not)? have(?: tag)? "(.*)"$/ do |negative, path|
|
67
|
+
if negative
|
68
|
+
last_fix.should_not have_fix_path(path)
|
69
|
+
else
|
70
|
+
last_fix.should have_fix_path(path)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
When /^(?:I )?keep the (?:fix|FIX)(?: message)?(?: at(?: tag)? "(.*)")? as "(.*)"$/ do |path, key|
|
75
|
+
CukeMem.memorize(key, normalize_json(FIXSpec::Helpers.message_to_unordered_json(last_fix), path))
|
76
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
module FIXSpec
|
3
|
+
module Helpers
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def fixify_string msg_str
|
7
|
+
message = msg_str.strip
|
8
|
+
begin_string = message.slice!(/^8=.*?[\001]/)
|
9
|
+
|
10
|
+
raise "Message '#{msg_str}' has no begin string" if begin_string.nil?
|
11
|
+
|
12
|
+
#nobody's perfect, allow for missing trailing soh
|
13
|
+
message+="\001" unless message.end_with?("\001")
|
14
|
+
|
15
|
+
#auto-calc length and checksum, ignore any existing
|
16
|
+
message.slice!(/^9=\d+\001/)
|
17
|
+
message.gsub!(/10=\d\d\d\001$/,"")
|
18
|
+
|
19
|
+
length = "9=#{message.length}\001"
|
20
|
+
checksum = "10=%03d\001" % (begin_string + length + message).sum(8)
|
21
|
+
return (begin_string + length + message + checksum)
|
22
|
+
end
|
23
|
+
|
24
|
+
def message_to_unordered_json msg
|
25
|
+
MultiJson.encode message_to_hash(msg)
|
26
|
+
end
|
27
|
+
|
28
|
+
def message_to_hash msg
|
29
|
+
header = msg.get_header
|
30
|
+
msg_type = extract_message_type header
|
31
|
+
msg_hash = field_map_to_hash header
|
32
|
+
msg_hash.merge! field_map_to_hash msg, FIXSpec::data_dictionary, msg_type
|
33
|
+
msg_hash.merge field_map_to_hash msg.get_trailer
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_message_type msg
|
37
|
+
if msg.is_set_field( quickfix::field::MsgType::FIELD)
|
38
|
+
msg.get_field(quickfix::field::MsgType::FIELD).get_value
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_field_type tag, data_dictionaries = []
|
45
|
+
data_dictionaries.each do |dd|
|
46
|
+
enum = dd.get_field_type_enum(tag)
|
47
|
+
value = enum.get_name unless enum.nil?
|
48
|
+
return value unless value.nil? or value.eql?("")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_field_name tag, data_dictionaries = []
|
53
|
+
data_dictionaries.each do |dd|
|
54
|
+
value = dd.get_field_name(tag)
|
55
|
+
return value unless value.nil? or value.eql?("")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def field_map_to_hash field_map, data_dictionary = FIXSpec::data_dictionary, msg_type = nil, all_dictionaries = [ data_dictionary ]
|
60
|
+
hash = {}
|
61
|
+
iter = field_map.iterator
|
62
|
+
while iter.has_next
|
63
|
+
field = iter.next()
|
64
|
+
tag = field.get_tag
|
65
|
+
value = field.get_value
|
66
|
+
|
67
|
+
if !data_dictionary.nil?
|
68
|
+
if !msg_type.nil? and data_dictionary.is_group(msg_type, tag)
|
69
|
+
group_dd = data_dictionary.get_group(msg_type,tag).get_data_dictionary
|
70
|
+
groups = []
|
71
|
+
for i in 1..value.to_i
|
72
|
+
groups << field_map_to_hash( field_map.get_group(i,tag), group_dd, msg_type,Array.new(all_dictionaries) << group_dd )
|
73
|
+
end
|
74
|
+
value = groups
|
75
|
+
elsif data_dictionary.is_field(tag)
|
76
|
+
value = case find_field_type(tag,all_dictionaries)
|
77
|
+
when "INT","DAYOFMONTH" then value.to_i
|
78
|
+
when "PRICE","FLOAT","QTY" then value.to_f
|
79
|
+
when "BOOLEAN" then value == "Y"
|
80
|
+
when "NUMINGROUP" then value = field_map.to_hash(value)
|
81
|
+
else
|
82
|
+
value_name = data_dictionary.get_value_name(tag, value)
|
83
|
+
unless value_name.nil?
|
84
|
+
value_name
|
85
|
+
else
|
86
|
+
value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
tag = find_field_name(tag,all_dictionaries)
|
91
|
+
end
|
92
|
+
hash[tag] = value
|
93
|
+
end
|
94
|
+
|
95
|
+
return hash
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FIXSpec
|
2
|
+
module Matchers
|
3
|
+
class BeFIXEql
|
4
|
+
include FIXSpec::Helpers
|
5
|
+
|
6
|
+
def initialize(expected_fix_as_json = nil)
|
7
|
+
@json_matcher= JsonSpec::Matchers::BeJsonEql.new expected_fix_as_json
|
8
|
+
end
|
9
|
+
|
10
|
+
def diffable?
|
11
|
+
@json_matcher.diffable?
|
12
|
+
end
|
13
|
+
|
14
|
+
def actual
|
15
|
+
@json_matcher.actual
|
16
|
+
end
|
17
|
+
|
18
|
+
def expected
|
19
|
+
@json_matcher.expected
|
20
|
+
end
|
21
|
+
|
22
|
+
def at_path(path)
|
23
|
+
@json_matcher.at_path path
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(fix_message)
|
28
|
+
fix_json = message_to_unordered_json(fix_message)
|
29
|
+
@json_matcher.matches? fix_json
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure_message_for_should
|
33
|
+
@json_matcher.message_with_path("Expected equivalent FIX")
|
34
|
+
end
|
35
|
+
|
36
|
+
def negative_failure_message
|
37
|
+
@json_matcher.message_with_path("Expected inequivalent FIX")
|
38
|
+
end
|
39
|
+
|
40
|
+
def description
|
41
|
+
@json_matcher.message_with_path("equal FIX")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FIXSpec
|
2
|
+
module Matchers
|
3
|
+
class HaveFIXPath
|
4
|
+
attr_reader :path
|
5
|
+
def initialize path
|
6
|
+
@path = path
|
7
|
+
@json_matcher = JsonSpec::Matchers::HaveJsonPath.new path
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches? fix_message
|
11
|
+
fix_json = FIXSpec::Helpers.message_to_unordered_json(fix_message)
|
12
|
+
|
13
|
+
@json_matcher.matches? fix_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message_for_should
|
17
|
+
%(Expected FIX path "#{@path}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_for_should_not
|
21
|
+
%(Expected no FIX path "#{@path}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
%(have Protobuf path "#{@path}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fix_spec/matchers/be_fix_eql'
|
2
|
+
require 'fix_spec/matchers/have_fix_path'
|
3
|
+
|
4
|
+
module FIXSpec
|
5
|
+
module Matchers
|
6
|
+
def be_fix_eql(expected=nil)
|
7
|
+
FIXSpec::Matchers::BeFIXEql.new(expected)
|
8
|
+
end
|
9
|
+
|
10
|
+
def have_fix_path(path)
|
11
|
+
FIXSpec::Matchers::HaveFIXPath.new(path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.include(FIXSpec::Matchers)
|
18
|
+
end
|
data/lib/fix_spec.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FIXSpec::Helpers do
|
4
|
+
include described_class
|
5
|
+
|
6
|
+
describe "fixify_string" do
|
7
|
+
it "can calculate checksum" do
|
8
|
+
msg="8=FIX.4.235=849=ITG56=SILO205=4"
|
9
|
+
fix_msg = fixify_string msg
|
10
|
+
fix_msg.end_with?("10=084").should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can ignore existing checksum" do
|
14
|
+
msg="8=FIX.4.235=849=ITG56=SILO205=410=045"
|
15
|
+
fix_msg = fixify_string msg
|
16
|
+
fix_msg.end_with?("10=084").should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can calculate length" do
|
20
|
+
msg="8=FIX.4.235=849=ITG56=SILO205=410=045"
|
21
|
+
fix_msg = fixify_string msg
|
22
|
+
fix_msg.start_with?("8=FIX.4.2\0019=26\001").should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can ignore existing length" do
|
26
|
+
msg="8=FIX.4.29=4535=849=ITG56=SILO205=410=045"
|
27
|
+
fix_msg = fixify_string msg
|
28
|
+
fix_msg.start_with?("8=FIX.4.2\0019=26\001").should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "will strip out spaces" do
|
33
|
+
msg=" 8=FIX.4.235=849=ITG56=SILO205=4 "
|
34
|
+
fixify_string(msg).should ==("8=FIX.4.2\0019=26\00135=8\00149=ITG\00156=SILO\001205=4\00110=084\001")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should be generous with missing last soh" do
|
38
|
+
msg="8=FIX.4.235=849=ITG56=SILO205=4"
|
39
|
+
fixify_string(msg).should ==("8=FIX.4.2\0019=26\00135=8\00149=ITG\00156=SILO\001205=4\00110=084\001")
|
40
|
+
|
41
|
+
msg="8=FIX.4.235=849=ITG56=SILO205=410=045"
|
42
|
+
fixify_string(msg).should ==("8=FIX.4.2\0019=26\00135=8\00149=ITG\00156=SILO\001205=4\00110=084\001")
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "message-to-hash functions" do
|
49
|
+
let(:order) {
|
50
|
+
ord = quickfix.fix42.NewOrderSingle.new
|
51
|
+
ord.header.set(quickfix.field.TargetCompID.new "target")
|
52
|
+
ord.header.set(quickfix.field.SenderCompID.new "sender")
|
53
|
+
|
54
|
+
ord.set(quickfix.field.Account.new "account")
|
55
|
+
ord.set(quickfix.field.OrderQty.new(100.0))
|
56
|
+
ord.set(quickfix.field.Price.new(75.0))
|
57
|
+
ord
|
58
|
+
}
|
59
|
+
|
60
|
+
context "field_map_to_hash" do
|
61
|
+
let(:hash) { field_map_to_hash order.get_header }
|
62
|
+
|
63
|
+
it "should have unordered tags" do
|
64
|
+
hash[8].should=="FIX.4.2"
|
65
|
+
hash[35].should=="D"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "message_to_hash" do
|
70
|
+
let(:hash) { message_to_hash order }
|
71
|
+
|
72
|
+
it "should have unordered tags from all message parts" do
|
73
|
+
hash[8].should=="FIX.4.2"
|
74
|
+
hash[35].should=="D"
|
75
|
+
hash[1].should=="account"
|
76
|
+
hash[44].should == "75"
|
77
|
+
hash[38].should == "100"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "get-message-type function" do
|
83
|
+
let(:order) {
|
84
|
+
ord = quickfix.fix42.NewOrderSingle.new
|
85
|
+
ord
|
86
|
+
}
|
87
|
+
let(:generic_message) {
|
88
|
+
msg = quickfix.Message.new
|
89
|
+
msg.setString( 8, "FIX.4.2" )
|
90
|
+
msg.setInt( 9, 26 )
|
91
|
+
msg.setString( 49, "ITG" )
|
92
|
+
msg.setString( 56, "SILO" )
|
93
|
+
msg.setInt( 205, 4 )
|
94
|
+
msg.setInt( 10, 84 )
|
95
|
+
msg
|
96
|
+
}
|
97
|
+
|
98
|
+
context "fix message with msgType field set" do
|
99
|
+
let(:msg_type) { extract_message_type( order.get_header ) }
|
100
|
+
|
101
|
+
it "should return the value of the msgType tag" do
|
102
|
+
msg_type.should == "D"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "fix message from raw string with no message type" do
|
107
|
+
let(:msg_type) { extract_message_type( generic_message.get_header ) }
|
108
|
+
|
109
|
+
it "should not error out and return nil" do
|
110
|
+
msg_type.should == nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'fix_spec'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
13
|
+
|