search_cop 1.2.2 → 1.3.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.
@@ -1,146 +0,0 @@
1
- require File.expand_path("test_helper", __dir__)
2
-
3
- class SearchCopTest < SearchCop::TestCase
4
- def test_scope_before
5
- expected = create(:product, stock: 1, title: "Title")
6
- rejected = create(:product, stock: 0, title: "Title")
7
-
8
- results = Product.where(stock: 1).search("Title")
9
-
10
- assert_includes results, expected
11
- refute_includes results, rejected
12
- end
13
-
14
- def test_scope_after
15
- expected = create(:product, stock: 1, title: "Title")
16
- rejected = create(:product, stock: 0, title: "Title")
17
-
18
- results = Product.search("Title").where(stock: 1)
19
-
20
- assert_includes results, expected
21
- refute_includes results, rejected
22
- end
23
-
24
- def test_scope
25
- expected = create(:product, stock: 1, title: "Title")
26
- rejected = create(:product, stock: 0, title: "Title")
27
-
28
- results = with_scope(Product.search_scopes[:search], -> { where stock: 1 }) { Product.search("title: Title") }
29
-
30
- assert_includes results, expected
31
- refute_includes results, rejected
32
- end
33
-
34
- def test_multi_associations
35
- product = create(:product, comments: [
36
- create(:comment, title: "Title1", message: "Message1"),
37
- create(:comment, title: "Title2", message: "Message2")
38
- ])
39
-
40
- assert_includes Product.search("comment: Title1 comment: Message1"), product
41
- assert_includes Product.search("comment: Title2 comment: Message2"), product
42
- end
43
-
44
- def test_single_association
45
- expected = create(:comment, user: create(:user, username: "Expected"))
46
- rejected = create(:comment, user: create(:user, username: "Rejected"))
47
-
48
- results = Comment.search("user: Expected")
49
-
50
- assert_includes results, expected
51
- refute_includes results, rejected
52
- end
53
-
54
- def test_deep_associations
55
- expected = create(:product, comments: [create(:comment, user: create(:user, username: "Expected"))])
56
- rejected = create(:product, comments: [create(:comment, user: create(:user, username: "Rejected"))])
57
-
58
- results = Product.search("user: Expected")
59
-
60
- assert_includes results, expected
61
- refute_includes results, rejected
62
- end
63
-
64
- def test_inherited_model
65
- expected = create(:available_product, comments: [create(:comment, user: create(:user, username: "Expected"))])
66
- rejected = create(:available_product, comments: [create(:comment, user: create(:user, username: "Rejected"))])
67
-
68
- results = AvailableProduct.search("user: Expected")
69
- assert_includes results, expected
70
- refute_includes results, rejected
71
- end
72
-
73
- def test_multiple
74
- product = create(:product, comments: [create(:comment, title: "Title", message: "Message")])
75
-
76
- assert_includes Product.search("comment: Title"), product
77
- assert_includes Product.search("comment: Message"), product
78
- end
79
-
80
- def test_default
81
- product1 = create(:product, title: "Expected")
82
- product2 = create(:product, description: "Expected")
83
-
84
- results = Product.search("Expected")
85
-
86
- assert_includes results, product1
87
- assert_includes results, product2
88
- end
89
-
90
- def test_custom_default_enabled
91
- product1 = create(:product, title: "Expected")
92
- product2 = create(:product, description: "Expected")
93
- product3 = create(:product, brand: "Expected")
94
-
95
- results = with_options(Product.search_scopes[:search], :primary, default: true) { Product.search "Expected" }
96
-
97
- assert_includes results, product1
98
- assert_includes results, product2
99
- refute_includes results, product3
100
- end
101
-
102
- def test_custom_default_disabled
103
- product1 = create(:product, brand: "Expected")
104
- product2 = create(:product, notice: "Expected")
105
-
106
- results = with_options(Product.search_scopes[:search], :notice, default: false) { Product.search "Expected" }
107
-
108
- assert_includes results, product1
109
- refute_includes results, product2
110
- end
111
-
112
- def test_count
113
- create_list :product, 2, title: "Expected"
114
-
115
- assert_equal 2, Product.search("Expected").count
116
- end
117
-
118
- def test_default_attributes_true
119
- with_options(Product.search_scopes[:search], :title, default: true) do
120
- with_options(Product.search_scopes[:search], :description, default: true) do
121
- assert_equal ["title", "description"], Product.search_scopes[:search].reflection.default_attributes.keys
122
- end
123
- end
124
- end
125
-
126
- def test_default_attributes_fales
127
- with_options(Product.search_scopes[:search], :title, default: false) do
128
- with_options(Product.search_scopes[:search], :description, default: false) do
129
- assert_equal Product.search_scopes[:search].reflection.attributes.keys - ["title", "description"], Product.search_scopes[:search].reflection.default_attributes.keys
130
- end
131
- end
132
- end
133
-
134
- def test_search_reflection
135
- assert_not_nil Product.search_reflection(:search)
136
- assert_not_nil Product.search_reflection(:user_search)
137
- end
138
-
139
- def test_blank
140
- assert_equal Product.all, Product.search("")
141
- end
142
-
143
- def test_not_adding_search_to_object
144
- refute Object.respond_to?(:search)
145
- end
146
- end
data/test/string_test.rb DELETED
@@ -1,142 +0,0 @@
1
- require File.expand_path("test_helper", __dir__)
2
-
3
- class StringTest < SearchCop::TestCase
4
- def test_anywhere
5
- product = create(:product, title: "Expected title")
6
-
7
- assert_includes Product.search("Expected"), product
8
- refute_includes Product.search("Rejected"), product
9
- end
10
-
11
- def test_anywhere_quoted
12
- product = create(:product, title: "Expected title")
13
-
14
- assert_includes Product.search("'Expected title'"), product
15
- assert_includes Product.search('"Expected title"'), product
16
-
17
- refute_includes Product.search("'Rejected title'"), product
18
- refute_includes Product.search('"Rejected title"'), product
19
- end
20
-
21
- def test_multiple
22
- product = create(:product, comments: [create(:comment, title: "Expected title", message: "Expected message")])
23
-
24
- assert_includes Product.search("Expected"), product
25
- refute_includes Product.search("Rejected"), product
26
- end
27
-
28
- def test_includes
29
- product = create(:product, title: "Expected")
30
-
31
- assert_includes Product.search("title: Expected"), product
32
- refute_includes Product.search("title: Rejected"), product
33
- end
34
-
35
- def test_query_string_wildcards
36
- product1 = create(:product, brand: "First brand")
37
- product2 = create(:product, brand: "Second brand")
38
-
39
- assert_equal Product.search("brand: First*"), [product1]
40
- assert_equal Product.search("brand: br*nd"), []
41
- assert_equal Product.search("brand: brand*"), []
42
- assert_equal Product.search("brand: *brand*").to_set, [product1, product2].to_set
43
- assert_equal Product.search("brand: *brand").to_set, [product1, product2].to_set
44
- end
45
-
46
- def test_query_string_wildcard_escaping
47
- product1 = create(:product, brand: "som% brand")
48
- product2 = create(:product, brand: "som_ brand")
49
- product3 = create(:product, brand: "som\\ brand")
50
- _product4 = create(:product, brand: "some brand")
51
-
52
- assert_equal Product.search("brand: som% brand"), [product1]
53
- assert_equal Product.search("brand: som_ brand"), [product2]
54
- assert_equal Product.search("brand: som\\ brand"), [product3]
55
- end
56
-
57
- def test_query_string_wildcards_with_left_wildcard_false
58
- product = create(:product, brand: "Some brand")
59
-
60
- with_options(Product.search_scopes[:search], :brand, left_wildcard: false) do
61
- refute_includes Product.search("brand: *brand"), product
62
- assert_includes Product.search("brand: Some"), product
63
- end
64
- end
65
-
66
- def test_query_string_wildcards_with_right_wildcard_false
67
- product = create(:product, brand: "Some brand")
68
-
69
- with_options(Product.search_scopes[:search], :brand, right_wildcard: false) do
70
- refute_includes Product.search("brand: Some*"), product
71
- assert_includes Product.search("brand: brand"), product
72
- end
73
- end
74
-
75
- def test_includes_with_left_wildcard
76
- product = create(:product, brand: "Some brand")
77
-
78
- assert_includes Product.search("brand: brand"), product
79
- end
80
-
81
- def test_includes_with_left_wildcard_false
82
- expected = create(:product, brand: "Brand")
83
- rejected = create(:product, brand: "Rejected brand")
84
-
85
- results = with_options(Product.search_scopes[:search], :brand, left_wildcard: false) { Product.search "brand: Brand" }
86
-
87
- assert_includes results, expected
88
- refute_includes results, rejected
89
- end
90
-
91
- def test_includes_with_right_wildcard_false
92
- expected = create(:product, brand: "Brand")
93
- rejected = create(:product, brand: "Brand rejected")
94
-
95
- results = with_options(Product.search_scopes[:search], :brand, right_wildcard: false) { Product.search "brand: Brand" }
96
-
97
- assert_includes results, expected
98
- refute_includes results, rejected
99
- end
100
-
101
- def test_equals
102
- product = create(:product, title: "Expected title")
103
-
104
- assert_includes Product.search("title = 'Expected title'"), product
105
- refute_includes Product.search("title = Expected"), product
106
- end
107
-
108
- def test_equals_not
109
- product = create(:product, title: "Expected")
110
-
111
- assert_includes Product.search("title != Rejected"), product
112
- refute_includes Product.search("title != Expected"), product
113
- end
114
-
115
- def test_greater
116
- product = create(:product, title: "Title B")
117
-
118
- assert_includes Product.search("title > 'Title A'"), product
119
- refute_includes Product.search("title > 'Title B'"), product
120
- end
121
-
122
- def test_greater_equals
123
- product = create(:product, title: "Title A")
124
-
125
- assert_includes Product.search("title >= 'Title A'"), product
126
- refute_includes Product.search("title >= 'Title B'"), product
127
- end
128
-
129
- def test_less
130
- product = create(:product, title: "Title A")
131
-
132
- assert_includes Product.search("title < 'Title B'"), product
133
- refute_includes Product.search("title < 'Title A'"), product
134
- end
135
-
136
- def test_less_or_greater
137
- product = create(:product, title: "Title B")
138
-
139
- assert_includes Product.search("title <= 'Title B'"), product
140
- refute_includes Product.search("title <= 'Title A'"), product
141
- end
142
- end
data/test/test_helper.rb DELETED
@@ -1,195 +0,0 @@
1
- require "search_cop"
2
-
3
- begin
4
- require "minitest"
5
-
6
- class SearchCop::TestCase < MiniTest::Test; end
7
- rescue LoadError
8
- require "minitest/unit"
9
-
10
- class SearchCop::TestCase < MiniTest::Unit::TestCase; end
11
- end
12
-
13
- require "minitest/autorun"
14
- require "active_record"
15
- require "factory_bot"
16
- require "yaml"
17
-
18
- DATABASE = ENV["DATABASE"] || "sqlite"
19
-
20
- ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("database.yml", __dir__))[DATABASE]
21
-
22
- class User < ActiveRecord::Base; end
23
-
24
- class Comment < ActiveRecord::Base
25
- include SearchCop
26
-
27
- belongs_to :user
28
-
29
- search_scope :search do
30
- attributes user: "user.username"
31
- attributes :title, :message
32
- end
33
- end
34
-
35
- class Product < ActiveRecord::Base
36
- include SearchCop
37
-
38
- search_scope :search do
39
- attributes :title, :description, :brand, :notice, :stock, :price, :created_at, :created_on, :available
40
- attributes comment: ["comments.title", "comments.message"], user: ["users.username", "users_products.username"]
41
- attributes primary: [:title, :description]
42
-
43
- aliases users_products: :user
44
-
45
- if DATABASE != "sqlite"
46
- options :title, type: :fulltext, coalesce: true
47
- options :description, type: :fulltext, coalesce: true
48
- options :comment, type: :fulltext, coalesce: true
49
- end
50
-
51
- if DATABASE == "postgres"
52
- options :title, dictionary: "english"
53
- end
54
-
55
- generator :custom_eq do |column_name, raw_value|
56
- "#{column_name} = #{quote raw_value}"
57
- end
58
- end
59
-
60
- search_scope :user_search do
61
- scope { joins "LEFT OUTER JOIN users users_products ON users_products.id = products.user_id" }
62
-
63
- attributes :title, :description
64
- attributes user: "users_products.username"
65
-
66
- options :title, default: true
67
- aliases users_products: User
68
- end
69
-
70
- search_scope :search_multi_columns do
71
- attributes all: [:title, :description]
72
- end
73
-
74
- has_many :comments
75
- has_many :users, through: :comments
76
-
77
- belongs_to :user
78
- end
79
-
80
- module SomeNamespace
81
- class Product < ActiveRecord::Base
82
- include SearchCop
83
-
84
- belongs_to :user
85
-
86
- search_scope :search do
87
- attributes :title, :description
88
- attributes user: ["user.username"]
89
- end
90
- end
91
- end
92
-
93
- class AvailableProduct < Product
94
- default_scope { where(available: true) }
95
- end
96
-
97
- FactoryBot.define do
98
- factory :product do
99
- end
100
-
101
- factory :available_product do
102
- available { true }
103
- end
104
-
105
- factory :comment do
106
- end
107
-
108
- factory :user do
109
- end
110
- end
111
-
112
- ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS products"
113
- ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS comments"
114
- ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS users"
115
-
116
- ActiveRecord::Base.connection.create_table :products do |t|
117
- t.references :user
118
- t.string :title
119
- t.text :description
120
- t.integer :stock
121
- t.float :price
122
- t.datetime :created_at
123
- t.date :created_on
124
- t.boolean :available
125
- t.string :brand
126
- t.string :notice
127
- end
128
-
129
- ActiveRecord::Base.connection.create_table :comments do |t|
130
- t.references :product
131
- t.references :user
132
- t.string :title
133
- t.text :message
134
- end
135
-
136
- ActiveRecord::Base.connection.create_table :users do |t|
137
- t.string :username
138
- end
139
-
140
- if DATABASE == "mysql"
141
- ActiveRecord::Base.connection.execute "ALTER TABLE products ENGINE=MyISAM"
142
- ActiveRecord::Base.connection.execute "ALTER TABLE products ADD FULLTEXT INDEX(title), ADD FULLTEXT INDEX(description), ADD FULLTEXT INDEX(title, description)"
143
-
144
- ActiveRecord::Base.connection.execute "ALTER TABLE comments ENGINE=MyISAM"
145
- ActiveRecord::Base.connection.execute "ALTER TABLE comments ADD FULLTEXT INDEX(title, message)"
146
- end
147
-
148
- class SearchCop::TestCase
149
- include FactoryBot::Syntax::Methods
150
-
151
- def teardown
152
- Product.delete_all
153
- Comment.delete_all
154
- end
155
-
156
- def with_options(scope, key, options = {})
157
- opts = scope.reflection.options[key.to_s] || {}
158
-
159
- scope.reflection.options[key.to_s] = opts.merge(options)
160
-
161
- yield
162
- ensure
163
- scope.reflection.options[key.to_s] = opts
164
- end
165
-
166
- def with_scope(scope, blk)
167
- orig = scope.reflection.scope
168
-
169
- scope.reflection.scope = blk
170
-
171
- yield
172
- ensure
173
- scope.reflection.scope = orig
174
- end
175
-
176
- def assert_not_nil(value)
177
- assert value
178
- end
179
-
180
- def assert_nothing_raised
181
- yield
182
- end
183
-
184
- def quote_table_name(name)
185
- ActiveRecord::Base.connection.quote_table_name name
186
- end
187
-
188
- def quote_column_name(name)
189
- ActiveRecord::Base.connection.quote_column_name name
190
- end
191
-
192
- def quote(object)
193
- ActiveRecord::Base.connection.quote object
194
- end
195
- end
data/test/visitor_test.rb DELETED
@@ -1,108 +0,0 @@
1
- require File.expand_path("test_helper", __dir__)
2
-
3
- class VisitorTest < SearchCop::TestCase
4
- def test_and
5
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(0).and(SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(2))
6
-
7
- assert_equal "(#{quote_table_name "products"}.#{quote_column_name "stock"} > 0 AND #{quote_table_name "products"}.#{quote_column_name "stock"} < 2)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
8
- end
9
-
10
- def test_or
11
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(0).or(SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(2))
12
-
13
- assert_equal "(#{quote_table_name "products"}.#{quote_column_name "stock"} > 0 OR #{quote_table_name "products"}.#{quote_column_name "stock"} < 2)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
14
- end
15
-
16
- def test_greater_than
17
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(1)
18
-
19
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} > 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
20
- end
21
-
22
- def test_greater_than_or_equal
23
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gteq(1)
24
-
25
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} >= 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
26
- end
27
-
28
- def test_less_than
29
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(1)
30
-
31
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} < 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
32
- end
33
-
34
- def test_less_than_or_equal
35
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lteq(1)
36
-
37
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} <= 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
38
- end
39
-
40
- def test_equality
41
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").eq(1)
42
-
43
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} = 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
44
- end
45
-
46
- def test_not_equal
47
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").not_eq(1)
48
-
49
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} != 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
50
- end
51
-
52
- def test_matches
53
- node = SearchCopGrammar::Attributes::String.new(Product, "products", "notice").matches("Notice")
54
-
55
- assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} LIKE #{quote "%Notice%"} ESCAPE #{quote "\\"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] != "postgres"
56
- assert_equal("(#{quote_table_name "products"}.#{quote_column_name "notice"} IS NOT NULL AND #{quote_table_name "products"}.#{quote_column_name "notice"} ILIKE #{quote "%Notice%"} ESCAPE #{quote "\\"})", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
57
- end
58
-
59
- def test_not
60
- node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").eq(1).not
61
-
62
- assert_equal "NOT (#{quote_table_name "products"}.#{quote_column_name "stock"} = 1)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
63
- end
64
-
65
- def test_attribute
66
- # Already tested
67
- end
68
-
69
- def test_quote
70
- assert_equal quote("Test"), SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit("Test")
71
- end
72
-
73
- def test_fulltext
74
- node = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query").optimize!
75
-
76
- assert_equal("MATCH(`products`.`title`) AGAINST('Query' IN BOOLEAN MODE)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
77
- assert_equal("to_tsvector('english', COALESCE(\"products\".\"title\", '')) @@ to_tsquery('english', '''Query''')", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
78
- end
79
-
80
- def test_fulltext_and
81
- query1 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query1")
82
- query2 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query2")
83
-
84
- node = query1.and(query2).optimize!
85
-
86
- assert_equal("(MATCH(`products`.`title`) AGAINST('+Query1 +Query2' IN BOOLEAN MODE))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
87
- assert_equal("(to_tsvector('english', COALESCE(\"products\".\"title\", '')) @@ to_tsquery('english', '(''Query1'') & (''Query2'')'))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
88
- end
89
-
90
- def test_fulltext_or
91
- query1 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query1")
92
- query2 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query2")
93
-
94
- node = query1.or(query2).optimize!
95
-
96
- assert_equal("(MATCH(`products`.`title`) AGAINST('(Query1) (Query2)' IN BOOLEAN MODE))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
97
- assert_equal("(to_tsvector('english', COALESCE(\"products\".\"title\", '')) @@ to_tsquery('english', '(''Query1'') | (''Query2'')'))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
98
- end
99
-
100
- def test_generator
101
- generator = lambda do |column_name, value|
102
- "#{column_name} = #{quote value}"
103
- end
104
- node = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").generator(generator, "value").optimize!
105
-
106
- assert_equal "#{quote_table_name "products"}.#{quote_column_name "title"} = 'value'", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
107
- end
108
- end