fbe 0.24.4 → 0.25.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92baa5e54a0df9b2e57f2d6619cfad4d4ee1b4a4150b1e30b82780062dd9bd34
4
- data.tar.gz: ac08a108536716ff130271018211616e02b58a55dcef303bed0c0d01808387eb
3
+ metadata.gz: 67cf152b4e3772a5ebd732741963baa7e6b02ced09fe0dfb372f3f5035edfb50
4
+ data.tar.gz: c9d7838bead7f87e11289bcd92ae84cd460d9f19ea875977326559ee4264b8c5
5
5
  SHA512:
6
- metadata.gz: 4b87c1fefa9477f19588b01eeb7a7d593ca38f1eed068fe7d27a84a66b4cc35214f83b8f6128a169a007df962063f4ae5373442a17c61d12f1c7e876c1099ef6
7
- data.tar.gz: ae01c566bca05de64432b34b6f27bbf82497d5d72817c5bbb0ef9f80e0f62f742681f1ae820d30bc081d891b31f926369ee1f0efe42e4b778a0223b4be26959e
6
+ metadata.gz: fb39e6b666c46b43b483f5075d99ca36fac1086577df736a624bf109487fef976da1e2c8fc61fd05aff252c6bae270e269b7f47ff9bde433b53d1b2d9f19c57f
7
+ data.tar.gz: ec66f9a5dab0b3f402881870516b07b1cc8d70fa282a6b020a880793ec1631e9a310158915b9b8ecff2a077fd8339d569d28fd7ae069dc4f8c488da75fc86bf6
data/Gemfile CHANGED
@@ -17,7 +17,7 @@ gem 'rubocop-minitest', '~>0.38', require: false
17
17
  gem 'rubocop-performance', '~>1.25', require: false
18
18
  gem 'rubocop-rake', '~>0.7', require: false
19
19
  gem 'simplecov', '~>0.22', require: false
20
- gem 'simplecov-cobertura', '~>2.1', require: false
20
+ gem 'simplecov-cobertura', '~>3.0', require: false
21
21
  gem 'veils', '~>0.4', require: false
22
22
  gem 'webmock', '~>3.25', require: false
23
23
  gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -223,7 +223,7 @@ GEM
223
223
  docile (~> 1.1)
224
224
  simplecov-html (~> 0.11)
225
225
  simplecov_json_formatter (~> 0.1)
226
- simplecov-cobertura (2.1.0)
226
+ simplecov-cobertura (3.0.0)
227
227
  rexml
228
228
  simplecov (~> 0.19)
229
229
  simplecov-html (0.13.1)
@@ -277,7 +277,7 @@ DEPENDENCIES
277
277
  rubocop-performance (~> 1.25)
278
278
  rubocop-rake (~> 0.7)
279
279
  simplecov (~> 0.22)
280
- simplecov-cobertura (~> 2.1)
280
+ simplecov-cobertura (~> 3.0)
281
281
  veils (~> 0.4)
282
282
  webmock (~> 3.25)
283
283
  yard (~> 0.9)
@@ -54,9 +54,10 @@ class Fbe::Middleware::SqliteStore
54
54
  # @param maxsize [Integer] Maximum database size in bytes (optional, defaults to 10MB)
55
55
  # @param maxvsize [Integer] Maximum size in bytes of a single value (optional, defaults to 10Kb)
56
56
  # @param ttl [Integer, nil] lifetime of keys in hours
57
+ # @param cache_min_age [Integer, nil] age which will could be overwritten in cache-control header
57
58
  # @raise [ArgumentError] If path is nil/empty, directory doesn't exist, version is nil/empty,
58
59
  # or ttl is not nil or not Integer or not positive
59
- def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb', ttl: nil)
60
+ def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb', ttl: nil, cache_min_age: nil)
60
61
  raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
61
62
  dir = File.dirname(path)
62
63
  raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
@@ -68,6 +69,10 @@ class Fbe::Middleware::SqliteStore
68
69
  @maxvsize = Filesize.from(maxvsize.to_s).to_i
