mysql_expectations 1.0.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.
- checksums.yaml +15 -0
- data/lib/mysql_expectations/array_refinements.rb +46 -0
- data/lib/mysql_expectations/database.rb +55 -0
- data/lib/mysql_expectations/field.rb +41 -0
- data/lib/mysql_expectations/key.rb +106 -0
- data/lib/mysql_expectations/key_field.rb +59 -0
- data/lib/mysql_expectations/matchers/database_have_table.rb +37 -0
- data/lib/mysql_expectations/matchers/database_only_have_tables.rb +68 -0
- data/lib/mysql_expectations/matchers/table_have_collation.rb +36 -0
- data/lib/mysql_expectations/matchers/table_have_engine_type.rb +37 -0
- data/lib/mysql_expectations/matchers/table_have_field.rb +107 -0
- data/lib/mysql_expectations/matchers/table_have_key.rb +52 -0
- data/lib/mysql_expectations/matchers.rb +50 -0
- data/lib/mysql_expectations/mysql.rb +55 -0
- data/lib/mysql_expectations/table.rb +102 -0
- data/lib/mysql_expectations/version.rb +10 -0
- data/lib/mysql_expectations.rb +19 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NGI4NWUzN2QwNTI4M2VlNjllMjY0ZTJjNTM3OWJlNmU4OWJmZTIwZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTc2Y2Y2OWY5ZTQzOTg1ZDIxOTk0MDcyNmRiZDZlZGM0NDkxYWQ4Yg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTJhYjNiYmVhOGYzZmVmM2Y3YWE4OTIyYWNhN2MxNjQ1MjI0YWU1NDllMDMx
|
10
|
+
MjU1YjM4NmZkNzA1MGJmOGFkNDIzMWE1NTUyZWIxZDk0ZTdjODAxZGU3NGI0
|
11
|
+
ZDU5NjRiNTY1MjM5MDIyZDgwM2I4ZmEzYWVhMTVjMmY4OWQyOTc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjcwMTUxYzVjYmE0ODViZmFjMDhmYmU4YWE2ZTQ1ZGIyNjE3N2YxZDNlYmQ1
|
14
|
+
MmIzZGY0NjE0YzZmMzBiOTRiMTliYTgyNThkZGQ2YmY0OWI2ODkzN2NkMDlj
|
15
|
+
N2MyYTk4Zjg4NzU5MmJhMTNhMjE5Mzg1NWMxYWNiMDZjNzEzNzI=
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
# Monkey patch Array to have #to_sentence.
|
7
|
+
# #to_sentence converts the array to a delimited string.
|
8
|
+
# you pick the delimiter, you pick the coordinating
|
9
|
+
# conjunction, you pick serial delimiter or not.
|
10
|
+
#
|
11
|
+
# Serial delimiter (or serial comma in the case the
|
12
|
+
# delimiter is a comma), means to include the comma
|
13
|
+
# between the last two items in the list.
|
14
|
+
#
|
15
|
+
module ArrayRefinements
|
16
|
+
refine Array do
|
17
|
+
def quote_elements(quote_with)
|
18
|
+
map { |e| "#{quote_with}#{e}#{quote_with}" }
|
19
|
+
end
|
20
|
+
|
21
|
+
def delimited_conjunction(conjunction, delimiter, serial_delimiter)
|
22
|
+
delimited_conjunction = conjunction
|
23
|
+
delimited_conjunction += ' ' unless conjunction.empty?
|
24
|
+
if serial_delimiter
|
25
|
+
delimited_conjunction = delimiter + delimited_conjunction
|
26
|
+
else
|
27
|
+
delimited_conjunction = ' ' + delimited_conjunction
|
28
|
+
end
|
29
|
+
delimited_conjunction
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sentence(
|
33
|
+
quote_with: "'", delimiter: ', ', conjunction: 'and',
|
34
|
+
serial_delimiter: true
|
35
|
+
)
|
36
|
+
return '' if self.empty?
|
37
|
+
words = quote_elements(quote_with)
|
38
|
+
return words[0] if words.size == 1
|
39
|
+
return "#{words[0]} #{conjunction} #{words[1]}" if words.size == 2
|
40
|
+
delimited_conjunction = delimited_conjunction(
|
41
|
+
conjunction, delimiter, serial_delimiter)
|
42
|
+
[words[0...-1].join(delimiter), words.last].join(delimited_conjunction)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/table'
|
6
|
+
|
7
|
+
module MySQLExpectations
|
8
|
+
# Allows assertions on a database
|
9
|
+
#
|
10
|
+
class Database
|
11
|
+
attr_reader
|
12
|
+
|
13
|
+
def initialize(database_element)
|
14
|
+
@database_element = database_element
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@database_element.attributes['name']
|
19
|
+
end
|
20
|
+
|
21
|
+
def table?(name)
|
22
|
+
query = "table_structure[@name='#{name}']"
|
23
|
+
!@database_element.elements[query].nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :has_table?, :table?
|
27
|
+
|
28
|
+
def table(name)
|
29
|
+
query = "table_structure[@name='#{name}']"
|
30
|
+
table_element = @database_element.elements[query]
|
31
|
+
Table.new table_element if table_element
|
32
|
+
end
|
33
|
+
|
34
|
+
def tables
|
35
|
+
query = 'table_structure'
|
36
|
+
tables = []
|
37
|
+
@database_element.elements.each(query) do |table_element|
|
38
|
+
tables << Table.new(table_element)
|
39
|
+
end
|
40
|
+
tables
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(method, *arguments, &block)
|
44
|
+
if arguments.empty? && block.nil?
|
45
|
+
table_name = method.to_s
|
46
|
+
return table(table_name) if table?(table_name)
|
47
|
+
end
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def respond_to_missing?(method, *)
|
52
|
+
table?(method.to_s) || super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
# Allows assertions on a database
|
7
|
+
#
|
8
|
+
class Field
|
9
|
+
def initialize(field_element)
|
10
|
+
@field_element = field_element
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@field_element.attributes['Field']
|
15
|
+
end
|
16
|
+
|
17
|
+
def definition?(expected_options)
|
18
|
+
expected_options.each do |expected_key, expected_value|
|
19
|
+
value = @field_element.attributes[expected_key]
|
20
|
+
return false if value.nil? || value != expected_value
|
21
|
+
end
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :has_definition?, :definition?
|
26
|
+
|
27
|
+
def type
|
28
|
+
@field_element.attributes['Type']
|
29
|
+
end
|
30
|
+
|
31
|
+
def type?(expected_type)
|
32
|
+
expected_type == type
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :has_type?, :type?
|
36
|
+
|
37
|
+
def nullable?
|
38
|
+
@field_element.attributes['Null'] == 'YES'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/key_field'
|
6
|
+
|
7
|
+
module MySQLExpectations
|
8
|
+
# An table key has a name and a sequence of index fields.
|
9
|
+
#
|
10
|
+
class Key
|
11
|
+
attr_reader :name, :unique, :fields
|
12
|
+
|
13
|
+
UNIQUE = true
|
14
|
+
NON_UNIQUE = false
|
15
|
+
|
16
|
+
def initialize(name, unique = nil, fields)
|
17
|
+
@name = name
|
18
|
+
@unique = unique
|
19
|
+
@fields = []
|
20
|
+
fields.each do |field|
|
21
|
+
field = KeyField.new(field) unless field.instance_of?(KeyField)
|
22
|
+
@fields << field
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
name == other.name && unique == other.unique && fields == other.fields
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
result = "{ '#{name}', #{uniqueness_to_s}, ["
|
32
|
+
fields.each_with_index do |field, index|
|
33
|
+
result << ', ' if index > 0
|
34
|
+
result << field.to_s
|
35
|
+
end
|
36
|
+
result << '] }'
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def uniqueness_to_s
|
41
|
+
return 'nil' if unique.nil?
|
42
|
+
return 'UNIQUE' if unique == true
|
43
|
+
return 'NON_UNIQUE' if unique == false
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.key?(table_element, key_name)
|
47
|
+
result = table_element.elements["key[@Key_name='#{key_name}']"]
|
48
|
+
!result.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.load_key(table_element, key_name)
|
52
|
+
if key?(table_element, key_name)
|
53
|
+
key_fields = load_key_fields(table_element, key_name)
|
54
|
+
unique = load_uniqueness(table_element, key_name)
|
55
|
+
return Key.new(key_name, unique, key_fields)
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.load_all_keys(table_element)
|
61
|
+
keys = {}
|
62
|
+
table_element.elements.each('key') do |e|
|
63
|
+
key_name = e.attributes['Key_name']
|
64
|
+
unless keys.key? key_name
|
65
|
+
keys[key_name] = load_key(table_element, key_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
keys.values
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.find_key_element(table_element, index_name, field_index)
|
72
|
+
query = "key[@Key_name='#{index_name}' and " \
|
73
|
+
"@Seq_in_index='#{field_index + 1}']"
|
74
|
+
table_element.elements[query]
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.collation_from_s(collation)
|
78
|
+
return KeyField::ORDER_DESC if collation == 'D'
|
79
|
+
KeyField::ORDER_ASC
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.load_key_field(key_element)
|
83
|
+
name = key_element.attributes['Column_name']
|
84
|
+
collation = key_element.attributes['Collation']
|
85
|
+
collation = collation_from_s(collation)
|
86
|
+
length = key_element.attributes['Length']
|
87
|
+
KeyField.new(name, collation, length)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.load_uniqueness(table_element, key_name)
|
91
|
+
key_element = find_key_element(table_element, key_name, 0)
|
92
|
+
return nil if key_element.nil?
|
93
|
+
(key_element.attributes['Non_unique'] == '0')
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.load_key_fields(table_element, key_name)
|
97
|
+
key_fields = []
|
98
|
+
(0..1000).each do |field_index|
|
99
|
+
key_element = find_key_element(table_element, key_name, field_index)
|
100
|
+
break if key_element.nil?
|
101
|
+
key_fields << load_key_field(key_element)
|
102
|
+
end
|
103
|
+
key_fields
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
# An index field has a field name, length, and order (aka order)
|
7
|
+
#
|
8
|
+
class KeyField
|
9
|
+
attr_reader :name, :length, :order
|
10
|
+
|
11
|
+
ORDER_ASC = :asc
|
12
|
+
ORDER_DESC = :desc
|
13
|
+
|
14
|
+
VALID_ORDERINGS = [ORDER_ASC, ORDER_DESC]
|
15
|
+
|
16
|
+
def initialize(name, order = nil, length = nil)
|
17
|
+
@name = name
|
18
|
+
@order = validate_order(order)
|
19
|
+
@length = validate_length(length)
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
other.name == @name && other.length == @length && other.order == @order
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"{ '#{name}', #{order_to_s}, #{length_to_s} }"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def order_to_s
|
33
|
+
return 'ASC' if order == ORDER_ASC
|
34
|
+
return 'DESC' if order == ORDER_DESC
|
35
|
+
'nil'
|
36
|
+
end
|
37
|
+
|
38
|
+
def length_to_s
|
39
|
+
length.to_s unless length.nil?
|
40
|
+
'nil'
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_length(length)
|
44
|
+
return nil if length.nil?
|
45
|
+
begin
|
46
|
+
length = Integer(length)
|
47
|
+
rescue ArgumentError
|
48
|
+
raise ArgumentError, 'length must be an integer or convertable to an integer'
|
49
|
+
end
|
50
|
+
length
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_order(order)
|
54
|
+
return nil if order.nil?
|
55
|
+
return order if VALID_ORDERINGS.include? order
|
56
|
+
fail ArgumentError, 'Order must be either KeyField::ORDER_ASC or KeyField::ORDER_DESC'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
module Matchers
|
7
|
+
# A matcher that checks a table's collation
|
8
|
+
#
|
9
|
+
class DatabaseHaveTable
|
10
|
+
attr_reader :expected_table, :database
|
11
|
+
|
12
|
+
def initialize(expected_table)
|
13
|
+
@expected_table = expected_table
|
14
|
+
@table = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(database)
|
18
|
+
@database = database
|
19
|
+
database.table?(expected_table)
|
20
|
+
end
|
21
|
+
|
22
|
+
def description
|
23
|
+
"have table '#{expected_table}'"
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure_message
|
27
|
+
"expected '#{database.name}' database to have table " \
|
28
|
+
"'#{expected_table}'"
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure_message_when_negated
|
32
|
+
"expected '#{database.name}' database not to have table " \
|
33
|
+
"'#{expected_table}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/array_refinements'
|
6
|
+
|
7
|
+
module MySQLExpectations
|
8
|
+
module Matchers
|
9
|
+
# A matcher that checks what tables are in a database
|
10
|
+
#
|
11
|
+
class DatabaseOnlyHaveTables
|
12
|
+
using ArrayRefinements
|
13
|
+
|
14
|
+
attr_reader :database, :expected_table_names, :actual_table_names
|
15
|
+
|
16
|
+
def initialize(*expected_table_names)
|
17
|
+
@expected_table_names = expected_table_names.flatten
|
18
|
+
@database = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def liquibase_tables
|
22
|
+
%w(DATABASECHANGELOG DATABASECHANGELOGLOCK)
|
23
|
+
end
|
24
|
+
|
25
|
+
def actual_table_names
|
26
|
+
database.tables.map(&:name) - liquibase_tables
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(database)
|
30
|
+
@database = database
|
31
|
+
actual_table_names.sort == expected_table_names.sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def description
|
35
|
+
"only have tables #{expected_table_names.to_sentence}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def additional_tables_message
|
39
|
+
message = ''
|
40
|
+
unless (actual_table_names - expected_table_names).empty?
|
41
|
+
message << ' but had additional tables: '
|
42
|
+
message <<
|
43
|
+
(actual_table_names - expected_table_names).to_sentence
|
44
|
+
end
|
45
|
+
message
|
46
|
+
end
|
47
|
+
|
48
|
+
def missing_tables_message
|
49
|
+
message = ''
|
50
|
+
unless (expected_table_names - actual_table_names).empty?
|
51
|
+
message << ' but was missing tables: '
|
52
|
+
message <<
|
53
|
+
(expected_table_names - actual_table_names).to_sentence
|
54
|
+
end
|
55
|
+
message
|
56
|
+
end
|
57
|
+
|
58
|
+
def failure_message
|
59
|
+
message = "expected '#{database.name}' "
|
60
|
+
message << 'database to only have tables '
|
61
|
+
message << expected_table_names.to_sentence
|
62
|
+
message << additional_tables_message
|
63
|
+
message << missing_tables_message
|
64
|
+
message
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
module Matchers
|
7
|
+
# A matcher that checks a table's collation
|
8
|
+
#
|
9
|
+
class TableHaveCollation
|
10
|
+
attr_reader :expected_collation, :table
|
11
|
+
|
12
|
+
def initialize(expected_collation)
|
13
|
+
@expected_collation = expected_collation
|
14
|
+
@table = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def actual_collation
|
18
|
+
table.collation
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(table)
|
22
|
+
@table = table
|
23
|
+
actual_collation == expected_collation
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
"have collation '#{expected_collation}'"
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_message
|
31
|
+
"expected '#{table.name}' table to have collation " \
|
32
|
+
"'#{expected_collation}' but it was '#{table.collation}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
module Matchers
|
7
|
+
# A matcher that check a table's engine type
|
8
|
+
#
|
9
|
+
class HaveEngineType
|
10
|
+
attr_reader :expected_engine_type, :table
|
11
|
+
|
12
|
+
def initialize(expected_engine_type)
|
13
|
+
@expected_engine_type = expected_engine_type
|
14
|
+
@table = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def actual_engine_type
|
18
|
+
table.engine_type
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(table)
|
22
|
+
@table = table
|
23
|
+
actual_engine_type == expected_engine_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
"have engine type '#{expected_engine_type}'"
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_message
|
31
|
+
"expected '#{table.name}' table engine type to be " \
|
32
|
+
"'#{expected_engine_type}' " \
|
33
|
+
"but it was '#{actual_engine_type}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
module Matchers
|
7
|
+
# A matcher that checks if a MySQLDumpExpectations::Table has a field.
|
8
|
+
# Optionally, checks the field type and nullability
|
9
|
+
#
|
10
|
+
class HaveField
|
11
|
+
attr_reader :expected_field_name, :expected_type
|
12
|
+
attr_reader :expected_nullable, :table
|
13
|
+
|
14
|
+
def initialize(expected_field_name)
|
15
|
+
@expected_field_name = expected_field_name
|
16
|
+
@expected_type = nil
|
17
|
+
@expected_nullable = nil
|
18
|
+
@table = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def field_match?
|
22
|
+
table.field?(expected_field_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def actual_type
|
26
|
+
table.field(expected_field_name).type
|
27
|
+
end
|
28
|
+
|
29
|
+
def type_match?
|
30
|
+
if field_match? && !expected_type.nil?
|
31
|
+
return actual_type == expected_type
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def actual_nullable
|
37
|
+
table.field(expected_field_name).nullable?
|
38
|
+
end
|
39
|
+
|
40
|
+
def nullable_match?
|
41
|
+
if field_match? && !expected_nullable.nil?
|
42
|
+
return actual_nullable == expected_nullable
|
43
|
+
end
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def matches?(table)
|
48
|
+
@table = table
|
49
|
+
field_match? && type_match? && nullable_match?
|
50
|
+
end
|
51
|
+
|
52
|
+
def description
|
53
|
+
description = "have field '#{expected_field_name}'"
|
54
|
+
description << " of type '#{expected_type}'" if expected_type
|
55
|
+
description << ' that is nullable' if expected_nullable == true
|
56
|
+
description << ' that is not nullable' if expected_nullable == false
|
57
|
+
description
|
58
|
+
end
|
59
|
+
|
60
|
+
def field_error(negated: false)
|
61
|
+
"expected '#{table.name}' table" \
|
62
|
+
" #{negated ? 'not ' : ''}to "\
|
63
|
+
"have field '#{expected_field_name}'."
|
64
|
+
end
|
65
|
+
|
66
|
+
def type_error
|
67
|
+
"expected '#{table.name}.#{expected_field_name}' field " \
|
68
|
+
"to be of type '#{expected_type}' but it was '#{actual_type}'"
|
69
|
+
end
|
70
|
+
|
71
|
+
def nullable_error
|
72
|
+
message = "expected '#{table.name}.#{expected_field_name}' field "
|
73
|
+
message << 'to be '
|
74
|
+
message << 'not ' unless expected_nullable
|
75
|
+
message << 'nullable but it was '
|
76
|
+
message << 'not ' unless actual_nullable
|
77
|
+
message << 'nullable.'
|
78
|
+
message
|
79
|
+
end
|
80
|
+
|
81
|
+
def failure_message
|
82
|
+
return field_error unless field_match?
|
83
|
+
return type_error unless type_match?
|
84
|
+
return nullable_error unless nullable_match?
|
85
|
+
end
|
86
|
+
|
87
|
+
def failure_message_when_negated
|
88
|
+
field_error(negated: true)
|
89
|
+
end
|
90
|
+
|
91
|
+
def of_type(expected_type)
|
92
|
+
@expected_type = expected_type
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def that_is_nullable
|
97
|
+
@expected_nullable = true
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def that_is_not_nullable
|
102
|
+
@expected_nullable = false
|
103
|
+
self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
module Matchers
|
7
|
+
# A matcher that checks if a MySQLDumpExpectations::Table has a field.
|
8
|
+
# Optionally, checks the field type and nullability
|
9
|
+
#
|
10
|
+
class HaveKey
|
11
|
+
attr_reader :expected_key, :table
|
12
|
+
|
13
|
+
def initialize(expected_key)
|
14
|
+
@expected_key = expected_key
|
15
|
+
@table = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(table)
|
19
|
+
@table = table
|
20
|
+
@table.key?(expected_key.name) &&
|
21
|
+
@table.key(expected_key.name) == expected_key
|
22
|
+
end
|
23
|
+
|
24
|
+
def description
|
25
|
+
description = "have key #{expected_key}"
|
26
|
+
description
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_keys(keys)
|
30
|
+
result = ''
|
31
|
+
keys.each_with_index do |key, index|
|
32
|
+
result << key.to_s
|
33
|
+
result << ",\n" unless index == keys.size - 1
|
34
|
+
end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure_message
|
39
|
+
result = "expected '#{table.name}' table to " \
|
40
|
+
"have key #{expected_key}"
|
41
|
+
keys = table.keys
|
42
|
+
if keys.empty?
|
43
|
+
result << ' but it has no keys.'
|
44
|
+
else
|
45
|
+
result << " but it has keys:\n"
|
46
|
+
result << current_keys(keys)
|
47
|
+
end
|
48
|
+
result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/matchers/database_have_table'
|
6
|
+
require 'mysql_expectations/matchers/database_only_have_tables'
|
7
|
+
require 'mysql_expectations/matchers/table_have_collation'
|
8
|
+
require 'mysql_expectations/matchers/table_have_engine_type'
|
9
|
+
require 'mysql_expectations/matchers/table_have_field'
|
10
|
+
require 'mysql_expectations/matchers/table_have_key'
|
11
|
+
|
12
|
+
# rubocop:disable Style/PredicateName
|
13
|
+
|
14
|
+
module MySQLExpectations
|
15
|
+
# Allows assertions on a database
|
16
|
+
#
|
17
|
+
module Matchers
|
18
|
+
####################
|
19
|
+
# Database Matchers
|
20
|
+
####################
|
21
|
+
|
22
|
+
def have_table(expected_table_name)
|
23
|
+
DatabaseHaveTable.new(expected_table_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def only_have_tables(*expected_table_names)
|
27
|
+
DatabaseOnlyHaveTables.new(*expected_table_names)
|
28
|
+
end
|
29
|
+
|
30
|
+
####################
|
31
|
+
# Table Matchers
|
32
|
+
####################
|
33
|
+
|
34
|
+
def have_collation(expected_collation)
|
35
|
+
TableHaveCollation.new(expected_collation)
|
36
|
+
end
|
37
|
+
|
38
|
+
def have_engine_type(expected_engine_type)
|
39
|
+
HaveEngineType.new(expected_engine_type)
|
40
|
+
end
|
41
|
+
|
42
|
+
def have_field(expected_field_name)
|
43
|
+
HaveField.new(expected_field_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def have_key(expected_key)
|
47
|
+
HaveKey.new(expected_key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'rexml/document'
|
6
|
+
require 'mysql_expectations/database'
|
7
|
+
|
8
|
+
module MySQLExpectations
|
9
|
+
# The database_structure_file should be an XML file resulting from the
|
10
|
+
# mysqldump command:
|
11
|
+
#
|
12
|
+
# mysqldump --xml --no-data --all-databases \
|
13
|
+
# --host=#{env.host} --port=#{env.port} \
|
14
|
+
# --user=#{env.username}
|
15
|
+
#
|
16
|
+
class MySQL
|
17
|
+
def initialize(source)
|
18
|
+
@doc = REXML::Document.new(source)
|
19
|
+
end
|
20
|
+
|
21
|
+
def database?(name)
|
22
|
+
query = "mysqldump/database[@name='#{name}']"
|
23
|
+
!@doc.elements[query].nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :has_database?, :database?
|
27
|
+
|
28
|
+
def database(name)
|
29
|
+
query = "mysqldump/database[@name='#{name}']"
|
30
|
+
database_element = @doc.elements[query]
|
31
|
+
Database.new database_element if database_element
|
32
|
+
end
|
33
|
+
|
34
|
+
def databases
|
35
|
+
query = 'mysqldump/database'
|
36
|
+
databases = []
|
37
|
+
@doc.elements.each(query) do |database_element|
|
38
|
+
databases << Database.new(database_element)
|
39
|
+
end
|
40
|
+
databases
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(method_sym, *arguments, &block)
|
44
|
+
if arguments.empty? && block.nil?
|
45
|
+
database_name = method_sym.to_s
|
46
|
+
return database(database_name) if database?(database_name)
|
47
|
+
end
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def respond_to_missing?(method, *)
|
52
|
+
database?(method.to_s) || super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/field'
|
6
|
+
require 'mysql_expectations/key'
|
7
|
+
|
8
|
+
module MySQLExpectations
|
9
|
+
# Allows assertions on a table
|
10
|
+
#
|
11
|
+
class Table
|
12
|
+
def initialize(table_element)
|
13
|
+
@table_element = table_element
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@table_element.attributes['name']
|
18
|
+
end
|
19
|
+
|
20
|
+
def field?(expected_field)
|
21
|
+
query = "field[@Field='#{expected_field}']"
|
22
|
+
!@table_element.elements[query].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :has_field?, :field?
|
26
|
+
|
27
|
+
def field(name)
|
28
|
+
query = "field[@Field='#{name}']"
|
29
|
+
field_element = @table_element.elements[query]
|
30
|
+
Field.new field_element if field_element
|
31
|
+
end
|
32
|
+
|
33
|
+
def fields
|
34
|
+
query = 'field'
|
35
|
+
fields = []
|
36
|
+
@table_element.elements.each(query) do |field_element|
|
37
|
+
fields << Field.new(field_element)
|
38
|
+
end
|
39
|
+
fields
|
40
|
+
end
|
41
|
+
|
42
|
+
def option?(expected_options)
|
43
|
+
options = @table_element.elements['options']
|
44
|
+
expected_options.each do |expected_key, expected_value|
|
45
|
+
value = options.attributes[expected_key]
|
46
|
+
return false if value.nil? || value != expected_value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :has_option?, :option?
|
51
|
+
|
52
|
+
def engine_type
|
53
|
+
options = @table_element.elements['options']
|
54
|
+
options.attributes['Engine'] if options
|
55
|
+
end
|
56
|
+
|
57
|
+
def engine_type?(expected_engine_type)
|
58
|
+
engine_type == expected_engine_type
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :has_engine_type?, :engine_type?
|
62
|
+
|
63
|
+
def collation
|
64
|
+
options = @table_element.elements['options']
|
65
|
+
options.attributes['Collation'] if options
|
66
|
+
end
|
67
|
+
|
68
|
+
def collation?(expected_collation)
|
69
|
+
collation == expected_collation
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :has_collation?, :collation?
|
73
|
+
|
74
|
+
def key?(key_name)
|
75
|
+
Key.key?(@table_element, key_name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def primary_key?
|
79
|
+
key?('PRIMARY')
|
80
|
+
end
|
81
|
+
|
82
|
+
def key(key_name)
|
83
|
+
Key.load_key(@table_element, key_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def keys
|
87
|
+
Key.load_all_keys(@table_element)
|
88
|
+
end
|
89
|
+
|
90
|
+
def method_missing(method, *arguments, &block)
|
91
|
+
if arguments.empty? && block.nil?
|
92
|
+
name = method.to_s
|
93
|
+
return field(name) if field?(name)
|
94
|
+
end
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def respond_to_missing?(method, *)
|
99
|
+
field?(method.to_s) || super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
module MySQLExpectations
|
6
|
+
# This module holds the gem version number used in the gemspec
|
7
|
+
module Version
|
8
|
+
VERSION = '1.0.0'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Copyright (c) 2015 Yahoo Inc.
|
2
|
+
# Copyrights licensed under the New BSD License.
|
3
|
+
# See the accompanying LICENSE file for terms.
|
4
|
+
|
5
|
+
require 'mysql_expectations/array_refinements'
|
6
|
+
require 'mysql_expectations/mysql'
|
7
|
+
require 'mysql_expectations/database'
|
8
|
+
require 'mysql_expectations/table'
|
9
|
+
require 'mysql_expectations/field'
|
10
|
+
require 'mysql_expectations/key'
|
11
|
+
require 'mysql_expectations/key_field'
|
12
|
+
require 'mysql_expectations/matchers'
|
13
|
+
require 'mysql_expectations/version'
|
14
|
+
|
15
|
+
require 'rspec'
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.include MySQLExpectations::Matchers
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql_expectations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Couball
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mustache
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-collection_matchers
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cucumber
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: aruba
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: relish
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.4'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.4'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ! '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: ! " The mysql_expectations gem allows you to make RSpec expectations\n
|
154
|
+
\ on the schemas and data in a MySQL database enabling test first\n development
|
155
|
+
and more confident Continuous Delivery.\n"
|
156
|
+
email: couballj@yahoo-inc.com
|
157
|
+
executables: []
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- lib/mysql_expectations.rb
|
162
|
+
- lib/mysql_expectations/array_refinements.rb
|
163
|
+
- lib/mysql_expectations/database.rb
|
164
|
+
- lib/mysql_expectations/field.rb
|
165
|
+
- lib/mysql_expectations/key.rb
|
166
|
+
- lib/mysql_expectations/key_field.rb
|
167
|
+
- lib/mysql_expectations/matchers.rb
|
168
|
+
- lib/mysql_expectations/matchers/database_have_table.rb
|
169
|
+
- lib/mysql_expectations/matchers/database_only_have_tables.rb
|
170
|
+
- lib/mysql_expectations/matchers/table_have_collation.rb
|
171
|
+
- lib/mysql_expectations/matchers/table_have_engine_type.rb
|
172
|
+
- lib/mysql_expectations/matchers/table_have_field.rb
|
173
|
+
- lib/mysql_expectations/matchers/table_have_key.rb
|
174
|
+
- lib/mysql_expectations/mysql.rb
|
175
|
+
- lib/mysql_expectations/table.rb
|
176
|
+
- lib/mysql_expectations/version.rb
|
177
|
+
homepage: https://github.com/yahoo/mysql_expectations
|
178
|
+
licenses:
|
179
|
+
- BSD-3-Clause
|
180
|
+
metadata: {}
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubyforge_project:
|
197
|
+
rubygems_version: 2.4.5
|
198
|
+
signing_key:
|
199
|
+
specification_version: 4
|
200
|
+
summary: RSpec matchers for MySQL schemas and data
|
201
|
+
test_files: []
|