slacker 0.0.1
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/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +3 -0
- data/README.markdown +16 -0
- data/Rakefile +11 -0
- data/bin/slacker +28 -0
- data/lib/slacker.rb +138 -0
- data/lib/slacker/application.rb +201 -0
- data/lib/slacker/command_line_formatter.rb +59 -0
- data/lib/slacker/configuration.rb +59 -0
- data/lib/slacker/formatter.rb +19 -0
- data/lib/slacker/query_result_matcher.rb +169 -0
- data/lib/slacker/rspec_ext.rb +69 -0
- data/lib/slacker/rspec_monkey.rb +7 -0
- data/lib/slacker/sql.rb +39 -0
- data/lib/slacker/string_helper.rb +17 -0
- data/lib/slacker/version.rb +3 -0
- data/slacker.gemspec +23 -0
- data/spec/application_spec.rb +14 -0
- data/spec/query_result_matcher_spec.rb +267 -0
- data/spec/rspec_ext_spec.rb +127 -0
- data/spec/slacker_spec.rb +60 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/test_files/matcher/completely_blank.csv +0 -0
- data/spec/test_files/matcher/no_rows.csv +1 -0
- data/spec/test_files/matcher/test_1.csv +3 -0
- data/spec/test_files/test_slacker_project/data/test_1.csv +3 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_1.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_2.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/example_1/helper_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/HELPER_4.SQL +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_1.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_3.sql +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/helper_3.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/helpers/text_file_1.txt +1 -0
- data/spec/test_files/test_slacker_project/sql/multi_nested.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nest/example_1/helper_1.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nest/nested_1.sql.erb +2 -0
- data/spec/test_files/test_slacker_project/sql/nest/nested_2.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nested.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/nested_with_params.sql.erb +1 -0
- data/spec/test_files/test_slacker_project/sql/no_params.sql.erb +3 -0
- data/spec/test_files/test_slacker_project/sql/params.sql.erb +2 -0
- data/spec/test_files/test_slacker_project/sql/test_1.sql +1 -0
- metadata +114 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Slacker
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :base_dir, :error_stream, :output_stream, :formatter, :db_server, :db_name, :db_user, :db_password, :console_enabled
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@console_enabled = true
|
7
|
+
@base_dir = Dir.pwd
|
8
|
+
@error_stream = nil
|
9
|
+
@output_stream = nil
|
10
|
+
@rspec_args = nil
|
11
|
+
@formatter = nil
|
12
|
+
@db_server = nil
|
13
|
+
@db_name = nil
|
14
|
+
@db_user = nil
|
15
|
+
@db_password = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def expand_path(path)
|
19
|
+
File.expand_path("#{@base_dir}/#{path}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def console_enabled
|
23
|
+
@console_enabled
|
24
|
+
end
|
25
|
+
|
26
|
+
def console_enabled=(value)
|
27
|
+
@console_enabled = value
|
28
|
+
if @console_enabled
|
29
|
+
@error_stream = $stderr
|
30
|
+
@output_stream = $stdout
|
31
|
+
@rspec_args = ARGV
|
32
|
+
else
|
33
|
+
@error_stream = nil
|
34
|
+
@output_stream = nil
|
35
|
+
@rspec_args = []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def rspec_args
|
40
|
+
if @rspec_args.nil? || @rspec_args.empty?
|
41
|
+
Dir.glob(expand_path("spec/**/*.rb"))
|
42
|
+
else
|
43
|
+
@rspec_args
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def rspec_args=(value)
|
48
|
+
@rspec_args = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def dsn_string
|
52
|
+
"Driver={SQL Server};Server=#{@db_server};Database=#{@db_name};Uid=#{@db_user};Pwd=#{@db_password}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def db_config
|
56
|
+
{:adapter => 'sqlserver', :mode => 'odbc', :dsn => dsn_string}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
|
3
|
+
module Slacker
|
4
|
+
module Formatter
|
5
|
+
def example_failure_text(example)
|
6
|
+
text = ''
|
7
|
+
exception = example.execution_result[:exception]
|
8
|
+
text << "Failure/Error: #{read_failed_line(exception, example).strip}\n"
|
9
|
+
text << "#{long_padding}#{exception.class.name << ":"}\n" unless exception.class.name =~ /RSpec/
|
10
|
+
exception.message.split("\n").each { |line| text << "#{line}\n" }
|
11
|
+
|
12
|
+
format_backtrace(example.execution_result[:exception].backtrace, example).each do |backtrace_info|
|
13
|
+
text << "# #{backtrace_info}\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Slacker
|
4
|
+
class QueryResultMatcher
|
5
|
+
def initialize(golden_master)
|
6
|
+
@golden_master = golden_master
|
7
|
+
@failure_message = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(subject)
|
11
|
+
does_match = false
|
12
|
+
|
13
|
+
subject = normalize_query_result_subject(subject)
|
14
|
+
|
15
|
+
catch :no_match do
|
16
|
+
test_type_match(subject)
|
17
|
+
test_value_match(subject)
|
18
|
+
does_match = true
|
19
|
+
end
|
20
|
+
|
21
|
+
does_match
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message_for_should
|
25
|
+
@failure_message
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
#test if the subject type is consistent with the golden master
|
31
|
+
def test_type_match(subject)
|
32
|
+
if !is_well_formed_query_result?(subject)
|
33
|
+
throw_no_match "Can perform query matches only against a well formed query result subject"
|
34
|
+
end
|
35
|
+
|
36
|
+
if (@golden_master.kind_of? Array) && !is_well_formed_query_result?(@golden_master)
|
37
|
+
throw_no_match "Cannot match against a non-well formed golden master array"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_value_match(subject)
|
42
|
+
case @golden_master
|
43
|
+
when CSV::Table
|
44
|
+
test_csv_table_match(subject)
|
45
|
+
when Array
|
46
|
+
test_array_match(subject)
|
47
|
+
else
|
48
|
+
test_single_value_match(subject)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Compare the golden master CSV table with the subject query result
|
53
|
+
def test_csv_table_match(subject)
|
54
|
+
# Compare the fields
|
55
|
+
if !subject.empty?
|
56
|
+
subject_fields = subject[0].keys
|
57
|
+
master_fields = @golden_master.headers
|
58
|
+
|
59
|
+
if subject_fields.count != master_fields.count
|
60
|
+
throw_no_match "Expected #{master_fields.count} field(s), got #{subject_fields.count}"
|
61
|
+
end
|
62
|
+
|
63
|
+
master_fields.each_with_index do |column, index|
|
64
|
+
if column != subject_fields[index]
|
65
|
+
throw_no_match "Expected field \"#{column}\", got field \"#{subject_fields[index]}\""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Compare the number of records
|
71
|
+
subject_record_count = subject.count
|
72
|
+
master_record_count = @golden_master.inject(0){|count| count += 1}
|
73
|
+
if subject_record_count != master_record_count
|
74
|
+
throw_no_match "Expected #{master_record_count} record(s), got #{subject_record_count}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Compare the values of the golden master with the subject
|
78
|
+
current_row = 0
|
79
|
+
@golden_master.each do |row|
|
80
|
+
row.each do |field, master_string|
|
81
|
+
subject_value = subject[current_row][field]
|
82
|
+
if !match_values?(master_string, subject_value)
|
83
|
+
throw_no_match "Field \"#{field}\", Record #{current_row + 1}: Expected value #{master_string.nil? ? '<NULL>' : "\"#{master_string}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
current_row += 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def match_types?(master_val, subject_val)
|
91
|
+
subject_val.kind_of? master_val.class
|
92
|
+
end
|
93
|
+
|
94
|
+
def match_values?(master_val, subject_val)
|
95
|
+
if master_val.nil?
|
96
|
+
master_val == subject_val
|
97
|
+
elsif subject_val.kind_of?(Time) && master_val.kind_of?(String)
|
98
|
+
Time.parse(master_val) == subject_val
|
99
|
+
else
|
100
|
+
subject_val.to_s == master_val.to_s
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_array_match(subject)
|
105
|
+
# Compare the fields
|
106
|
+
if !(subject.empty? || @golden_master.empty?)
|
107
|
+
subject_fields = subject[0].keys
|
108
|
+
master_fields = @golden_master[0].keys
|
109
|
+
|
110
|
+
if subject_fields.count != master_fields.count
|
111
|
+
throw_no_match "Expected #{master_fields.count} field(s), got #{subject_fields.count}"
|
112
|
+
end
|
113
|
+
|
114
|
+
master_fields.each_with_index do |column, index|
|
115
|
+
if column != subject_fields[index]
|
116
|
+
throw_no_match "Expected field \"#{column}\", got field \"#{subject_fields[index]}\""
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Compare the number of records
|
122
|
+
subject_record_count = subject.count
|
123
|
+
master_record_count = @golden_master.count
|
124
|
+
if subject_record_count != master_record_count
|
125
|
+
throw_no_match "Expected #{master_record_count} record(s), got #{subject_record_count}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Compare the values of the golden master with the subject
|
129
|
+
current_row = 0
|
130
|
+
@golden_master.each do |row|
|
131
|
+
row.each do |field, master_value|
|
132
|
+
subject_value = subject[current_row][field]
|
133
|
+
if !match_values?(master_value, subject_value)
|
134
|
+
throw_no_match "Field \"#{field}\", Record #{current_row + 1}: Expected value #{master_value.nil? ? '<NULL>' : "\"#{master_value}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
current_row += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def is_well_formed_query_result?(arr)
|
142
|
+
return false unless arr.kind_of? Array
|
143
|
+
header =[]
|
144
|
+
arr.find{|row| !row.kind_of?(Hash) || (header = header.empty? ? row.keys : header) != row.keys || row.keys.empty?}.nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_single_value_match(subject)
|
148
|
+
subject_value = subject[0].values[0]
|
149
|
+
subject_field = subject[0].keys[0]
|
150
|
+
if !match_types?(@golden_master, subject_value)
|
151
|
+
throw_no_match "Field \"#{subject_field}\", Record 1: Expected type \"#{@golden_master.class}\", got \"#{subject_value.class}\""
|
152
|
+
end
|
153
|
+
|
154
|
+
if !match_values?(@golden_master, subject_value)
|
155
|
+
throw_no_match "Field \"#{subject_field}\", Record 1: Expected value #{@golden_master.nil? ? '<NULL>' : "\"#{@golden_master}\""}, got #{subject_value.nil? ? '<NULL>' : "\"#{subject_value}\""}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# In case of a multi-resultset subject, extract the first result set
|
160
|
+
def normalize_query_result_subject(subject)
|
161
|
+
subject.kind_of?(Array) && !subject.empty? && is_well_formed_query_result?(subject[0]) ? subject[0] : subject
|
162
|
+
end
|
163
|
+
|
164
|
+
def throw_no_match(message)
|
165
|
+
@failure_message = message
|
166
|
+
throw :no_match
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'slacker/query_result_matcher'
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Slacker
|
6
|
+
module RSpecExt
|
7
|
+
def query(query_string, options = {}, log_name = nil)
|
8
|
+
log_name ||= Slacker.construct_log_name('query', query_string, options)
|
9
|
+
sql = Slacker.sql_from_query_string(query_string, options)
|
10
|
+
@results = Slacker.query_script(example, sql, log_name)
|
11
|
+
if block_given?
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
@results
|
15
|
+
end
|
16
|
+
|
17
|
+
def result(index = 1, options = {})
|
18
|
+
# Flatten the result in case we're getting a multi-result-set response
|
19
|
+
res = case !@results.empty? && @results[0].kind_of?(Array)
|
20
|
+
when true
|
21
|
+
raise "The query result contains only #{@results.count} result set(s)" unless @results.count >= index
|
22
|
+
@results[index - 1]
|
23
|
+
else
|
24
|
+
raise "The query result contains a single result set" unless index == 1
|
25
|
+
@results
|
26
|
+
end
|
27
|
+
|
28
|
+
#Optionally extract the record and or the field
|
29
|
+
if options[:record] != nil
|
30
|
+
raise "The result set contains only #{res.count} record(s)" unless res.count >= options[:record]
|
31
|
+
res = res[options[:record] - 1]
|
32
|
+
if options[:field] != nil
|
33
|
+
raise "The result set does not contain field \"#{options[:field]}\"" unless res[options[:field]] != nil
|
34
|
+
res = res[options[:field]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get a matcher which will compare the query results to a golden master
|
42
|
+
def match(golden_master)
|
43
|
+
QueryResultMatcher.new(Slacker.filter_golden_master(golden_master))
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_csv(csv_file_or_object, table_name, log_name = nil)
|
47
|
+
log_name ||= "load_csv '#{csv_file_or_object.kind_of?(CSV::Table) ? 'CSV Object' : csv_file_or_object }', 'table_name'"
|
48
|
+
csv_object = case csv_file_or_object
|
49
|
+
when String then Slacker.get_csv(csv_file_or_object)
|
50
|
+
when CSV::Table then csv_file_or_object
|
51
|
+
when Array then Slacker.hash_array_to_csv(csv_file_or_object)
|
52
|
+
end
|
53
|
+
|
54
|
+
Slacker.load_csv(example, csv_object, table_name, log_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def csv(csv_file)
|
58
|
+
Slacker.get_csv(csv_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
def sql
|
62
|
+
Slacker.sql(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def yes?(val)
|
66
|
+
val != nil && val.downcase == 'yes'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/slacker/sql.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'slacker/rspec_ext'
|
3
|
+
|
4
|
+
module Slacker
|
5
|
+
class Sql < BasicObject
|
6
|
+
attr_accessor :base_folder, :rspec_ext
|
7
|
+
|
8
|
+
def initialize(base_folder, rspec_ext)
|
9
|
+
@base_folder = base_folder
|
10
|
+
@rspec_ext = rspec_ext
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_name, *params, &block)
|
14
|
+
::Kernel.raise "Slacker::Sql.rspec_ext not initialized" if rspec_ext.nil?
|
15
|
+
::Kernel.raise "Missing folder #{base_folder}" if !::File.directory?(base_folder)
|
16
|
+
|
17
|
+
method_name = method_name.to_s
|
18
|
+
|
19
|
+
if ::File.directory?(::File.join(base_folder, method_name))
|
20
|
+
::Slacker::Sql.new(::File.join(base_folder, method_name), rspec_ext)
|
21
|
+
else
|
22
|
+
sql_file = ::Slacker.sql_file_from_method_name(base_folder, method_name)
|
23
|
+
case sql_file
|
24
|
+
when nil
|
25
|
+
::Kernel.raise "No SQL file found corresponding to method '#{method_name}' in folder #{base_folder}"
|
26
|
+
else
|
27
|
+
rspec_ext.query sql_file, *params, &block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to?(method_name)
|
33
|
+
method_name = method_name.to_s
|
34
|
+
::Kernel.raise "Slacker::Sql.rspec_ext not initialized" if rspec_ext.nil?
|
35
|
+
::File.directory?(::File.join(base_folder, method_name)) ||
|
36
|
+
!(::Slacker.sql_file_from_method_name(base_folder, method_name).nil?)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Slacker
|
2
|
+
module StringHelper
|
3
|
+
class << self
|
4
|
+
def camelize(lower_case_and_underscored_word)
|
5
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
6
|
+
end
|
7
|
+
|
8
|
+
def constantize(camel_cased_word)
|
9
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
|
10
|
+
raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
|
11
|
+
end
|
12
|
+
|
13
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/slacker.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "slacker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "slacker"
|
7
|
+
s.version = Slacker::VERSION
|
8
|
+
s.authors = ["Vassil Kovatchev"]
|
9
|
+
s.email = ["vassil.kovatchev@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/vassilvk/slacker"
|
11
|
+
s.summary = %q{Behavior Driven Development for SQL Server}
|
12
|
+
s.description = %q{RSpec-based framework for developing automated tests for SQL Server programmable objects such as stored procedures and scalar/table functions}
|
13
|
+
|
14
|
+
s.rubyforge_project = "slacker"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'ruby-odbc', '=0.99994'
|
22
|
+
s.add_dependency 'rspec', '=2.5.0'
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
|
3
|
+
describe Slacker::Application do
|
4
|
+
it 'responds to run' do
|
5
|
+
Slacker.application.should respond_to(:run)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'responds to target_folder_structure with the correct list of folders' do
|
9
|
+
app = Slacker.application
|
10
|
+
app.should respond_to(:target_folder_structure)
|
11
|
+
folder_struct = app.target_folder_structure
|
12
|
+
folder_struct.should == ['data', 'debug', 'debug/passed_examples', 'debug/failed_examples', 'sql', 'spec', 'lib', 'lib/helpers']
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'slacker'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
describe Slacker::QueryResultMatcher do
|
6
|
+
def deep_copy(obj)
|
7
|
+
Marshal.load(Marshal.dump(obj))
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@subject = [{'Field 1' => 12, 'Field_2' => nil, 'b' => ''},
|
12
|
+
{'Field 1' => 'test string', 'Field_2' => Time.parse('1/1/2011'), 'b' => 8.9}]
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_examples_for 'table-based matcher' do
|
16
|
+
it 'correctly rejects a non-matching query result based on wrong columns' do
|
17
|
+
one_column_too_few = deep_copy(@subject).each{|row| row.delete('b')}
|
18
|
+
one_column_too_many = deep_copy(@subject).each{|row| row['new column'] = 'val 1'}
|
19
|
+
wrong_column_name = deep_copy(@subject).each{|row| row.delete('Field_2'); row['Field_x'] = 'val x'}
|
20
|
+
|
21
|
+
@matcher.matches?(one_column_too_few).should be_false
|
22
|
+
@matcher.failure_message_for_should.should == 'Expected 3 field(s), got 2'
|
23
|
+
|
24
|
+
@matcher.matches?(one_column_too_many).should be_false
|
25
|
+
@matcher.failure_message_for_should.should == 'Expected 3 field(s), got 4'
|
26
|
+
|
27
|
+
@matcher.matches?(wrong_column_name).should be_false
|
28
|
+
@matcher.failure_message_for_should.should == 'Expected field "Field_2", got field "b"'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'correctly rejects a non-matching query result based on number of records' do
|
32
|
+
one_row_too_few = deep_copy(@subject)
|
33
|
+
one_row_too_few.shift
|
34
|
+
one_row_too_many = deep_copy(@subject) << @subject[0]
|
35
|
+
empty_query_result = []
|
36
|
+
|
37
|
+
@matcher.matches?(one_row_too_few).should be_false
|
38
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 1'
|
39
|
+
|
40
|
+
@matcher.matches?(one_row_too_many).should be_false
|
41
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 3'
|
42
|
+
|
43
|
+
@matcher.matches?(empty_query_result).should be_false
|
44
|
+
@matcher.failure_message_for_should.should == 'Expected 2 record(s), got 0'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'correctly rejects a non-matching query result based on a value' do
|
48
|
+
wrong_int = deep_copy(@subject)
|
49
|
+
wrong_int[0]['Field 1'] = 14
|
50
|
+
|
51
|
+
wrong_int_type = deep_copy(@subject)
|
52
|
+
wrong_int_type[1]['Field 1'] = 14
|
53
|
+
|
54
|
+
wrong_nil = deep_copy(@subject)
|
55
|
+
wrong_nil[0]['Field 1'] = nil
|
56
|
+
|
57
|
+
wrong_non_nil = deep_copy(@subject)
|
58
|
+
wrong_non_nil[0]['Field_2'] = 'whatever'
|
59
|
+
|
60
|
+
misplaced_fields = deep_copy(@subject)
|
61
|
+
misplaced_fields[0].delete('Field 1')
|
62
|
+
misplaced_fields[1].delete('Field 1')
|
63
|
+
misplaced_fields[0]['Field 1'] = 12
|
64
|
+
misplaced_fields[1]['Field 1'] = 'test string'
|
65
|
+
|
66
|
+
@matcher.matches?(wrong_int).should be_false
|
67
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 1: Expected value "12", got "14"'
|
68
|
+
|
69
|
+
|
70
|
+
@matcher.matches?(wrong_int_type).should be_false
|
71
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 2: Expected value "test string", got "14"'
|
72
|
+
|
73
|
+
@matcher.matches?(wrong_nil).should be_false
|
74
|
+
@matcher.failure_message_for_should.should == 'Field "Field 1", Record 1: Expected value "12", got <NULL>'
|
75
|
+
|
76
|
+
@matcher.matches?(wrong_non_nil).should be_false
|
77
|
+
@matcher.failure_message_for_should.should == 'Field "Field_2", Record 1: Expected value <NULL>, got "whatever"'
|
78
|
+
|
79
|
+
@matcher.matches?(misplaced_fields).should be_false
|
80
|
+
@matcher.failure_message_for_should.should == 'Expected field "Field 1", got field "Field_2"'
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'correctly compares with a query result' do
|
84
|
+
@matcher.matches?(@subject).should be_true(@matcher.failure_message_for_should)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'only accepts well formed query result subjects' do
|
88
|
+
expected_message = /Can perform query matches only against a well formed query result subject/
|
89
|
+
@matcher.matches?(1).should be_false
|
90
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
91
|
+
|
92
|
+
@matcher.matches?('test string').should be_false
|
93
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
94
|
+
|
95
|
+
@matcher.matches?(1.1).should be_false
|
96
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
97
|
+
|
98
|
+
@matcher.matches?(Time.now).should be_false
|
99
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
100
|
+
|
101
|
+
@matcher.matches?(nil).should be_false
|
102
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
103
|
+
|
104
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x'}]).should be_false
|
105
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
106
|
+
|
107
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f3 => 'y'}]).should be_false
|
108
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
109
|
+
|
110
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f2 => 'y', :f3 => 'z'}]).should be_false
|
111
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
112
|
+
|
113
|
+
@matcher.matches?([{:f1 => 'x', :f2 => 'y'}, 12]).should be_false
|
114
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
115
|
+
|
116
|
+
@matcher.matches?([12, {:f1 => 'x', :f2 => 'y'}]).should be_false
|
117
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
118
|
+
|
119
|
+
# An array with a single empty row is not a well-formed query result
|
120
|
+
@matcher.matches?([{}]).should be_false
|
121
|
+
@matcher.failure_message_for_should.should =~ expected_message
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'CSV-based golden master' do
|
127
|
+
before(:each) do
|
128
|
+
@matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/test_1.csv'))
|
129
|
+
end
|
130
|
+
|
131
|
+
it_behaves_like 'table-based matcher'
|
132
|
+
|
133
|
+
it 'correctly compares an empty subject with an empty CSV file' do
|
134
|
+
matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/no_rows.csv'))
|
135
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
136
|
+
|
137
|
+
matcher = Slacker::QueryResultMatcher.new(SpecHelper.load_csv('matcher/completely_blank.csv'))
|
138
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'Array-based golden master' do
|
143
|
+
before(:each) do
|
144
|
+
@matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
145
|
+
end
|
146
|
+
|
147
|
+
it_behaves_like 'table-based matcher'
|
148
|
+
|
149
|
+
it 'only accepts well formed query result golden master' do
|
150
|
+
expected_message = /Cannot match against a non-well formed golden master array/
|
151
|
+
|
152
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x'}])
|
153
|
+
matcher.matches?([]).should be_false
|
154
|
+
matcher.failure_message_for_should.should =~ expected_message
|
155
|
+
|
156
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f3 => 'y'}])
|
157
|
+
matcher.matches?([]).should be_false
|
158
|
+
matcher.failure_message_for_should.should =~ expected_message
|
159
|
+
|
160
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, {:f1 => 'x', :f2 => 'y', :f3 => 'z'}])
|
161
|
+
matcher.matches?([]).should be_false
|
162
|
+
matcher.failure_message_for_should.should =~ expected_message
|
163
|
+
|
164
|
+
matcher = Slacker::QueryResultMatcher.new([{:f1 => 'x', :f2 => 'y'}, 12])
|
165
|
+
matcher.matches?([]).should be_false
|
166
|
+
matcher.failure_message_for_should.should =~ expected_message
|
167
|
+
|
168
|
+
matcher = Slacker::QueryResultMatcher.new([12, {:f1 => 'x', :f2 => 'y'}])
|
169
|
+
matcher.matches?([]).should be_false
|
170
|
+
matcher.failure_message_for_should.should =~ expected_message
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'correctly compares an empty subject with an empty golden master' do
|
174
|
+
matcher = Slacker::QueryResultMatcher.new([])
|
175
|
+
matcher.matches?([]).should be_true(matcher.failure_message_for_should)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
shared_examples_for 'single-value-based matcher' do
|
180
|
+
it 'should correctly match the first value in the result set' do
|
181
|
+
matcher = Slacker::QueryResultMatcher.new(@correct_golden_master)
|
182
|
+
matcher.matches?(@subject).should be_true(matcher.failure_message_for_should)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should correctly reject a non-matching first item by value' do
|
186
|
+
if !@wrong_value_golden_master.nil? #Skip nil from value check when the master is nil
|
187
|
+
matcher = Slacker::QueryResultMatcher.new(@wrong_value_golden_master)
|
188
|
+
matcher.matches?(@subject).should be_false
|
189
|
+
matcher.failure_message_for_should.should =~ /Expected value "#{@wrong_value_golden_master}", got "#{@subject[0].values[0]}"/
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should correctly reject a non-matching first item by type' do
|
194
|
+
matcher = Slacker::QueryResultMatcher.new(@wrong_type_golden_master)
|
195
|
+
matcher.matches?(@subject).should be_false
|
196
|
+
matcher.failure_message_for_should.should =~ /Expected type "#{@wrong_type_golden_master.class}", got \"#{@subject[0].values[0].class}\"/
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe 'Integer-based golden master' do
|
201
|
+
before(:each) do
|
202
|
+
@subject = [{'Field 1' => 14, 'Field_2' => 'whatever'}]
|
203
|
+
@correct_golden_master = 14
|
204
|
+
@wrong_value_golden_master = 15
|
205
|
+
@wrong_type_golden_master = 'whatever'
|
206
|
+
end
|
207
|
+
|
208
|
+
it_behaves_like 'single-value-based matcher'
|
209
|
+
end
|
210
|
+
|
211
|
+
describe 'String-based golden master' do
|
212
|
+
before(:each) do
|
213
|
+
@subject = [{'Field 1' => 'test string', 'Field_2' => 12}]
|
214
|
+
@correct_golden_master = 'test string'
|
215
|
+
@wrong_value_golden_master = 'whatever'
|
216
|
+
@wrong_type_golden_master = 15
|
217
|
+
end
|
218
|
+
|
219
|
+
it_behaves_like 'single-value-based matcher'
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'Floating point-based golden master' do
|
223
|
+
before(:each) do
|
224
|
+
@subject = [{'Field 1' => 18.2, 'Field_2' => 12}]
|
225
|
+
@correct_golden_master = 18.2
|
226
|
+
@wrong_value_golden_master = 18.7
|
227
|
+
@wrong_type_golden_master = 15
|
228
|
+
end
|
229
|
+
|
230
|
+
it_behaves_like 'single-value-based matcher'
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'Date-based golden master' do
|
234
|
+
before(:each) do
|
235
|
+
@subject = [{'Field 1' => Time.parse('1/1/2011'), 'Field_2' => 12}]
|
236
|
+
@correct_golden_master = Time.parse('1/1/2011')
|
237
|
+
@wrong_value_golden_master = Time.parse('2/1/2011')
|
238
|
+
@wrong_type_golden_master = 'whatever'
|
239
|
+
end
|
240
|
+
|
241
|
+
it_behaves_like 'single-value-based matcher'
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'Nil-based golden master' do
|
245
|
+
before(:each) do
|
246
|
+
@subject = [{'Field 1' => nil, 'Field_2' => 12}]
|
247
|
+
@correct_golden_master = nil
|
248
|
+
@wrong_value_golden_master = nil # There is no wrong value for nil-based master
|
249
|
+
@wrong_type_golden_master = 'whatever'
|
250
|
+
end
|
251
|
+
|
252
|
+
it_behaves_like 'single-value-based matcher'
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'correctly matches a multi-result subject' do
|
256
|
+
subject = [@subject, [{:Field_x => 12}]]
|
257
|
+
matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
258
|
+
matcher.matches?(subject).should be_true(matcher.failure_message_for_should)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'correctly rejects a wrong multi-result subject' do
|
262
|
+
subject = [[{:Field_x => 12}], @subject]
|
263
|
+
matcher = Slacker::QueryResultMatcher.new(deep_copy(@subject))
|
264
|
+
matcher.matches?(subject).should be_false
|
265
|
+
matcher.failure_message_for_should.should == 'Expected 3 field(s), got 1'
|
266
|
+
end
|
267
|
+
end
|