69
70
  raise ArgumentError, 'TTL can be nil or Integer > 0' if !ttl.nil? && !(ttl.is_a?(Integer) && ttl.positive?)
70
71
  @ttl = ttl
72
+ if !cache_min_age.nil? && !(cache_min_age.is_a?(Integer) && cache_min_age.positive?)
73
+ raise ArgumentError, 'Cache min age can be nil or Integer > 0'
74
+ end
75
+ @cache_min_age = cache_min_age
71
76
  end
72
77
 
73
78
  # Read a value from the cache.
@@ -106,6 +111,26 @@ class Fbe::Middleware::SqliteStore
106
111
  req = JSON.parse(vv[0])
107
112
  req['method'] != 'get'
108
113
  end
114
+ if @cache_min_age && value.is_a?(Array) && value[0].is_a?(Array) && value[0].size > 1
115
+ begin
116
+ resp = JSON.parse(value[0][1])
117
+ rescue TypeError, JSON::ParserError => e
118
+ @loog.info("Failed to parse response to rewrite the cache age: #{e.message}")
119
+ resp = nil
120
+ end
121
+ cache_control = resp.dig('response_headers', 'cache-control') if resp.is_a?(Hash)
122
+ if cache_control && !cache_control.empty?
123
+ %w[max-age s-maxage].each do |key|
124
+ age = cache_control.scan(/#{key}=(\d+)/i).first&.first&.to_i
125
+ if age
126
+ age = [age, @cache_min_age].max
127
+ cache_control = cache_control.sub(/#{key}=(\d+)/, "#{key}=#{age}")
128
+ end
129
+ end
130
+ resp['response_headers']['cache-control'] = cache_control
131
+ value[0][1] = JSON.dump(resp)
132
+ end
133
+ end
109
134
  value = Zlib::Deflate.deflate(JSON.dump(value))
110
135
  return if value.bytesize > @maxvsize
111
136
  perform do |t|
data/lib/fbe/octo.rb CHANGED
@@ -93,8 +93,9 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
93
93
  if options.sqlite_cache
94
94
  maxsize = Filesize.from(options.sqlite_cache_maxsize || '100M').to_i
95
95
  maxvsize = Filesize.from(options.sqlite_cache_maxvsize || '100K').to_i
96
+ cache_min_age = options.sqlite_cache_min_age&.to_i
96
97
  store = Fbe::Middleware::SqliteStore.new(
97
- options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:, ttl: 24
98
+ options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:, ttl: 24, cache_min_age:
98
99
  )
99
100
  loog.info(
100
101
  "Using HTTP cache in SQLite file: #{store.path} (" \
@@ -665,6 +666,7 @@ class Fbe::FakeOctokit
665
666
  number:,
666
667
  repo: { full_name: repo },
667
668
  user: { login: 'yegor256', id: 526_301, type: 'User' },
669
+ state: 'closed',
668
670
  created_at: Time.parse('2025-06-01 12:00:55 UTC'),
669
671
  updated_at: Time.parse('2025-06-01 15:47:18 UTC'),
670
672
  closed_at: Time.parse('2025-06-02 15:00:00 UTC'),
@@ -677,6 +679,7 @@ class Fbe::FakeOctokit
677
679
  repo: { full_name: repo },
678
680
  user: { login: 'yegor256', id: 526_301, type: 'User' },
679
681
  pull_request: { merged_at: nil },
682
+ state: 'closed',
680
683
  created_at: Time.parse('2025-05-29 17:00:55 UTC'),
681
684
  updated_at: Time.parse('2025-05-29 19:00:00 UTC'),
682
685
  closed_at: Time.parse('2025-06-01 18:20:00 UTC'),
data/lib/fbe.rb CHANGED
@@ -10,5 +10,5 @@
10
10
  # License:: MIT
11
11
  module Fbe
12
12
  # Current version of the gem (changed by +.rultor.yml+ on every release)
13
- VERSION = '0.24.4' unless const_defined?(:VERSION)
13
+ VERSION = '0.25.1' unless const_defined?(:VERSION)
14
14
  end
@@ -330,6 +330,94 @@ class SqliteStoreTest < Fbe::Test
330
330
  end
331
331
  end
332
332
 
333
+ def test_set_correct_cache_min_age
334
+ with_tmpfile('c.db') do |f|
335
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age: nil)
336
+ refute_nil(s)
337
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age: 600)
338
+ refute_nil(s)
339
+ end
340
+ end
341
+
342
+ def test_set_incorrect_cache_min_age
343
+ with_tmpfile('c.db') do |f|
344
+ msg = 'Cache min age can be nil or Integer > 0'
345
+ [0, -50, 120.0, '120', Object.new].each do |cache_min_age|
346
+ ex =
347
+ assert_raises(ArgumentError) do
348
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age:)
349
+ end
350
+ assert_equal(msg, ex.message)
351
+ end
352
+ end
353
+ end
354
+
355
+ def test_not_overwrite_cache_control
356
+ with_tmpfile('t.db') do |f|
357
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age: 30).then do |store|
358
+ store.write(
359
+ 'test1',
360
+ faraday_value(resp: { 'response_headers' => { 'cache-control' => 'public, max-age=60, s-maxage=60' } })
361
+ )
362
+ store.write(
363
+ 'test2',
364
+ faraday_value(resp: { 'response_headers' => { 'cache-control' => 'public, max-age=30, s-maxage=30' } })
365
+ )
366
+ store.write(
367
+ 'test3',
368
+ faraday_value(resp: { 'response_headers' => { 'content-type' => 'application/json; charset=utf-8' } })
369
+ )
370
+ store.write('test4', faraday_value(resp: { 'status' => 200, 'body' => '{"some":"value"}' }))
371
+ store.write('test5', faraday_value(resp: {}))
372
+ store.write('test6', [[JSON.dump({ 'method' => 'get' }), 1]])
373
+ store.write('test7', faraday_value(resp: 'some string'))
374
+ store.write('test8', faraday_value(resp: nil))
375
+ end
376
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog).then do |store|
377
+ assert_equal(
378
+ 'public, max-age=60, s-maxage=60',
379
+ JSON.parse(store.read('test1')[0][1]).dig('response_headers', 'cache-control')
380
+ )
381
+ assert_equal(
382
+ 'public, max-age=30, s-maxage=30',
383
+ JSON.parse(store.read('test2')[0][1]).dig('response_headers', 'cache-control')
384
+ )
385
+ assert_nil(JSON.parse(store.read('test3')[0][1]).dig('response_headers', 'cache-control'))
386
+ assert_nil(JSON.parse(store.read('test4')[0][1]).dig('response_headers', 'cache-control'))
387
+ assert_nil(JSON.parse(store.read('test5')[0][1]).dig('response_headers', 'cache-control'))
388
+ assert_equal(1, store.read('test6')[0][1])
389
+ assert_equal(JSON.dump('some string'), store.read('test7')[0][1])
390
+ assert_nil(store.read('test8')[0][1])
391
+ end
392
+ end
393
+ end
394
+
395
+ def test_overwrite_cache_control
396
+ with_tmpfile('t.db') do |f|
397
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age: 300).then do |store|
398
+ store.write(
399
+ 'test1',
400
+ faraday_value(resp: { 'response_headers' => { 'cache-control' => 'public, max-age=60, s-maxage=60' } })
401
+ )
402
+ end
403
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, cache_min_age: 1555).then do |store|
404
+ store.write(
405
+ 'test2',
406
+ faraday_value(resp: { 'response_headers' => { 'cache-control' => 'public, max-age=60, s-maxage=60' } })
407
+ )
408
+ end
409
+ store = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog)
410
+ assert_equal(
411
+ 'public, max-age=300, s-maxage=300',
412
+ JSON.parse(store.read('test1')[0][1]).dig('response_headers', 'cache-control')
413
+ )
414
+ assert_equal(
415
+ 'public, max-age=1555, s-maxage=1555',
416
+ JSON.parse(store.read('test2')[0][1]).dig('response_headers', 'cache-control')
417
+ )
418
+ end
419
+ end
420
+
333
421
  private
