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 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
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rspec-sequel_expectations.gemspec
4
+ gemspec
5
+
6
+ gem 'sequelize', github: 'Ptico/sequelize'
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/Thorfile.thor ADDED
@@ -0,0 +1,6 @@
1
+ # DB tasks
2
+ require 'sequelize/thor/db'
3
+ require File.join(File.dirname(__FILE__), 'db.rb')
4
+
5
+ # Generators
6
+ require 'sequelize/thor/generate'
@@ -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,12 @@
1
+ require 'sequelize'
2
+
3
+ Sequelize.configure do
4
+ root File.expand_path('.', File.dirname(__FILE__))
5
+ config_file 'config/database.yml'
6
+ end
7
+
8
+ Sequelize.setup(ENV['DB_ENV'] || 'test')
9
+
10
+ DB = Sequelize.connection
11
+
12
+ DB.extension(:pg_enum)
@@ -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