Skip to content

Commit 70c84a1

Browse files
committed
Add missing specs for untested source files
1 parent f71abc3 commit 70c84a1

File tree

3 files changed

+294
-2
lines changed

3 files changed

+294
-2
lines changed

spec/chewy/search/scoping_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
end
2323

2424
it 'pops scope even when block raises' do
25-
expect {
25+
expect do
2626
request.scoping { raise 'boom' }
27-
}.to raise_error('boom')
27+
end.to raise_error('boom')
2828

2929
expect(Chewy::Search::Request.scopes).to be_empty
3030
end
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
require 'spec_helper'
2+
3+
if defined?(Sidekiq)
4+
require 'sidekiq/testing'
5+
6+
describe Chewy::Strategy::DelayedSidekiq::Scheduler do
7+
before do
8+
stub_model(:city)
9+
10+
stub_index(:cities) do
11+
index_scope City
12+
end
13+
end
14+
15+
let(:default_config) do
16+
Struct.new(:latency, :margin, :ttl, :reindex_wrapper).new(
17+
nil, nil, nil, ->(&reindex) { reindex.call }
18+
)
19+
end
20+
21+
before do
22+
allow(CitiesIndex).to receive(:strategy_config).and_return(
23+
Struct.new(:delayed_sidekiq).new(default_config)
24+
)
25+
end
26+
27+
describe '#postpone' do
28+
let(:redis) { Redis.new }
29+
30+
before do
31+
allow(Sidekiq).to receive(:redis).and_yield(redis)
32+
Chewy::Strategy::DelayedSidekiq.clear_timechunks!
33+
end
34+
35+
it 'schedules a Sidekiq job' do
36+
Timecop.freeze do
37+
expect(Sidekiq::Client).to receive(:push).with(
38+
hash_including(
39+
'queue' => 'chewy',
40+
'class' => Chewy::Strategy::DelayedSidekiq::Worker,
41+
'args' => ['CitiesIndex', an_instance_of(Integer)]
42+
)
43+
)
44+
described_class.new(CitiesIndex, [1, 2]).postpone
45+
end
46+
end
47+
48+
it 'does not schedule a second job within the same time window' do
49+
Timecop.freeze do
50+
expect(Sidekiq::Client).to receive(:push).once
51+
described_class.new(CitiesIndex, [1]).postpone
52+
described_class.new(CitiesIndex, [2]).postpone
53+
end
54+
end
55+
56+
it 'uses custom queue from settings' do
57+
allow(Chewy).to receive(:settings).and_return(sidekiq: {queue: 'low'})
58+
59+
Timecop.freeze do
60+
expect(Sidekiq::Client).to receive(:push).with(
61+
hash_including('queue' => 'low')
62+
)
63+
described_class.new(CitiesIndex, [1]).postpone
64+
end
65+
end
66+
67+
it 'schedules at time = at + margin' do
68+
Timecop.freeze do
69+
expect(Sidekiq::Client).to receive(:push) do |payload|
70+
latency = described_class::DEFAULT_LATENCY
71+
margin = described_class::DEFAULT_MARGIN
72+
expected_at = latency.seconds.from_now.to_f
73+
expected_at = (expected_at - (expected_at % latency)).to_i
74+
expect(payload['at']).to eq(expected_at + margin)
75+
end
76+
described_class.new(CitiesIndex, [1]).postpone
77+
end
78+
end
79+
end
80+
81+
describe 'serialization' do
82+
it 'serializes ids and fallback fields' do
83+
scheduler = described_class.new(CitiesIndex, [1, 2, 3])
84+
expect(scheduler.send(:serialize_data)).to eq('1,2,3;all')
85+
end
86+
87+
it 'serializes ids with update_fields' do
88+
scheduler = described_class.new(CitiesIndex, [1, 2], update_fields: %w[name rating])
89+
expect(scheduler.send(:serialize_data)).to eq('1,2;name,rating')
90+
end
91+
end
92+
93+
describe 'key generation' do
94+
it 'generates timechunks_key from type name' do
95+
scheduler = described_class.new(CitiesIndex, [1])
96+
expect(scheduler.send(:timechunks_key)).to eq('chewy:delayed_sidekiq:CitiesIndex:timechunks')
97+
end
98+
99+
it 'generates timechunk_key from type name and time' do
100+
scheduler = described_class.new(CitiesIndex, [1])
101+
key = scheduler.send(:timechunk_key)
102+
expect(key).to start_with('chewy:delayed_sidekiq:CitiesIndex:')
103+
expect(key).not_to end_with(':timechunks')
104+
end
105+
end
106+
107+
describe 'config defaults' do
108+
it 'uses DEFAULT_LATENCY when config has nil latency' do
109+
scheduler = described_class.new(CitiesIndex, [1])
110+
expect(scheduler.send(:latency)).to eq(described_class::DEFAULT_LATENCY)
111+
end
112+
113+
it 'uses DEFAULT_MARGIN when config has nil margin' do
114+
scheduler = described_class.new(CitiesIndex, [1])
115+
expect(scheduler.send(:margin)).to eq(described_class::DEFAULT_MARGIN)
116+
end
117+
118+
it 'uses DEFAULT_TTL when config has nil ttl' do
119+
scheduler = described_class.new(CitiesIndex, [1])
120+
expect(scheduler.send(:ttl)).to eq(described_class::DEFAULT_TTL)
121+
end
122+
end
123+
124+
describe 'custom config' do
125+
let(:custom_config) do
126+
Struct.new(:latency, :margin, :ttl, :reindex_wrapper).new(
127+
60, 5, 3600, ->(&reindex) { reindex.call }
128+
)
129+
end
130+
131+
before do
132+
allow(CitiesIndex).to receive(:strategy_config).and_return(
133+
Struct.new(:delayed_sidekiq).new(custom_config)
134+
)
135+
end
136+
137+
it 'uses custom latency' do
138+
scheduler = described_class.new(CitiesIndex, [1])
139+
expect(scheduler.send(:latency)).to eq(60)
140+
end
141+
142+
it 'uses custom margin' do
143+
scheduler = described_class.new(CitiesIndex, [1])
144+
expect(scheduler.send(:margin)).to eq(5)
145+
end
146+
147+
it 'uses custom ttl' do
148+
scheduler = described_class.new(CitiesIndex, [1])
149+
expect(scheduler.send(:ttl)).to eq(3600)
150+
end
151+
end
152+
153+
describe 'time chunking' do
154+
it 'returns the same value for calls within the same latency window' do
155+
# Freeze at start of a latency window to avoid boundary flakes
156+
Timecop.freeze(Time.at((Time.now.to_i / 10) * 10)) do
157+
scheduler1 = described_class.new(CitiesIndex, [1])
158+
at1 = scheduler1.send(:at)
159+
160+
Timecop.travel(1.second) do
161+
scheduler2 = described_class.new(CitiesIndex, [2])
162+
at2 = scheduler2.send(:at)
163+
expect(at1).to eq(at2)
164+
end
165+
end
166+
end
167+
168+
it 'returns different values for calls in different latency windows' do
169+
Timecop.freeze do
170+
scheduler1 = described_class.new(CitiesIndex, [1])
171+
at1 = scheduler1.send(:at)
172+
173+
Timecop.travel(described_class::DEFAULT_LATENCY.seconds) do
174+
scheduler2 = described_class.new(CitiesIndex, [2])
175+
at2 = scheduler2.send(:at)
176+
expect(at1).not_to eq(at2)
177+
end
178+
end
179+
end
180+
end
181+
end
182+
end
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
require 'spec_helper'
2+
3+
if defined?(Sidekiq)
4+
require 'sidekiq/testing'
5+
require 'redis'
6+
7+
describe Chewy::Strategy::DelayedSidekiq::Worker do
8+
before do
9+
stub_model(:city) do
10+
update_index('cities') { self }
11+
end
12+
13+
stub_index(:cities) do
14+
index_scope City
15+
end
16+
17+
redis = Redis.new
18+
allow(Sidekiq).to receive(:redis).and_yield(redis)
19+
Sidekiq::Worker.clear_all
20+
Chewy::Strategy::DelayedSidekiq.clear_timechunks!
21+
end
22+
23+
around { |example| Chewy.strategy(:bypass) { example.run } }
24+
25+
describe '#extract_ids_and_fields' do
26+
subject { described_class.new }
27+
28+
it 'parses single member with fallback fields' do
29+
ids, fields = subject.send(:extract_ids_and_fields, ['1,2,3;all'])
30+
expect(ids).to match_array(%w[1 2 3])
31+
expect(fields).to be_nil
32+
end
33+
34+
it 'parses single member with specific fields' do
35+
ids, fields = subject.send(:extract_ids_and_fields, ['1,2;name,rating'])
36+
expect(ids).to match_array(%w[1 2])
37+
expect(fields).to match_array(%w[name rating])
38+
end
39+
40+
it 'merges multiple members with union of ids' do
41+
members = ['1,2;name', '2,3;name']
42+
ids, fields = subject.send(:extract_ids_and_fields, members)
43+
expect(ids).to match_array(%w[1 2 3])
44+
expect(fields).to match_array(%w[name])
45+
end
46+
47+
it 'merges multiple members with union of fields' do
48+
members = ['1;name', '2;rating']
49+
ids, fields = subject.send(:extract_ids_and_fields, members)
50+
expect(ids).to match_array(%w[1 2])
51+
expect(fields).to match_array(%w[name rating])
52+
end
53+
54+
it 'returns nil fields when any member has fallback' do
55+
members = ['1;name', '2;all']
56+
ids, fields = subject.send(:extract_ids_and_fields, members)
57+
expect(ids).to match_array(%w[1 2])
58+
expect(fields).to be_nil
59+
end
60+
61+
it 'handles empty members' do
62+
ids, fields = subject.send(:extract_ids_and_fields, [])
63+
expect(ids).to eq([])
64+
expect(fields).to eq([])
65+
end
66+
end
67+
68+
describe '#perform' do
69+
let(:city) { City.create!(name: 'London') }
70+
71+
it 'imports records via the index' do
72+
Timecop.freeze do
73+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id])
74+
scheduler.postpone
75+
76+
expect(CitiesIndex).to receive(:import!).with([city.id.to_s])
77+
described_class.drain
78+
end
79+
end
80+
81+
it 'passes update_fields when present' do
82+
Timecop.freeze do
83+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(
84+
CitiesIndex, [city.id], update_fields: ['name']
85+
)
86+
scheduler.postpone
87+
88+
expect(CitiesIndex).to receive(:import!).with(
89+
[city.id.to_s], update_fields: ['name']
90+
)
91+
described_class.drain
92+
end
93+
end
94+
95+
it 'sets refresh: false when disable_refresh_async is true' do
96+
allow(Chewy).to receive(:disable_refresh_async).and_return(true)
97+
98+
Timecop.freeze do
99+
scheduler = Chewy::Strategy::DelayedSidekiq::Scheduler.new(CitiesIndex, [city.id])
100+
scheduler.postpone
101+
102+
expect(CitiesIndex).to receive(:import!).with(
103+
[city.id.to_s], refresh: false
104+
)
105+
described_class.drain
106+
end
107+
end
108+
end
109+
end
110+
end

0 commit comments

Comments
 (0)