334
422
 
335
423
  def with_tmpfile(name = 'test.db', &)
@@ -337,4 +425,22 @@ class SqliteStoreTest < Fbe::Test
337
425
  yield File.expand_path(name, dir)
338
426
  end
339
427
  end
428
+
429
+ def faraday_value(
430
+ req: {
431
+ 'method' => 'get',
432
+ 'url' => 'https://example.com/test',
433
+ 'headers' => { 'Content-Type' => 'application/json' }
434
+ },
435
+ resp: {
436
+ 'status' => 200,
437
+ 'body' => '{"some":"value"}',
438
+ 'response_headers' => { 'content-type' => 'application/json; charset=utf-8' }
439
+ }
440
+ )
441
+ value = []
442
+ value << JSON.dump(req) if req
443
+ value << JSON.dump(resp) if resp
444
+ [value]
445
+ end
340
446
  end
@@ -342,6 +342,7 @@ class TestOcto < Fbe::Test
342
342
  id: 655,
343
343
  number: 142,
344
344
  user: { login: 'yegor256', id: 526_301, type: 'User' },
345
+ state: 'closed',
345
346
  created_at: Time,
346
347
  updated_at: Time,
347
348
  closed_at: Time
@@ -354,6 +355,7 @@ class TestOcto < Fbe::Test
354
355
  id: 656,
