rspec-sequel_expectations 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 758f1cf23d10f38246e347bf38abdb55f6a88710
|
4
|
+
data.tar.gz: 2b9f2554615ac7ff91d8152fc2af937044661343
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c7926ba67c720199dfef2c2868054289206810ad5c3a8e3140fcfc7784f7e74f3b8cf2f810cd278004e483d3fa535a49f71b26e996683bb2aae24fe17ba0cc1
|
7
|
+
data.tar.gz: f9425b101ce337c03998da126e08f032b6ff703d4139eaa112adfbaa4eccf078f2cb4a811c1228a3325133c84073a1950cd48d7b0016d72c8781444b0a83eea4
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
config/database.yml
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Andrey Savchenko
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Rspec Sequel expectations
|
2
|
+
|
3
|
+
RSpec matchers for Sequel which tests database, not model
|
4
|
+
|
5
|
+
## Using
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.include RSpec::Matchers::Sequel
|
10
|
+
end
|
11
|
+
|
12
|
+
describe DB do
|
13
|
+
it { expect(DB).to have_enum('role_types').with_values(%w(admin manager user)) }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'companies table' do
|
17
|
+
let(:table) { :companies }
|
18
|
+
|
19
|
+
it { expect(table).to have_primary_keys(:id, :city_id) }
|
20
|
+
it { expect(table).to have_column(:name).of_type(String).not_null.with_default('') }
|
21
|
+
|
22
|
+
it { expect(table).to have_unique_index_on(:email) }
|
23
|
+
it { expect(table).to have_index_on([:password, :email]).named('companies_credentials') }
|
24
|
+
|
25
|
+
it { expect(table).to refer_to(:city).from_fk(:city_id).to_pk(:id).on_update(:cascade).on_delete(:set_null) }
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Add this line to your application's Gemfile:
|
32
|
+
|
33
|
+
gem 'rspec-sequel_expectations'
|
34
|
+
|
35
|
+
And then execute:
|
36
|
+
|
37
|
+
$ bundle
|
38
|
+
|
39
|
+
Or install it yourself as:
|
40
|
+
|
41
|
+
$ gem install rspec-sequel_expectations
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
1. Fork it ( https://github.com/Ptico/rspec-sequel_expectations/fork )
|
46
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
47
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
48
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
49
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/Thorfile.thor
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# For available adapters and connection options
|
2
|
+
# see: https://github.com/Ptico/sequelize/wiki/Database-configuration
|
3
|
+
#
|
4
|
+
default: &default
|
5
|
+
adapter: postgres
|
6
|
+
encoding: utf8
|
7
|
+
|
8
|
+
development:
|
9
|
+
database: myapp_development
|
10
|
+
user: postgres
|
11
|
+
password: secret
|
12
|
+
<<: *default
|
data/db.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'rspec/sequel_expectations/version'
|
2
|
+
|
3
|
+
require 'rspec/sequel_expectations/matchers/have_primary_key'
|
4
|
+
require 'rspec/sequel_expectations/matchers/have_column'
|
5
|
+
require 'rspec/sequel_expectations/matchers/have_index_on'
|
6
|
+
require 'rspec/sequel_expectations/matchers/refer_to'
|
7
|
+
require 'rspec/sequel_expectations/matchers/have_enum'
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module Sequel
|
4
|
+
|
5
|
+
class HaveColumn
|
6
|
+
|
7
|
+
def matches?(subject)
|
8
|
+
get_column_from(subject)
|
9
|
+
|
10
|
+
have_column? && correct_type? && correct_default? && correct_null?
|
11
|
+
end
|
12
|
+
|
13
|
+
def of_type(type)
|
14
|
+
@type = type
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_default(val)
|
19
|
+
@default = val
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def allow_null
|
24
|
+
@null = true
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def not_null
|
29
|
+
@null = false
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def description
|
34
|
+
text = [%(have column named "#{@name}")]
|
35
|
+
text << "of type #{@type}" if @type
|
36
|
+
text << %(with default value "#{@default}") unless @default == false
|
37
|
+
text << 'allowing null' if @null == true
|
38
|
+
text << 'not allowing null' if @null == false
|
39
|
+
text.join(' ')
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure_message
|
43
|
+
%(expected #{@table} to #{description} but #{@error})
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure_message_when_negated
|
47
|
+
%(did not expect #{@table} to #{description})
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def initialize(column_name)
|
53
|
+
@name = column_name
|
54
|
+
@type = nil
|
55
|
+
@null = nil
|
56
|
+
@default = false
|
57
|
+
@table = nil
|
58
|
+
@error = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_column_from(table)
|
62
|
+
column = DB.schema(table.to_sym).detect { |tuple| tuple.first == @name }
|
63
|
+
|
64
|
+
@table = table
|
65
|
+
@column = column ? column.last : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def have_column?
|
69
|
+
if @column.nil?
|
70
|
+
@error = %(#{@table} does not have a column named "#{@name}")
|
71
|
+
false
|
72
|
+
else
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def correct_type?
|
78
|
+
return true unless @type
|
79
|
+
|
80
|
+
expected = DB.send(:type_literal, { type: @type }).to_s
|
81
|
+
actual = [@column[:type].to_s, @column[:db_type].to_s]
|
82
|
+
|
83
|
+
if actual.include?(expected)
|
84
|
+
true
|
85
|
+
else
|
86
|
+
@error = %(it have type [#{actual.join(', ')}])
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def correct_null?
|
92
|
+
return true if @null.nil?
|
93
|
+
|
94
|
+
if @column[:allow_null] == @null
|
95
|
+
true
|
96
|
+
else
|
97
|
+
@error = %(it #{"does not " if @null == true}allow null)
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def correct_default?
|
103
|
+
return true if @default == false
|
104
|
+
|
105
|
+
if [@column[:default], @column[:ruby_default]].include?(@default)
|
106
|
+
true
|
107
|
+
else
|
108
|
+
@error = %(it has default value "#{@column[:ruby_default] || @column[:default]}")
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
def have_column(name)
|
116
|
+
HaveColumn.new(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module RSpec
|
3
|
+
module Matchers
|
4
|
+
module Sequel
|
5
|
+
# TODO: refactor with Sequel API
|
6
|
+
# http://www.rubydoc.info/gems/sequel/4.13.0/Sequel/Postgres/EnumDatabaseMethods
|
7
|
+
class HaveEnum
|
8
|
+
def matches?(db)
|
9
|
+
@db = db
|
10
|
+
enum_exists? && with_valid_values?
|
11
|
+
end
|
12
|
+
|
13
|
+
def failure_message_when_negated
|
14
|
+
"expected database not to #{@description} #{@error}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message
|
18
|
+
"expected database to #{description} #{@error}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_values(*values)
|
22
|
+
@enum_values = values.flatten
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def description
|
29
|
+
text = [%(have enum named "#{@enum_name}")]
|
30
|
+
text << %(with values "#{@enum_values}") unless @enum_values.empty?
|
31
|
+
text.join(' ')
|
32
|
+
end
|
33
|
+
|
34
|
+
def enum_exists?
|
35
|
+
!!@db.fetch("SELECT '#{@enum_name}'::regtype;").first
|
36
|
+
rescue ::Sequel::DatabaseError => e
|
37
|
+
|
38
|
+
if e.message[0..18] == 'PG::UndefinedObject'
|
39
|
+
@error = "but it doesn't exist"
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_valid_values?
|
47
|
+
return true if @enum_values.empty?
|
48
|
+
|
49
|
+
sql = "SELECT e.enumlabel FROM pg_enum e JOIN pg_type t ON t.oid = e.enumtypid WHERE t.typname = '#{@enum_name}';"
|
50
|
+
values = @db.fetch(sql).map { |enum| enum[:enumlabel] }
|
51
|
+
|
52
|
+
if @enum_values.sort == values.sort
|
53
|
+
true
|
54
|
+
else
|
55
|
+
@error = "but got #{values}"
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(enum_name)
|
61
|
+
@enum_values = []
|
62
|
+
@enum_name = enum_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def have_enum(enum_name)
|
67
|
+
HaveEnum.new(enum_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module Sequel
|
4
|
+
|
5
|
+
class HaveIndexOn
|
6
|
+
|
7
|
+
def named(name)
|
8
|
+
@name = name.to_sym
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(table)
|
13
|
+
@table = table
|
14
|
+
|
15
|
+
get_required_index
|
16
|
+
|
17
|
+
index_present? && correct_name? && unique?
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
text = %w(have)
|
22
|
+
text << 'unique' if @unique
|
23
|
+
text << %(index on #{@columns.inspect})
|
24
|
+
text << %(named "#{@name}") if @name
|
25
|
+
text.join(' ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message
|
29
|
+
%(expected #{@table} to #{description} but #{@error})
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure_message_when_negated
|
33
|
+
%(did not expect #{@table} to #{description})
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def initialize(column, unique=false)
|
39
|
+
@columns = Array(column)
|
40
|
+
@unique = unique
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_required_index
|
44
|
+
@index = DB.indexes(@table).each_pair.detect { |index, opts|
|
45
|
+
index.to_s.split('_').last != 'key' && opts[:columns] == @columns
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# PostgreSQL adapter returns index and constraints as separate objects,
|
50
|
+
# each with an appropriate postfix.
|
51
|
+
# Other case is when constaint name is defined manually and
|
52
|
+
# following method must take that in account too.
|
53
|
+
def get_required_key
|
54
|
+
key = DB.indexes(@table).each_pair.detect do |key, opts|
|
55
|
+
if @name
|
56
|
+
key == @name
|
57
|
+
else
|
58
|
+
key.to_s.split('_').last == 'key' && opts[:columns] == @columns
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@key = key.nil? ? {} : key.last
|
62
|
+
end
|
63
|
+
|
64
|
+
def index_present?
|
65
|
+
if @index.nil?
|
66
|
+
@error = 'none exists'
|
67
|
+
false
|
68
|
+
else
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def correct_name?
|
74
|
+
if @name && @index.first != @name
|
75
|
+
@error = %(index have name "#{@index.first}")
|
76
|
+
false
|
77
|
+
else
|
78
|
+
true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def unique?
|
83
|
+
get_required_key
|
84
|
+
if @index.last[:unique] == @unique || @key.fetch(:unique, false) == @unique
|
85
|
+
true
|
86
|
+
else
|
87
|
+
@error = 'index is non-unique'
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def have_index_on(column)
|
95
|
+
HaveIndexOn.new(column)
|
96
|
+
end
|
97
|
+
|
98
|
+
def have_unique_index_on(column)
|
99
|
+
HaveIndexOn.new(column, true)
|
100
|
+
end
|
101
|
+
alias :have_uniq_index_on :have_unique_index_on
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|