File: caching_spec.rb

package info (click to toggle)
ruby-faraday-middleware 1.2.0-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 376 kB
  • sloc: ruby: 2,388; sh: 15; makefile: 6
file content (251 lines) | stat: -rw-r--r-- 7,891 bytes parent folder | download
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# frozen_string_literal: true

require 'rack/cache'

RSpec.describe FaradayMiddleware::Caching do
  let(:caching_lint_middleware) do
    Struct.new(:app) do
      def call(env)
        app.call(env).on_complete do
          raise 'no headers' unless env[:response_headers].is_a? Hash
          raise 'no response' unless env[:response].is_a? Faraday::Response
          # raise "env not identical" unless env[:response].env.object_id == env.object_id
        end
      end
    end
  end

  let(:test_cache_class) do
    Class.new(Hash) do
      def read(key)
        cached = self[key]
        return unless cached

        Marshal.load(cached)
      end

      def write(key, data, _options = nil)
        self[key] = Marshal.dump(data)
      end

      def fetch(key)
        read(key) || yield.tap { |data| write(key, data) }
      end
    end
  end

  before do
    @cache = test_cache_class.new
    request_count = 0
    response = lambda { |_env|
      [200, { 'Content-Type' => 'text/plain' }, "request:#{request_count += 1}"]
    }
    not_found = lambda { |_env|
      [404, { 'Content-Type' => 'text/plain' }, "request:#{request_count += 1}"]
    }
    broken = lambda { |_env|
      [500, { 'Content-Type' => 'text/plain' }, "request:#{request_count += 1}"]
    }
    @conn = Faraday.new do |b|
      b.use caching_lint_middleware
      b.use FaradayMiddleware::Caching, @cache, options
      b.adapter :test do |stub|
        stub.get('/', &response)
        stub.get('/?foo=bar', &response)
        stub.post('/', &response)
        stub.get('/other', &response)
        stub.get('/broken', &broken)
        stub.get('http://www.site-a.com/test', &response)
        stub.get('http://www.site-b.com/test', &response)
        stub.get('/not_found', &not_found)
      end
    end
  end

  let(:options) { {} }

  extend Forwardable
  def_delegators :@conn, :get, :post

  it 'caches get requests' do
    expect(get('/').body).to eq('request:1')
    expect(get('/').body).to eq('request:1')
    expect(get('/other').body).to eq('request:2')
    expect(get('/other').body).to eq('request:2')
  end

  it 'includes request params in the response' do
    get('/') # make cache
    response = get('/')
    expect(response.env[:method]).to eq(:get)
    expect(response.env[:url].request_uri).to eq('/')
  end

  it 'caches requests with query params' do
    expect(get('/').body).to eq('request:1')
    expect(get('/?foo=bar').body).to eq('request:2')
    expect(get('/?foo=bar').body).to eq('request:2')
    expect(get('/').body).to eq('request:1')
  end

  it 'does not cache post requests' do
    expect(post('/').body).to eq('request:1')
    expect(post('/').body).to eq('request:2')
    expect(post('/').body).to eq('request:3')
  end

  it 'does not cache responses with invalid status code' do
    expect(get('/broken').body).to eq('request:1')
    expect(get('/broken').body).to eq('request:2')
  end

  context ':cacheable_status_code' do
    let(:options) { { status_codes: %w[404] } }

    it 'caches requests based on defined cacheable_status_code' do
      expect(get('/').body).to eq('request:1')
      expect(get('/not_found').body).to eq('request:2')
      expect(get('/').body).to eq('request:3')
      expect(get('/not_found').body).to eq('request:2')
    end

    context 'with invalid :cacheable_status_code status' do
      let(:options) { { status_codes: %w[404,500] } }

      it 'caches requests based on valid defined cacheable_status_code' do
        expect(get('/not_found').body).to eq('request:1')
        expect(get('/broken').body).to eq('request:2')
        expect(get('/not_found').body).to eq('request:1')
        expect(get('/broken').body).to eq('request:3')
      end
    end

    context 'with no valid :cacheable_status_code status' do
      let(:options) { { status_codes: %w[500] } }
      it 'caches requests based on default cacheable_status_code' do
        expect(get('/').body).to eq('request:1')
        expect(get('/broken').body).to eq('request:2')
        expect(get('/').body).to eq('request:1')
        expect(get('/not_found').body).to eq('request:3')
        expect(get('/not_found').body).to eq('request:3')
        expect(get('/broken').body).to eq('request:4')
      end
    end
  end

  context ':ignore_params' do
    let(:options) { { ignore_params: %w[utm_source utm_term] } }

    it 'strips ignored parameters from cache_key' do
      expect(get('/').body).to eq('request:1')
      expect(get('/?utm_source=a').body).to eq('request:1')
      expect(get('/?utm_source=a&utm_term=b').body).to eq('request:1')
      expect(get('/?utm_source=a&utm_term=b&foo=bar').body).to eq('request:2')
      expect(get('/?foo=bar').body).to eq('request:2')
    end
  end

  context ':full_key' do
    let(:options) { { full_key: true } }

    it 'use full URL as cache key' do
      expect(get('http://www.site-a.com/test').body).to eq('request:1')
      expect(get('http://www.site-b.com/test').body).to eq('request:2')
    end
  end

  context ':write_options' do
    let(:options) { { write_options: { expires_in: 9000 } } }

    it 'passes on the options when writing to the cache' do
      expect(@cache).to receive(:write).with(Digest::SHA1.hexdigest('/'),
                                             instance_of(Faraday::Response),
                                             options[:write_options])
      get('/')
    end

    context 'with no :write_options' do
      let(:options) { {} }

      it "doesn't pass a third options parameter to the cache's #write" do
        expect(@cache).to receive(:write).with(Digest::SHA1.hexdigest('/'), instance_of(Faraday::Response))
        get('/')
      end
    end
  end