355
356
  number: 143,
356
357
  user: { login: 'yegor256', id: 526_301, type: 'User' },
358
+ state: 'closed',
357
359
  pull_request: { merged_at: nil },
358
360
  created_at: Time,
359
361
  updated_at: Time,
@@ -709,4 +711,53 @@ class TestOcto < Fbe::Test
709
711
  end
710
712
  end
711
713
  end
714
+
715
+ def test_octo_with_set_sqlite_cache_min_age
716
+ WebMock.disable_net_connect!
717
+ now = Time.now
718
+ stub_request(:get, 'https://api.github.com/rate_limit')
719
+ .to_return(
720
+ status: 200, headers: { 'Content-Type' => 'application/json', 'X-RateLimit-Remaining' => '5000' },
721
+ body: { 'rate' => { 'limit' => 5000, 'remaining' => 5000, 'reset' => 1_672_531_200 } }.to_json
722
+ )
723
+ Dir.mktmpdir do |dir|
724
+ sqlite_cache = File.expand_path('t.db', dir)
725
+ options = Judges::Options.new({ 'sqlite_cache' => sqlite_cache, 'sqlite_cache_min_age' => 120 })
726
+ o = Fbe.octo(loog: fake_loog, global: {}, options:)
727
+ stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
728
+ status: 200,
729
+ body: { id: 798_641_472, name: 'factbase' }.to_json,
730
+ headers: {
731
+ 'Date' => now.httpdate,
732
+ 'Content-Type' => 'application/json; charset=utf-8',
733
+ 'Cache-Control' => 'public, max-age=60, s-maxage=60',
734
+ 'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487bc"',
735
+ 'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
736
+ }
737
+ ).times(1).then.to_raise('no more request to /repositories/798641472')
738
+ Time.stub(:now, now) do
739
+ assert_equal('factbase', o.repo(798_641_472)['name'])
740
+ end
741
+ Time.stub(:now, now + 50) do
742
+ assert_equal('factbase', o.repo(798_641_472)['name'])
743
+ end
744
+ Time.stub(:now, now + 100) do
745
+ assert_equal('factbase', o.repo(798_641_472)['name'])
746
+ end
747
+ stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
748
+ status: 200,
749
+ body: { id: 798_641_472, name: 'factbase_changed' }.to_json,
750
+ headers: {
751
+ 'Date' => (now + 120).httpdate,
752
+ 'Content-Type' => 'application/json; charset=utf-8',
753
+ 'Cache-Control' => 'public, max-age=60, s-maxage=60',
754
+ 'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487bc"',
755
+ 'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
756
+ }
757
+ )
758
+ Time.stub(:now, now + 120) do
759
+ assert_equal('factbase_changed', o.repo(798_641_472)['name'])
760
+ end
761
+ end
762
+ end
712
763
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fbe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.4
4
+ version: 0.25.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko