aboutsummaryrefslogtreecommitdiffstats
path: root/spec/services/retry_service_spec.rb
blob: 22957b565fe398b329bec514527a0b3cb66e9124 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
RSpec.describe RetryService do
  subject { described_class.new delays: [2, 3], rescue_from: [NameError, ArgumentError] }

  context 'no retry necessary' do
    before do
      expect( subject ).not_to receive(:sleep)
    end

    it 'returns a tuple :ok and the result' do
      expect( subject.execute { 42 } ).to eq([:ok, 42])
    end
    it 'does not fail on nil' do
      expect( subject.execute { nil } ).to eq([:ok, nil])
    end

    it 'fails wihout retries if raising un unregistered exception' do
      expect{ subject.execute{ raise KeyError } }.to raise_error(KeyError)
    end

  end

  context 'all retries fail' do
    before do
      expect( subject ).to receive(:sleep).with(2)
      expect( subject ).to receive(:sleep).with(3)
    end
    it 'fails after raising a registered exception n times' do
      result = subject.execute{ raise ArgumentError }
      expect( result.first ).to eq(:error)
      expect( result.last ).to be_kind_of(ArgumentError)
    end
    it 'fails with an explicit try again (automatically registered exception)'  do
      result = subject.execute{ raise RetryService::Retry }
      expect( result.first ).to eq(:error)
      expect( result.last ).to be_kind_of(RetryService::Retry)
    end
  end

  context "if at first you don't succeed" do
    before do
      @count = 0
      expect( subject ).to receive(:sleep).with(2)
    end

    it 'succeds the second time' do
      expect( subject.execute{ succeed_later(ArgumentError){ 42 } } ).to eq([:ok, 42])
    end

    it 'succeeds the second time with try again (automatically registered exception)' do
      expect( subject.execute{ succeed_later(RetryService::Retry){ 42 } } ).to eq([:ok, 42])
    end
  end

  context 'last chance' do
    before do
      @count = 0
      expect( subject ).to receive(:sleep).with(2)
      expect( subject ).to receive(:sleep).with(3)
    end
    it 'succeeds the third time with try again (automatically registered exception)' do
      expect( subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } } ).to eq([:ok, 42])
    end
  end

  context 'failure callback once' do 
    before do
      @failures = 0
      @count    = 0
      expect( subject ).to receive(:sleep).with(2)
      subject.register_failure_callback { |reason, count| @reason=reason; @callback_count=count; @failures += 1 }
    end
    it 'succeeds the second time and calls the failure_callback once' do
      subject.execute{ succeed_later(RetryService::Retry){ 42 } }
      expect( @failures ).to eq(1)
    end
    it '... and the failure is passed into the callback' do
      subject.execute{ succeed_later(RetryService::Retry){ 42 } }
      expect( @reason ).to be_a(RetryService::Retry)
      expect( @callback_count ).to eq(1)
    end
  end

  context 'failure callback twice' do
    before do
      @failures = 0
      @count    = 0
      expect( subject ).to receive(:sleep).with(2)
      expect( subject ).to receive(:sleep).with(3)
      subject.register_failure_callback { @failures += 1 }
    end
    it 'succeeds the third time and calls the failure_callback twice' do
      subject.execute{ succeed_later(NameError, count: 2){ 42 } }
      expect( @failures ).to eq(2)
    end
  end

  context 'failure callback in constructor' do
    subject do
      described_class.new(delays: [1, 2], &method(:add2failures))
    end
    before do
      @failures = []
      @count    = 0
      expect( subject ).to receive(:sleep).with(1)
      expect( subject ).to receive(:sleep).with(2)
    end
    it 'succeeds the second time and calls the failure_callback once' do
      subject.execute{ succeed_later(RetryService::Retry, count: 2){ 42 } }
      expect( @failures ).to eq([1,2])
    end
  end

  def add2failures( e, c)
    @failures << c 
  end

  def succeed_later error, count: 1, &blk
    return blk.() unless @count < count
    @count += 1
    raise error, 'error'
  end
end