rspec-sequel_expectations 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/Rakefile +2 -0
- data/Thorfile.thor +6 -0
- data/config/database.yml.example +12 -0
- data/db.rb +12 -0
- data/lib/rspec/sequel_expectations.rb +7 -0
- data/lib/rspec/sequel_expectations/matchers/have_column.rb +121 -0
- data/lib/rspec/sequel_expectations/matchers/have_enum.rb +71 -0
- data/lib/rspec/sequel_expectations/matchers/have_index_on.rb +105 -0
- data/lib/rspec/sequel_expectations/matchers/have_primary_key.rb +62 -0
- data/lib/rspec/sequel_expectations/matchers/refer_to.rb +123 -0
- data/lib/rspec/sequel_expectations/version.rb +5 -0
- data/rspec-sequel_expectations.gemspec +30 -0
- data/spec/matchers/have_column_spec.rb +188 -0
- data/spec/matchers/have_enum_spec.rb +38 -0
- data/spec/matchers/have_index_on_spec.rb +160 -0
- data/spec/matchers/have_primary_key_spec.rb +155 -0
- data/spec/matchers/refer_to_spec.rb +171 -0
- data/spec/spec_helper.rb +30 -0
- metadata +186 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module Sequel
|
4
|
+
|
5
|
+
class HavePrimaryKey
|
6
|
+
|
7
|
+
def matches?(subject)
|
8
|
+
@table = subject
|
9
|
+
|
10
|
+
get_keys
|
11
|
+
|
12
|
+
includes_all?
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
%(have #{wording(@names)})
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message
|
20
|
+
%(expected #{@table} to #{description} but #{@table} have #{wording(@keys)})
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message_when_negated
|
24
|
+
%(did not expect #{@table} to #{description})
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize(*names)
|
30
|
+
@names = names
|
31
|
+
@keys = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_keys
|
35
|
+
@keys = DB.schema(@table).reject { |tuple| !tuple.last[:primary_key] }.map(&:first)
|
36
|
+
end
|
37
|
+
|
38
|
+
def includes_all?
|
39
|
+
@names.reject { |k| @keys.include?(k) }.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def wording(arr)
|
43
|
+
case arr.length
|
44
|
+
when 0
|
45
|
+
%(no primary keys)
|
46
|
+
when 1
|
47
|
+
%(primary key "#{arr.first}")
|
48
|
+
else
|
49
|
+
%(primary keys #{arr.inspect})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def have_primary_key(*names)
|
56
|
+
HavePrimaryKey.new(*names)
|
57
|
+
end
|
58
|
+
alias :have_primary_keys :have_primary_key
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module Sequel
|
4
|
+
class ReferTo
|
5
|
+
def matches?(subject)
|
6
|
+
get_reference_for(subject)
|
7
|
+
|
8
|
+
refer_to? && correct_fk? && correct_pk? && correct_update? && correct_delete?
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_fk(key)
|
12
|
+
@foreign_key = key
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_pk(key)
|
17
|
+
@primary_key = key
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_update(action)
|
22
|
+
@on_update = action
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_delete(action)
|
27
|
+
@on_delete = action
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def description
|
32
|
+
text = [%(have reference to "#{@table}")]
|
33
|
+
text << %(with column "#{@foreign_key}") if @foreign_key
|
34
|
+
text << %(with primary key column "#{@primary_key}") if @primary_key
|
35
|
+
text << %(with "#{@on_update}" action on update) if @on_update
|
36
|
+
text << %(with "#{@on_delete}" action on delete) if @on_delete
|
37
|
+
|
38
|
+
text.join(' ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def failure_message
|
42
|
+
%(expected "#{@relation}" to #{description} but #{@error})
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message_when_negated
|
46
|
+
%(did not expect "#{@relation}" to #{description})
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def initialize(table)
|
52
|
+
@table = table
|
53
|
+
@foreign_key = nil
|
54
|
+
@primary_key = nil
|
55
|
+
@on_update = nil
|
56
|
+
@on_delete = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def refer_to?
|
60
|
+
if @reference
|
61
|
+
true
|
62
|
+
else
|
63
|
+
@error = %("#{@relation}" does not have a reference to "#{@table}")
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def correct_fk?
|
69
|
+
return true unless @foreign_key
|
70
|
+
|
71
|
+
if @reference[:columns].include?(@foreign_key)
|
72
|
+
true
|
73
|
+
else
|
74
|
+
@error = %("#{@relation}" does not have a foreign key column "#{@foreign_key}")
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def correct_pk?
|
80
|
+
return true unless @primary_key
|
81
|
+
|
82
|
+
if @reference[:key].first == @primary_key
|
83
|
+
true
|
84
|
+
else
|
85
|
+
@error = %("#{@table}" does not have a primary key column "#{@primary_key}")
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def correct_update?
|
91
|
+
return true unless @on_update
|
92
|
+
|
93
|
+
if @reference[:on_update] == @on_update
|
94
|
+
true
|
95
|
+
else
|
96
|
+
@error = %(reference does not have action "#{@on_update}" on update)
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def correct_delete?
|
102
|
+
return true unless @on_delete
|
103
|
+
|
104
|
+
if @reference[:on_delete] == @on_delete
|
105
|
+
true
|
106
|
+
else
|
107
|
+
@error = %(reference does not have action "#{@on_delete}" on delete)
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_reference_for(relation)
|
113
|
+
@relation = relation
|
114
|
+
@reference = DB.foreign_key_list(relation).select { |fk| fk[:table] == @table }.first
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def refer_to(table)
|
119
|
+
ReferTo.new(table)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rspec/sequel_expectations/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rspec-sequel_expectations"
|
8
|
+
spec.version = Rspec::SequelExpectations::VERSION
|
9
|
+
spec.authors = ["Andrey Savchenko"]
|
10
|
+
spec.email = ["andrey@aejis.eu"]
|
11
|
+
spec.summary = %q{RSpec matchers for Sequel}
|
12
|
+
spec.description = %q{RSpec matchers for Sequel which tests database, not model}
|
13
|
+
spec.homepage = "https://github.com/Ptico/rspec-sequel_expectations"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "sequel", "~> 4.15"
|
22
|
+
spec.add_dependency "rspec-expectations", "~> 3.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "sequel_pg"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
spec.add_development_dependency "pry"
|
29
|
+
spec.add_development_dependency "faker"
|
30
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RSpec::Matchers::Sequel::HaveColumn do
|
4
|
+
before :all do
|
5
|
+
DB.create_table(:users) do
|
6
|
+
column :full_name, String
|
7
|
+
column :age, Integer, default: 18, null: false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after :all do
|
12
|
+
DB.drop_table(:users)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:table) { :users }
|
16
|
+
|
17
|
+
let(:result) { matcher.matches?(table) }
|
18
|
+
|
19
|
+
describe 'column exists' do
|
20
|
+
context 'when exists' do
|
21
|
+
let(:matcher) { have_column(:full_name) }
|
22
|
+
|
23
|
+
it 'should success' do
|
24
|
+
expect(result).to be(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have description' do
|
28
|
+
expect(matcher.description).to eql('have column named "full_name"')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'column not exists' do
|
33
|
+
let(:matcher) { have_column(:abyrvalg) }
|
34
|
+
|
35
|
+
it 'should fail' do
|
36
|
+
expect(result).to be(false)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should set error message' do
|
40
|
+
expect { result }.to change {
|
41
|
+
matcher.failure_message
|
42
|
+
}.to %(expected users to #{matcher.description} but users does not have a column named "abyrvalg")
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should set negative error message' do
|
46
|
+
expect { result }.to change {
|
47
|
+
matcher.failure_message_when_negated
|
48
|
+
}.to("did not expect users to #{matcher.description}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'column type' do
|
54
|
+
context 'when correct type' do
|
55
|
+
[String, :string, 'string', 'text'].each do |type|
|
56
|
+
context "with #{type}" do
|
57
|
+
let(:matcher) { have_column(:full_name).of_type(type) }
|
58
|
+
|
59
|
+
it 'should success' do
|
60
|
+
expect(result).to be(true)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should have description' do
|
64
|
+
expect(matcher.description).to eql %(have column named "full_name" of type #{type})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when incorrect type' do
|
71
|
+
let(:matcher) { have_column(:full_name).of_type(Integer) }
|
72
|
+
|
73
|
+
it 'should fail' do
|
74
|
+
expect(result).to be(false)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should set error message' do
|
78
|
+
expect { result }.to change {
|
79
|
+
matcher.failure_message
|
80
|
+
}.to %(expected users to #{matcher.description} but it have type [string, text])
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should set negative error message' do
|
84
|
+
expect { result }.to change {
|
85
|
+
matcher.failure_message_when_negated
|
86
|
+
}.to %(did not expect users to #{matcher.description})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'with_default' do
|
92
|
+
context 'when match' do
|
93
|
+
let(:matcher) { have_column(:age).with_default(18) }
|
94
|
+
|
95
|
+
it 'should success' do
|
96
|
+
expect(result).to be(true)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should have description' do
|
100
|
+
expect(matcher.description).to eql('have column named "age" with default value "18"')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when did not match' do
|
105
|
+
let(:matcher) { have_column(:age).with_default(2) }
|
106
|
+
|
107
|
+
it 'should fail' do
|
108
|
+
expect(result).to be(false)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should have description' do
|
112
|
+
expect(matcher.description).to eql('have column named "age" with default value "2"')
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should set error message' do
|
116
|
+
expect { result }.to change {
|
117
|
+
matcher.failure_message
|
118
|
+
}.to %(expected users to #{matcher.description} but it has default value "18")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'allow_null' do
|
124
|
+
context 'when match' do
|
125
|
+
let(:matcher) { have_column(:full_name).allow_null }
|
126
|
+
|
127
|
+
it 'should success' do
|
128
|
+
expect(result).to be(true)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should have description' do
|
132
|
+
expect(matcher.description).to eql('have column named "full_name" allowing null')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when did not match' do
|
137
|
+
let(:matcher) { have_column(:age).allow_null }
|
138
|
+
|
139
|
+
it 'should fail' do
|
140
|
+
expect(result).to be(false)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should have description' do
|
144
|
+
expect(matcher.description).to eql('have column named "age" allowing null')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should set error message' do
|
148
|
+
expect { result }.to change {
|
149
|
+
matcher.failure_message
|
150
|
+
}.to %(expected users to #{matcher.description} but it does not allow null)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'not_null' do
|
156
|
+
context 'when match' do
|
157
|
+
let(:matcher) { have_column(:age).not_null }
|
158
|
+
|
159
|
+
it 'should success' do
|
160
|
+
expect(result).to be(true)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should have description' do
|
164
|
+
expect(matcher.description).to eql('have column named "age" not allowing null')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'when did not match' do
|
169
|
+
let(:matcher) { have_column(:full_name).not_null }
|
170
|
+
|
171
|
+
it 'should fail' do
|
172
|
+
expect(result).to be(false)
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should have description' do
|
176
|
+
expect(matcher.description).to eql('have column named "full_name" not allowing null')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should set error message' do
|
180
|
+
expect { result }.to change {
|
181
|
+
matcher.failure_message
|
182
|
+
}.to %(expected users to #{matcher.description} but it allow null)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RSpec::Matchers::Sequel::HaveEnum do
|
4
|
+
before :all do
|
5
|
+
@enum_name = Faker::Lorem.word
|
6
|
+
@values = %w(admin manager user inspector)
|
7
|
+
DB.create_enum(@enum_name, @values)
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
DB.drop_enum(@enum_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#have_enum' do
|
15
|
+
it 'return true if enum exists' do
|
16
|
+
expect(DB).to have_enum(@enum_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'accept symbol as enum name' do
|
20
|
+
expect(DB).to have_enum(@enum_name.to_sym)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'return false if enum does not exists' do
|
24
|
+
expect(DB).to_not have_enum(@enum_name + "1")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#with_values' do
|
29
|
+
|
30
|
+
it 'returns true if enum have exactly that values' do
|
31
|
+
expect(DB).to have_enum(@enum_name).with_values(@values)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns false if enum values differ' do
|
35
|
+
expect(DB).not_to have_enum(@enum_name).with_values(@values.dup.push "something_new" )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|