fix_spec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|