end

# RackCompatible + Rack::Cache
RSpec.describe FaradayMiddleware::RackCompatible, 'caching' do
  include FileUtils

  let(:cache_dir) { File.expand_path('../tmp/cache', __dir__) }

  let(:rack_errors_complainer_middleware) do
    # middleware to check whether "rack.errors" is free of error reports
    Struct.new(:app) do
      def call(env)
        response = app.call(env)
        error_stream = env[:rack_errors]
        if error_stream.respond_to?(:string) && error_stream.string.include?('error')
          raise format(%(unexpected error in 'rack.errors': %<error>p), error: error_stream.string)
        end

        response
      end
    end
  end

  before do
    rm_r(cache_dir) if File.exist?(cache_dir)
    # force reinitializing cache dirs
    Rack::Cache::Storage.instance.clear

    request_count = 0
    response = lambda { |_env|
      [200,
       { 'Content-Type' => 'text/plain',
         'Cache-Control' => 'public, max-age=900' },
       "request:#{request_count += 1}"]
    }

    @conn = Faraday.new do |b|
      b.use rack_errors_complainer_middleware

      b.use FaradayMiddleware::RackCompatible, Rack::Cache::Context,
            metastore: "file:#{cache_dir}/rack/meta",
            entitystore: "file:#{cache_dir}/rack/body",
            verbose: true

      b.adapter :test do |stub|
        stub.get('/', &response)
        stub.post('/', &response)
      end
    end
  end

  extend Forwardable
  def_delegators :@conn, :get, :post

  it 'caches get requests' do
    response = get('/', user_agent: 'test')
    expect(response.body).to eq('request:1')
    expect(response.env[:method]).to eq(:get)
    expect(response.status).to eq(200)

    response = get('/', user_agent: 'test')
    expect(response.body).to eq('request:1')
    expect(response['content-type']).to eq('text/plain')
    expect(response.env[:method]).to eq(:get)
    expect(response.env[:request].respond_to?(:fetch)).to be true
    expect(response.status).to eq(200)

    expect(post('/').body).to eq('request:2')
  end

  it 'does not cache post requests' do
    expect(get('/').body).to eq('request:1')
    expect(post('/').body).to eq('request:2')
    expect(post('/').body).to eq('request:3')
  end
end