kiroshi 0.1.0 → 0.2.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.
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kiroshi::FilterQuery::Exact, type: :model do
6
+ describe '#apply' do
7
+ subject(:query) { described_class.new(filter_runner) }
8
+
9
+ let(:filter_runner) { Kiroshi::FilterRunner.new(filter: filter, scope: scope, value: filter_value) }
10
+ let(:filter) { Kiroshi::Filter.new(:name, match: :exact) }
11
+ let(:scope) { Document.all }
12
+ let(:filter_value) { 'test_document' }
13
+
14
+ let!(:matching_document) { create(:document, name: 'test_document') }
15
+ let!(:non_matching_document) { create(:document, name: 'other_document') }
16
+
17
+ let(:expected_sql) do
18
+ <<~SQL.squish
19
+ SELECT "documents".* FROM "documents" WHERE "documents"."name" = 'test_document'
20
+ SQL
21
+ end
22
+
23
+ it 'returns records that exactly match the filter value' do
24
+ expect(query.apply).to include(matching_document)
25
+ end
26
+
27
+ it 'does not return records that do not exactly match' do
28
+ expect(query.apply).not_to include(non_matching_document)
29
+ end
30
+
31
+ it 'generates correct SQL with exact equality' do
32
+ expect(query.apply.to_sql).to eq(expected_sql)
33
+ end
34
+
35
+ context 'when filtering by status attribute' do
36
+ let(:filter) { Kiroshi::Filter.new(:status, match: :exact) }
37
+ let(:filter_value) { 'published' }
38
+
39
+ let!(:published_document) { create(:document, status: 'published') }
40
+ let!(:draft_document) { create(:document, status: 'draft') }
41
+
42
+ let(:expected_sql) do
43
+ <<~SQL.squish
44
+ SELECT "documents".* FROM "documents" WHERE "documents"."status" = 'published'
45
+ SQL
46
+ end
47
+
48
+ it 'returns documents with exact status match' do
49
+ expect(query.apply).to include(published_document)
50
+ end
51
+
52
+ it 'does not return documents without exact status match' do
53
+ expect(query.apply).not_to include(draft_document)
54
+ end
55
+
56
+ it 'generates correct SQL for status filtering' do
57
+ expect(query.apply.to_sql).to eq(expected_sql)
58
+ end
59
+ end
60
+
61
+ context 'when filtering with numeric values' do
62
+ let(:filter) { Kiroshi::Filter.new(:priority, match: :exact) }
63
+ let(:filter_value) { 1 }
64
+
65
+ let!(:high_priority_document) { create(:document, priority: 1) }
66
+ let!(:medium_priority_document) { create(:document, priority: 2) }
67
+
68
+ let(:expected_sql) do
69
+ <<~SQL.squish
70
+ SELECT "documents".* FROM "documents" WHERE "documents"."priority" = 1
71
+ SQL
72
+ end
73
+
74
+ it 'returns documents with exact numeric match' do
75
+ expect(query.apply).to include(high_priority_document)
76
+ end
77
+
78
+ it 'does not return documents without exact numeric match' do
79
+ expect(query.apply).not_to include(medium_priority_document)
80
+ end
81
+
82
+ it 'generates correct SQL for numeric filtering' do
83
+ expect(query.apply.to_sql).to eq(expected_sql)
84
+ end
85
+ end
86
+
87
+ context 'when filtering with boolean values' do
88
+ let(:filter) { Kiroshi::Filter.new(:active, match: :exact) }
89
+ let(:filter_value) { true }
90
+
91
+ let!(:active_document) { create(:document, active: true) }
92
+ let!(:inactive_document) { create(:document, active: false) }
93
+
94
+ let(:expected_sql) do
95
+ <<~SQL.squish
96
+ SELECT "documents".* FROM "documents" WHERE "documents"."active" = 1
97
+ SQL
98
+ end
99
+
100
+ it 'returns documents with exact boolean match' do
101
+ expect(query.apply).to include(active_document)
102
+ end
103
+
104
+ it 'does not return documents without exact boolean match' do
105
+ expect(query.apply).not_to include(inactive_document)
106
+ end
107
+
108
+ it 'generates correct SQL for boolean filtering' do
109
+ expect(query.apply.to_sql).to eq(expected_sql)
110
+ end
111
+ end
112
+
113
+ context 'when no records match' do
114
+ let(:filter_value) { 'nonexistent_value' }
115
+
116
+ let(:expected_sql) do
117
+ <<~SQL.squish
118
+ SELECT "documents".* FROM "documents" WHERE "documents"."name" = 'nonexistent_value'
119
+ SQL
120
+ end
121
+
122
+ it 'returns empty relation' do
123
+ expect(query.apply).to be_empty
124
+ end
125
+
126
+ it 'still generates valid SQL' do
127
+ expect(query.apply.to_sql).to eq(expected_sql)
128
+ end
129
+ end
130
+
131
+ context 'with case sensitivity' do
132
+ let(:filter_value) { 'Test_Document' }
133
+ let!(:lowercase_document) { create(:document, name: 'test_document') }
134
+ let!(:uppercase_document) { create(:document, name: 'TEST_DOCUMENT') }
135
+ let!(:mixedcase_document) { create(:document, name: 'Test_Document') }
136
+
137
+ it 'includes documents with exact case match' do
138
+ expect(query.apply).to include(mixedcase_document)
139
+ end
140
+
141
+ it 'excludes documents with lowercase' do
142
+ expect(query.apply).not_to include(lowercase_document)
143
+ end
144
+
145
+ it 'excludes documents with upcase' do
146
+ expect(query.apply).not_to include(uppercase_document)
147
+ end
148
+ end
149
+
150
+ context 'when filter has table configured' do
151
+ let(:scope) { Document.joins(:tags) }
152
+ let(:filter_value) { 'ruby' }
153
+
154
+ let!(:first_tag) { Tag.find_or_create_by(name: 'ruby') }
155
+ let!(:second_tag) { Tag.find_or_create_by(name: 'javascript') }
156
+
157
+ let!(:document_with_ruby_tag) { create(:document, name: 'My Document') }
158
+ let!(:document_with_js_tag) { create(:document, name: 'JS Guide') }
159
+ let!(:document_without_tag) { create(:document, name: 'Other Document') }
160
+
161
+ before do
162
+ Tag.find_or_create_by(name: 'programming')
163
+ document_with_ruby_tag.tags << [first_tag]
164
+ document_with_js_tag.tags << [second_tag]
165
+ end
166
+
167
+ context 'when filtering by tags table' do
168
+ let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: :tags) }
169
+
170
+ let(:expected_sql) do
171
+ <<~SQL.squish
172
+ SELECT "documents".* FROM "documents"#{' '}
173
+ INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
174
+ INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
175
+ WHERE "tags"."name" = 'ruby'
176
+ SQL
177
+ end
178
+
179
+ it 'returns documents with tags that exactly match the filter value' do
180
+ expect(query.apply).to include(document_with_ruby_tag)
181
+ end
182
+
183
+ it 'does not return documents with tags that do not exactly match' do
184
+ expect(query.apply).not_to include(document_with_js_tag)
185
+ end
186
+
187
+ it 'does not return documents without matching tags' do
188
+ expect(query.apply).not_to include(document_without_tag)
189
+ end
190
+
191
+ it 'generates SQL with tags table qualification' do
192
+ expect(query.apply.to_sql).to eq(expected_sql)
193
+ end
194
+ end
195
+
196
+ context 'when filtering by documents table explicitly' do
197
+ let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: :documents) }
198
+ let(:filter_value) { 'JS Guide' }
199
+
200
+ let(:expected_sql) do
201
+ <<~SQL.squish
202
+ SELECT "documents".* FROM "documents"#{' '}
203
+ INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
204
+ INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
205
+ WHERE "documents"."name" = 'JS Guide'
206
+ SQL
207
+ end
208
+
209
+ it 'returns documents that exactly match the filter value in document name' do
210
+ expect(query.apply).to include(document_with_js_tag)
211
+ end
212
+
213
+ it 'does not return documents that do not exactly match document name' do
214
+ expect(query.apply).not_to include(document_with_ruby_tag)
215
+ end
216
+
217
+ it 'does not return documents without exact document name match' do
218
+ expect(query.apply).not_to include(document_without_tag)
219
+ end
220
+
221
+ it 'generates SQL with documents table qualification' do
222
+ expect(query.apply.to_sql).to eq(expected_sql)
223
+ end
224
+ end
225
+
226
+ context 'when table is specified as string' do
227
+ let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: 'tags') }
228
+
229
+ let(:expected_sql) do
230
+ <<~SQL.squish
231
+ SELECT "documents".* FROM "documents"#{' '}
232
+ INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
233
+ INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
234
+ WHERE "tags"."name" = 'ruby'
235
+ SQL
236
+ end
237
+
238
+ it 'works the same as with symbol table name' do
239
+ expect(query.apply).to include(document_with_ruby_tag)
240
+ end
241
+
242
+ it 'generates SQL with string table qualification' do
243
+ expect(query.apply.to_sql).to eq(expected_sql)
244
+ end
245
+ end
246
+
247
+ context 'when filtering by different attributes with table qualification' do
248
+ let(:filter) { Kiroshi::Filter.new(:id, match: :exact, table: :tags) }
249
+ let(:filter_value) { first_tag.id }
250
+
251
+ let(:expected_sql) do
252
+ <<~SQL.squish
253
+ SELECT "documents".* FROM "documents"#{' '}
254
+ INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
255
+ INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
256
+ WHERE "tags"."id" = #{first_tag.id}
257
+ SQL
258
+ end
259
+
260
+ it 'returns documents with tags that match the tag id' do
261
+ expect(query.apply).to include(document_with_ruby_tag)
262
+ end
263
+
264
+ it 'does not return documents without the matching tag id' do
265
+ expect(query.apply).not_to include(document_with_js_tag)
266
+ end
267
+
268
+ it 'generates SQL with tags table qualification for id attribute' do
269
+ expect(query.apply.to_sql).to eq(expected_sql)
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kiroshi::FilterQuery::Like, type: :model do
6
+ describe '#apply' do
7
+ subject(:query) { described_class.new(filter_runner) }
8
+
9
+ let(:filter_runner) { Kiroshi::FilterRunner.new(filter: filter, scope: scope, value: filter_value) }
10
+ let(:filter) { Kiroshi::Filter.new(:name, match: :like) }
11
+ let(:scope) { Document.all }
12
+ let(:filter_value) { 'test' }
13
+
14
+ let!(:matching_document) { create(:document, name: 'test_document') }
15
+ let!(:another_match) { create(:document, name: 'my_test_file') }
16
+ let!(:non_matching_document) { create(:document, name: 'other_document') }
17
+
18
+ let(:expected_sql) do
19
+ <<~SQL.squish
20
+ SELECT "documents".* FROM "documents" WHERE (documents.name LIKE '%test%')
21
+ SQL
22
+ end
23
+
24
+ it 'returns records that partially match the filter value' do
25
+ expect(query.apply).to include(matching_document)
26
+ end
27
+
28
+ it 'returns multiple records that contain the filter value' do
29
+ expect(query.apply).to include(another_match)
30
+ end
31
+
32
+ it 'does not return records that do not contain the filter value' do
33
+ expect(query.apply).not_to include(non_matching_document)
34
+ end
35
+
36
+ it 'generates correct SQL with LIKE operation' do
37
+ expect(query.apply.to_sql).to eq(expected_sql)
38
+ end
39
+
40
+ context 'when filtering by status attribute' do
41
+ let(:filter) { Kiroshi::Filter.new(:status, match: :like) }
42
+ let(:filter_value) { 'pub' }
43
+
44
+ let!(:published_document) { create(:document, status: 'published') }
45
+ let!(:republished_document) { create(:document, status: 'republished') }
46
+ let!(:draft_document) { create(:document, status: 'draft') }
47
+
48
+ let(:expected_sql) do
49
+ <<~SQL.squish
50
+ SELECT "documents".* FROM "documents" WHERE (documents.status LIKE '%pub%')
51
+ SQL
52
+ end
53
+
54
+ it 'returns documents with partial status match' do
55
+ expect(query.apply).to include(published_document)
56
+ end
57
+
58
+ it 'returns documents with partial match in different positions' do
59
+ expect(query.apply).to include(republished_document)
60
+ end
61
+
62
+ it 'does not return documents without partial status match' do
63
+ expect(query.apply).not_to include(draft_document)
64
+ end
65
+
66
+ it 'generates correct SQL for status filtering' do
67
+ expect(query.apply.to_sql).to eq(expected_sql)
68
+ end
69
+ end
70
+
71
+ context 'when filtering with numeric values as strings' do
72
+ let(:filter) { Kiroshi::Filter.new(:version, match: :like) }
73
+ let(:filter_value) { '1.2' }
74
+
75
+ let!(:version_match) { create(:document, version: '1.2.3') }
76
+ let!(:another_version) { create(:document, version: '2.1.2') }
77
+ let!(:different_version) { create(:document, version: '3.0.0') }
78
+
79
+ let(:expected_sql) do
80
+ <<~SQL.squish
81
+ SELECT "documents".* FROM "documents" WHERE (documents.version LIKE '%1.2%')
82
+ SQL
83
+ end
84
+
85
+ it 'returns documents with partial numeric match' do
86
+ expect(query.apply).to include(version_match)
87
+ end
88
+
89
+ it 'returns documents with partial match in different positions' do
90
+ expect(query.apply).to include(another_version)
91
+ end
92
+
93
+ it 'does not return documents without partial numeric match' do
94
+ expect(query.apply).not_to include(different_version)
95
+ end
96
+
97
+ it 'generates correct SQL for numeric string filtering' do
98
+ expect(query.apply.to_sql).to eq(expected_sql)
99
+ end
100
+ end
101
+
102
+ context 'when no records match' do
103
+ let(:filter_value) { 'nonexistent' }
104
+
105
+ let(:expected_sql) do
106
+ <<~SQL.squish
107
+ SELECT "documents".* FROM "documents" WHERE (documents.name LIKE '%nonexistent%')
108
+ SQL
109
+ end
110
+
111
+ it 'returns empty relation' do
112
+ expect(query.apply).to be_empty
113
+ end
114
+
115
+ it 'still generates valid SQL' do
116
+ expect(query.apply.to_sql).to eq(expected_sql)
117
+ end
118
+ end
119
+
120
+ context 'with case sensitivity' do
121
+ let(:filter_value) { 'Test' }
122
+ let!(:lowercase_document) { create(:document, name: 'test_document') }
123
+ let!(:uppercase_document) { create(:document, name: 'TEST_FILE') }
124
+ let!(:mixedcase_document) { create(:document, name: 'Test_Document') }
125
+ let!(:no_match_document) { create(:document, name: 'example') }
126
+
127
+ it 'includes documents with exact case match' do
128
+ expect(query.apply).to include(mixedcase_document)
129
+ end
130
+
131
+ it 'includes documents with lowercase match' do
132
+ expect(query.apply).to include(lowercase_document)
133
+ end
134
+
135
+ it 'includes documents with uppercase match' do
136
+ expect(query.apply).to include(uppercase_document)
137
+ end
138
+
139
+ it 'excludes documents without any case match' do
140
+ expect(query.apply).not_to include(no_match_document)
141
+ end
142
+ end
143
+
144
+ context 'with special characters in filter value' do
145
+ let(:filter_value) { 'user@' }
146
+ let!(:email_document) { create(:document, name: 'user@example.com') }
147
+ let!(:partial_email_document) { create(:document, name: 'admin_user@test.org') }
148
+ let!(:no_match_document) { create(:document, name: 'username') }
149
+
150
+ it 'includes documents with special character match' do
151
+ expect(query.apply).to include(email_document)
152
+ end
153
+
154
+ it 'includes documents with partial special character match' do
155
+ expect(query.apply).to include(partial_email_document)
156
+ end
157
+
158
+ it 'excludes documents without special character match' do
159
+ expect(query.apply).not_to include(no_match_document)
160
+ end
161
+ end
162
+
163
+ context 'with single character filter' do
164
+ let(:filter_value) { 'a' }
165
+ let!(:start_match) { create(:document, name: 'apple') }
166
+ let!(:middle_match) { create(:document, name: 'banana') }
167
+ let!(:end_match) { create(:document, name: 'extra') }
168
+ let!(:no_match_document) { create(:document, name: 'test') }
169
+
170
+ it 'includes documents with character at start' do
171
+ expect(query.apply).to include(start_match)
172
+ end
173
+
174
+ it 'includes documents with character in middle' do
175
+ expect(query.apply).to include(middle_match)
176
+ end
177
+
178
+ it 'includes documents with character at end' do
179
+ expect(query.apply).to include(end_match)
180
+ end
181
+
182
+ it 'excludes documents without the character' do
183
+ expect(query.apply).not_to include(no_match_document)
184
+ end
185
+ end
186
+
187
+ context 'when filter has table configured' do
188
+ let(:scope) { Document.joins(:tags) }
189
+ let(:filter_value) { 'ruby' }
190
+
191
+ let!(:first_tag) { Tag.find_or_create_by(name: 'ruby') }
192
+ let!(:second_tag) { Tag.find_or_create_by(name: 'ruby_on_rails') }
193
+
194
+ let!(:document_with_ruby_tag) { create(:document, name: 'My Document') }
195
+ let!(:document_with_rails_tag) { create(:document, name: 'Rails Guide') }
196
+ let!(:document_without_tag) { create(:document, name: 'Other Document') }
197
+
198
+ before do
199
+ Tag.find_or_create_by(name: 'programming')
200
+ document_with_ruby_tag.tags << [first_tag]
201
+ document_with_rails_tag.tags << [second_tag]
202
+ end
203
+
204
+ context 'when filtering by tags table' do
205
+ let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: :tags) }
206
+
207
+ it 'returns documents with tags that partially match the filter value' do
208
+ expect(query.apply).to include(document_with_ruby_tag)
209
+ end
210
+
211
+ it 'returns documents with tags that contain the filter value' do
212
+ expect(query.apply).to include(document_with_rails_tag)
213
+ end
214
+
215
+ it 'does not return documents without matching tags' do
216
+ expect(query.apply).not_to include(document_without_tag)
217
+ end
218
+
219
+ it 'generates SQL with tags table qualification' do
220
+ result_sql = query.apply.to_sql
221
+ expect(result_sql).to include('tags.name LIKE')
222
+ end
223
+
224
+ it 'generates SQL with correct LIKE pattern for tag name' do
225
+ result_sql = query.apply.to_sql
226
+ expect(result_sql).to include("'%ruby%'")
227
+ end
228
+ end
229
+
230
+ context 'when filtering by documents table explicitly' do
231
+ let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: :documents) }
232
+ let(:filter_value) { 'Guide' }
233
+
234
+ it 'returns documents that partially match the filter value in document name' do
235
+ expect(query.apply).to include(document_with_rails_tag)
236
+ end
237
+
238
+ it 'does not return documents that do not match document name' do
239
+ expect(query.apply).not_to include(document_with_ruby_tag)
240
+ end
241
+
242
+ it 'does not return documents without matching document name' do
243
+ expect(query.apply).not_to include(document_without_tag)
244
+ end
245
+
246
+ it 'generates SQL with documents table qualification' do
247
+ result_sql = query.apply.to_sql
248
+ expect(result_sql).to include('documents.name LIKE')
249
+ end
250
+
251
+ it 'generates SQL with correct LIKE pattern for document name' do
252
+ result_sql = query.apply.to_sql
253
+ expect(result_sql).to include("'%Guide%'")
254
+ end
255
+ end
256
+
257
+ context 'when table is specified as string' do
258
+ let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: 'tags') }
259
+
260
+ it 'works the same as with symbol table name' do
261
+ expect(query.apply).to include(document_with_ruby_tag)
262
+ end
263
+
264
+ it 'generates SQL with string table qualification' do
265
+ result_sql = query.apply.to_sql
266
+ expect(result_sql).to include('tags.name LIKE')
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kiroshi::FilterQuery, type: :model do
6
+ describe '.for' do
7
+ context 'when match is :exact' do
8
+ it 'returns the Exact class' do
9
+ expect(described_class.for(:exact)).to eq(Kiroshi::FilterQuery::Exact)
10
+ end
11
+ end
12
+
13
+ context 'when match is :like' do
14
+ it 'returns the Like class' do
15
+ expect(described_class.for(:like)).to eq(Kiroshi::FilterQuery::Like)
16
+ end
17
+ end
18
+
19
+ context 'when match is an unsupported type' do
20
+ it 'raises ArgumentError for unsupported match type' do
21
+ expect { described_class.for(:invalid) }.to raise_error(
22
+ ArgumentError, 'Unsupported match type: invalid'
23
+ )
24
+ end
25
+
26
+ it 'raises ArgumentError for nil match type' do
27
+ expect { described_class.for(nil) }.to raise_error(
28
+ ArgumentError, 'Unsupported match type: '
29
+ )
30
+ end
31
+
32
+ it 'raises ArgumentError for string match type' do
33
+ expect { described_class.for('exact') }.to raise_error(
34
+ ArgumentError, 'Unsupported match type: exact'
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kiroshi::FilterRunner, type: :model do
6
+ describe '#apply' do
7
+ subject(:runner) { described_class.new(filter: filter, scope: scope, value: filter_value) }
8
+
9
+ let(:scope) { Document.all }
10
+ let(:filter_value) { 'test_value' }
11
+ let(:document_name) { filter_value }
12
+ let!(:matching_document) { create(:document, name: document_name) }
13
+ let!(:non_matching_document) { create(:document, name: 'other_value') }
14
+
15
+ context 'when filter match is :exact' do
16
+ let(:filter) { Kiroshi::Filter.new(:name, match: :exact) }
17
+
18
+ it 'returns exact matches' do
19
+ expect(runner.apply).to include(matching_document)
20
+ end
21
+
22
+ it 'does not return non-matching records' do
23
+ expect(runner.apply).not_to include(non_matching_document)
24
+ end
25
+ end
26
+
27
+ context 'when filter match is :like' do
28
+ let(:filter) { Kiroshi::Filter.new(:name, match: :like) }
29
+ let(:filter_value) { 'test' }
30
+ let!(:matching_document) { create(:document, name: 'test_document') }
31
+ let!(:non_matching_document) { create(:document, name: 'other_value') }
32
+
33
+ it 'returns partial matches' do
34
+ expect(runner.apply).to include(matching_document)
35
+ end
36
+
37
+ it 'does not return non-matching records' do
38
+ expect(runner.apply).not_to include(non_matching_document)
39
+ end
40
+
41
+ it 'generates correct SQL with table name prefix' do
42
+ expected_sql = "SELECT \"documents\".* FROM \"documents\" WHERE (documents.name LIKE '%test%')"
43
+ expect(runner.apply.to_sql).to eq(expected_sql)
44
+ end
45
+ end
46
+
47
+ context 'when filter match is not specified (default)' do
48
+ let(:filter) { Kiroshi::Filter.new(:name) }
49
+
50
+ it 'defaults to exact match returning only exact matches' do
51
+ expect(runner.apply).to include(matching_document)
52
+ end
53
+
54
+ it 'defaults to exact match not returning non-matching records' do
55
+ expect(runner.apply).not_to include(non_matching_document)
56
+ end
57
+ end
58
+
59
+ context 'when filter value is not present' do
60
+ let(:document_name) { 'Some name' }
61
+ let(:filter) { Kiroshi::Filter.new(:name) }
62
+ let(:filter_value) { nil }
63
+
64
+ it 'returns the original scope unchanged' do
65
+ expect(runner.apply).to eq(scope)
66
+ end
67
+ end
68
+
69
+ context 'when filter value is empty string' do
70
+ let(:document_name) { 'Some name' }
71
+ let(:filter) { Kiroshi::Filter.new(:name) }
72
+ let(:filter_value) { '' }
73
+
74
+ it 'returns the original scope unchanged' do
75
+ expect(runner.apply).to eq(scope)
76
+ end
77
+ end
78
+
79
+ context 'with status filter' do
80
+ let(:filter) { Kiroshi::Filter.new(:status, match: :exact) }
81
+ let(:filter_value) { 'finished' }
82
+ let!(:matching_document) { create(:document, name: 'test_name', status: 'finished') }
83
+ let!(:non_matching_document) { create(:document, name: 'other_name', status: 'processing') }
84
+
85
+ it 'filters by the configured attribute only returning the matched' do
86
+ expect(runner.apply).to include(matching_document)
87
+ end
88
+
89
+ it 'does not return non-matching records' do
90
+ expect(runner.apply).not_to include(non_matching_document)
91
+ end
92
+ end
93
+ end
94
+ end