fbe 0.24.2 → 0.24.4

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: 1896cdb2e02c15259d12df1f1ecddb5049b902ff0b52dd67cb25a9c113606175
4
- data.tar.gz: b3fdf07d9b80f1b7cffb9974f09296c8f1f33eb44d88f4faa54918f747fcd0cf
3
+ metadata.gz: 92baa5e54a0df9b2e57f2d6619cfad4d4ee1b4a4150b1e30b82780062dd9bd34
4
+ data.tar.gz: ac08a108536716ff130271018211616e02b58a55dcef303bed0c0d01808387eb
5
5
  SHA512:
6
- metadata.gz: bfa0a5da515f2054d9e22c7894ea329f70894a7751dc9879c77ba074fcc07df24d70a0332771381c9698b37b83a896bd88bccd40b49877a97d35a688e858af0b
7
- data.tar.gz: f15e90b61d66b43e54bf44ecc767b7943ab5f22167203e1ea36900a1b29476a8208bbcece5f8d1c66eb9d5abf9d49d551d190fdcef7dc01c274ce749282cd218
6
+ metadata.gz: 4b87c1fefa9477f19588b01eeb7a7d593ca38f1eed068fe7d27a84a66b4cc35214f83b8f6128a169a007df962063f4ae5373442a17c61d12f1c7e876c1099ef6
7
+ data.tar.gz: ae01c566bca05de64432b34b6f27bbf82497d5d72817c5bbb0ef9f80e0f62f742681f1ae820d30bc081d891b31f926369ee1f0efe42e4b778a0223b4be26959e
data/.rubocop.yml CHANGED
@@ -56,3 +56,5 @@ Metrics/ParameterLists:
56
56
  Enabled: false
57
57
  Layout/MultilineAssignmentLayout:
58
58
  Enabled: true
59
+ Style/RequireOrder:
60
+ Enabled: true
data/Gemfile.lock CHANGED
@@ -189,7 +189,7 @@ GEM
189
189
  regexp_parser (2.10.0)
190
190
  retries (0.0.5)
191
191
  rexml (3.4.1)
192
- rubocop (1.77.0)
192
+ rubocop (1.78.0)
193
193
  json (~> 2.3)
194
194
  language_server-protocol (~> 3.17.0.2)
195
195
  lint_roller (~> 1.1.0)
data/Rakefile CHANGED
@@ -4,9 +4,9 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'qbash'
7
- require 'rubygems'
8
7
  require 'rake'
9
8
  require 'rake/clean'
9
+ require 'rubygems'
10
10
 
11
11
  def name
12
12
  @name ||= File.basename(Dir['*.gemspec'].first, '.*')
data/lib/fbe/conclude.rb CHANGED
@@ -6,8 +6,8 @@
6
6
  require 'tago'
7
7
  require_relative '../fbe'
8
8
  require_relative 'fb'
9
- require_relative 'octo'
10
9
  require_relative 'if_absent'
10
+ require_relative 'octo'
11
11
 
12
12
  # Creates an instance of {Fbe::Conclude} and evals it with the block provided.
13
13
  #
@@ -17,13 +17,13 @@ require_relative 'if_absent'
17
17
  # @param [Judges::Options] options The options coming from the +judges+ tool
18
18
  # @param [Loog] loog The logging facility
19
19
  # @yield [Factbase::Fact] The fact
20
- def Fbe.conclude(fb: Fbe.fb, judge: $judge, loog: $loog, options: $options, global: $global, &)
20
+ def Fbe.conclude(fb: Fbe.fb, judge: $judge, loog: $loog, options: $options, global: $global, time: Time, &)
21
21
  raise 'The fb is nil' if fb.nil?
22
22
  raise 'The $judge is not set' if judge.nil?
23
23
  raise 'The $global is not set' if global.nil?
24
24
  raise 'The $options is not set' if options.nil?
25
25
  raise 'The $loog is not set' if loog.nil?
26
- c = Fbe::Conclude.new(fb:, judge:, loog:, options:, global:)
26
+ c = Fbe::Conclude.new(fb:, judge:, loog:, options:, global:, time:)
27
27
  c.instance_eval(&)
28
28
  end
29
29
 
@@ -58,7 +58,8 @@ class Fbe::Conclude
58
58
  # @param [Hash] global The hash for global caching
59
59
  # @param [Judges::Options] options The options coming from the +judges+ tool
60
60
  # @param [Loog] loog The logging facility
61
- def initialize(fb:, judge:, global:, options:, loog:)
61
+ # @param [Time] time The time
62
+ def initialize(fb:, judge:, global:, options:, loog:, time: Time)
62
63
  @fb = fb
63
64
  @judge = judge
64
65
  @loog = loog
@@ -68,6 +69,7 @@ class Fbe::Conclude
68
69
  @follows = []
69
70
  @quota_aware = false
70
71
  @timeout = 60
72
+ @time = time
71
73
  end
72
74
 
73
75
  # Make this block aware of GitHub API quota.
@@ -180,22 +182,23 @@ class Fbe::Conclude
180
182
  # end
181
183
  def roll(&)
182
184
  passed = 0
183
- start = Time.now
185
+ start = @time.now
184
186
  oct = Fbe.octo(loog: @loog, options: @options, global: @global)
185
187
  @fb.query(@query).each do |a|
188
+ if @quota_aware && oct.off_quota?
189
+ @loog.debug('We ran out of GitHub quota, must stop here')
190
+ break
191
+ end
192
+ now = @time.now
193
+ if now > start + @timeout
194
+ @loog.debug("We've spent more than #{start.ago}, must stop here")
195
+ break
196
+ end
186
197
  @fb.txn do |fbt|
187
- if @quota_aware && oct.off_quota?
188
- @loog.debug('We ran out of GitHub quota, must stop here')
189
- throw :commit
190
- end
191
- if Time.now > start + @timeout
192
- @loog.debug("We've spent more than #{start.ago}, must stop here")
193
- throw :commit
194
- end
195
198
  n = yield fbt, a
196
199
  @loog.info("#{n.what}: #{n.details}") unless n.nil?
197
- passed += 1
198
200
  end
201
+ passed += 1
199
202
  end
200
203
  @loog.debug("Found and processed #{passed} facts by: #{@query}")
201
204
  passed
data/lib/fbe/fb.rb CHANGED
@@ -4,11 +4,11 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'factbase'
7
+ require 'factbase/cached/cached_factbase'
8
+ require 'factbase/indexed/indexed_factbase'
7
9
  require 'factbase/logged'
8
10
  require 'factbase/pre'
9
11
  require 'factbase/rules'
10
- require 'factbase/cached/cached_factbase'
11
- require 'factbase/indexed/indexed_factbase'
12
12
  require 'factbase/sync/sync_factbase'
13
13
  require 'judges'
14
14
  require 'loog'
data/lib/fbe/just_one.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'time'
7
6
  require 'others'
7
+ require 'time'
8
8
  require_relative '../fbe'
9
9
  require_relative 'fb'
10
10
 
@@ -3,6 +3,7 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
+ require 'ellipsized'
6
7
  require 'faraday'
7
8
  require 'faraday/logging/formatter'
8
9
  require_relative '../../fbe'
@@ -66,6 +67,21 @@ class Fbe::Middleware::Formatter < Faraday::Logging::Formatter
66
67
  )
67
68
  return
68
69
  end
70
+ if http.status >= 500 && http.response_headers['content-type']&.start_with?('text')
71
+ error(
72
+ [
73
+ "#{@req.method.upcase} #{apply_filters(@req.url.to_s)} HTTP/1.1",
74
+ shifted(apply_filters(dump_headers(@req.request_headers))),
75
+ '',
76
+ shifted(apply_filters(@req.request_body)),
77
+ "HTTP/1.1 #{http.status}",
78
+ shifted(apply_filters(dump_headers(http.response_headers))),
79
+ '',
80
+ shifted(apply_filters(http.response_body&.ellipsized(100, :right)))
81
+ ].join("\n")
82
+ )
83
+ return
84
+ end
69
85
  error(
70
86
  [
71
87
  "#{@req.method.upcase} #{apply_filters(@req.url.to_s)} HTTP/1.1",
@@ -3,12 +3,12 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'zlib'
7
6
  require 'filesize'
8
7
  require 'json'
9
8
  require 'loog'
10
9
  require 'sqlite3'
11
10
  require 'time'
11
+ require 'zlib'
12
12
  require_relative '../../fbe'
13
13
  require_relative '../../fbe/middleware'
14
14
 
@@ -53,8 +53,10 @@ class Fbe::Middleware::SqliteStore
53
53
  # @param loog [Loog] Logger instance (optional, defaults to Loog::NULL)
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
- # @raise [ArgumentError] If path is nil/empty, directory doesn't exist, or version is nil/empty
57
- def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb')
56
+ # @param ttl [Integer, nil] lifetime of keys in hours
57
+ # @raise [ArgumentError] If path is nil/empty, directory doesn't exist, version is nil/empty,
58
+ # 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)
58
60
  raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
59
61
  dir = File.dirname(path)
60
62
  raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
@@ -64,6 +66,8 @@ class Fbe::Middleware::SqliteStore
64
66
  @loog = loog
65
67
  @maxsize = Filesize.from(maxsize.to_s).to_i
66
68
  @maxvsize = Filesize.from(maxvsize.to_s).to_i
69
+ raise ArgumentError, 'TTL can be nil or Integer > 0' if !ttl.nil? && !(ttl.is_a?(Integer) && ttl.positive?)
70
+ @ttl = ttl
67
71
  end
68
72
 
69
73
  # Read a value from the cache.
@@ -106,8 +110,8 @@ class Fbe::Middleware::SqliteStore
106
110
  return if value.bytesize > @maxvsize
107
111
  perform do |t|
108
112
  t.execute(<<~SQL, [key, value, Time.now.utc.iso8601])
109
- INSERT INTO cache(key, value, touched_at) VALUES(?1, ?2, ?3)
110
- ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3
113
+ INSERT INTO cache(key, value, touched_at, created_at) VALUES(?1, ?2, ?3, ?3)
114
+ ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3, created_at = ?3
111
115
  SQL
112
116
  end
113
117
  nil
@@ -153,10 +157,29 @@ class Fbe::Middleware::SqliteStore
153
157
  SQL
154
158
  t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
155
159
  t.execute 'DROP TABLE cache_old;'
160
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
156
161
  t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
157
162
  end
158
163
  d.execute 'VACUUM;'
159
164
  end
165
+ if d.execute("SELECT 1 FROM pragma_table_info('cache') WHERE name = 'created_at';").dig(0, 0) != 1
166
+ d.transaction do |t|
167
+ t.execute 'ALTER TABLE cache ADD COLUMN created_at TEXT;'
168
+ t.execute 'UPDATE cache set created_at = ?;', [Time.now.utc.iso8601]
169
+ t.execute 'ALTER TABLE cache RENAME TO cache_old;'
170
+ t.execute <<~SQL
171
+ CREATE TABLE IF NOT EXISTS cache(
172
+ key TEXT UNIQUE NOT NULL, value TEXT, touched_at TEXT NOT NULL, created_at TEXT NOT NULL
173
+ );
174
+ SQL
175
+ t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
176
+ t.execute 'DROP TABLE cache_old;'
177
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
178
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
179
+ t.execute 'CREATE INDEX IF NOT EXISTS cache_created_at_idx ON cache(created_at);'
180
+ end
181
+ d.execute 'VACUUM;'
182
+ end
160
183
  found = d.execute("SELECT value FROM meta WHERE key = 'version' LIMIT 1;").dig(0, 0)
161
184
  if found != @version
162
185
  @loog.info("Version mismatch in SQLite cache: stored '#{found}' != current '#{@version}', cleaning up")
@@ -166,6 +189,15 @@ class Fbe::Middleware::SqliteStore
166
189
  end
167
190
  d.execute 'VACUUM;'
168
191
  end
192
+ unless @ttl.nil?
193
+ d.transaction do |t|
194
+ t.execute <<~SQL, [(Time.now.utc - (@ttl * 60 * 60)).iso8601]
195
+ DELETE FROM cache
196
+ WHERE key IN (SELECT key FROM cache WHERE (created_at < ?));
197
+ SQL
198
+ end
199
+ d.execute 'VACUUM;'
200
+ end
169
201
  if File.size(@path) > @maxsize
170
202
  @loog.info(
171
203
  "SQLite cache file size (#{Filesize.from(File.size(@path).to_s).pretty} bytes) exceeds " \
data/lib/fbe/octo.rb CHANGED
@@ -93,7 +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
- store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:)
96
+ store = Fbe::Middleware::SqliteStore.new(
97
+ options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:, ttl: 24
98
+ )
97
99
  loog.info(
98
100
  "Using HTTP cache in SQLite file: #{store.path} (" \
99
101
  "#{File.exist?(store.path) ? Filesize.from(File.size(store.path).to_s).pretty : 'file is absent'}, " \
@@ -665,7 +667,8 @@ class Fbe::FakeOctokit
665
667
  user: { login: 'yegor256', id: 526_301, type: 'User' },
666
668
  created_at: Time.parse('2025-06-01 12:00:55 UTC'),
667
669
  updated_at: Time.parse('2025-06-01 15:47:18 UTC'),
668
- closed_at: Time.parse('2025-06-02 15:00:00 UTC')
670
+ closed_at: Time.parse('2025-06-02 15:00:00 UTC'),
671
+ closed_by: { id: 526_301, login: 'yegor256' }
669
672
  }
670
673
  elsif number == 143
671
674
  {
@@ -676,7 +679,8 @@ class Fbe::FakeOctokit
676
679
  pull_request: { merged_at: nil },
677
680
  created_at: Time.parse('2025-05-29 17:00:55 UTC'),
678
681
  updated_at: Time.parse('2025-05-29 19:00:00 UTC'),
679
- closed_at: Time.parse('2025-06-01 18:20:00 UTC')
682
+ closed_at: Time.parse('2025-06-01 18:20:00 UTC'),
683
+ closed_by: { id: 526_301, login: 'yegor256' }
680
684
  }
681
685
  else
682
686
  {
@@ -708,6 +712,11 @@ class Fbe::FakeOctokit
708
712
  repo: {
709
713
  full_name: repo
710
714
  },
715
+ base: {
716
+ repo: {
717
+ full_name: repo
718
+ }
719
+ },
711
720
  state: 'closed',
712
721
  user: { login: 'yegor256', id: 526_301, type: 'User' },
713
722
  head: { ref: 'master', sha: '6dcb09b5b57875f334f61aebed695e2e4193db5e' },
@@ -716,6 +725,7 @@ class Fbe::FakeOctokit
716
725
  changed_files: 3,
717
726
  comments: 2,
718
727
  review_comments: 2,
728
+ closed_at: Time.parse('2024-12-20'),
719
729
  merged_at: Time.parse('2024-12-20'),
720
730
  created_at: Time.parse('2024-09-20')
721
731
  }
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.2' unless const_defined?(:VERSION)
13
+ VERSION = '0.24.4' unless const_defined?(:VERSION)
14
14
  end
@@ -5,10 +5,11 @@
5
5
 
6
6
  require 'faraday'
7
7
  require 'loog'
8
- require_relative '../../test__helper'
8
+ require 'securerandom'
9
9
  require_relative '../../../lib/fbe'
10
10
  require_relative '../../../lib/fbe/middleware'
11
11
  require_relative '../../../lib/fbe/middleware/formatter'
12
+ require_relative '../../test__helper'
12
13
 
13
14
  # Test.
14
15
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -49,9 +50,38 @@ class LoggingFormatterTest < Fbe::Test
49
50
  end
50
51
  end
51
52
 
53
+ def test_truncate_body_for_error_text_response
54
+ body = SecureRandom.alphanumeric(120)
55
+ log_it(
56
+ status: 502,
57
+ response_body: body,
58
+ response_headers: {
59
+ 'content-type' => 'text/html; charset=utf-8',
60
+ 'x-github-api-version-selected' => '2022-11-28'
61
+ }
62
+ ) do |loog|
63
+ str = loog.to_s
64
+ refute_empty(str)
65
+ [
66
+ %r{http://example.com},
67
+ /Authorization:/,
68
+ /some request body/,
69
+ %r{HTTP/1.1 502},
70
+ /x-github-api-version-selected: "2022-11-28"/,
71
+ %r{content-type: "text/html; charset=utf-8"},
72
+ "#{body.slice(0, 97)}..."
73
+ ].each { |ptn| assert_match(ptn, str) }
74
+ end
75
+ end
76
+
52
77
  private
53
78
 
54
- def log_it(status:, method: :get)
79
+ def log_it(
80
+ status:,
81
+ method: :get,
82
+ response_body: '{"message": "hello, world!"}',
83
+ response_headers: { 'content-type' => 'application/json', 'x-github-api-version-selected' => '2022-11-28' }
84
+ )
55
85
  loog = Loog::Buffer.new
56
86
  formatter = Fbe::Middleware::Formatter.new(logger: loog, options: {})
57
87
  formatter.request(
@@ -67,16 +97,7 @@ class LoggingFormatterTest < Fbe::Test
67
97
  )
68
98
  )
69
99
  formatter.response(
70
- Faraday::Env.from(
71
- {
72
- status:,
73
- response_body: '{"message": "hello, world!"}',
74
- response_headers: {
75
- 'content-type' => 'application/json',
76
- 'x-github-api-version-selected' => '2022-11-28'
77
- }
78
- }
79
- )
100
+ Faraday::Env.from({ status:, response_body:, response_headers: })
80
101
  )
81
102
  yield loog
82
103
  end
@@ -5,10 +5,10 @@
5
5
 
6
6
  require 'faraday'
7
7
  require 'webmock'
8
- require_relative '../../test__helper'
9
8
  require_relative '../../../lib/fbe'
10
9
  require_relative '../../../lib/fbe/middleware'
11
10
  require_relative '../../../lib/fbe/middleware/rate_limit'
11
+ require_relative '../../test__helper'
12
12
 
13
13
  # Test.
14
14
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -3,11 +3,11 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'securerandom'
7
6
  require 'qbash'
8
- require_relative '../../test__helper'
7
+ require 'securerandom'
9
8
  require_relative '../../../lib/fbe/middleware'
10
9
  require_relative '../../../lib/fbe/middleware/sqlite_store'
10
+ require_relative '../../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -237,6 +237,99 @@ class SqliteStoreTest < Fbe::Test
237
237
  end
238
238
  end
239
239
 
240
+ def test_upgrade_sqlite_schema_for_add_created_at_column
241
+ with_tmpfile('a.db') do |f|
242
+ SQLite3::Database.new(f).tap do |d|
243
+ d.execute 'CREATE TABLE IF NOT EXISTS cache(key TEXT UNIQUE NOT NULL, value TEXT, touched_at TEXT NOT NULL);'
244
+ [
245
+ ['key1', Zlib::Deflate.deflate(JSON.dump('value1')), Time.now.utc.iso8601],
246
+ ['key2', Zlib::Deflate.deflate(JSON.dump('value2')), Time.now.utc.iso8601]
247
+ ].each { d.execute 'INSERT INTO cache(key, value, touched_at) VALUES(?1, ?2, ?3);', _1 }
248
+ d.execute 'CREATE TABLE IF NOT EXISTS meta(key TEXT UNIQUE NOT NULL, value TEXT);'
249
+ d.execute "INSERT INTO meta(key, value) VALUES('version', ?);", ['0.0.1']
250
+ end
251
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog).then do |store|
252
+ assert_equal('value1', store.read('key1'))
253
+ assert_equal('value2', store.read('key2'))
254
+ rescue SQLite3::SQLException => e
255
+ assert_nil(e)
256
+ end
257
+ end
258
+ end
259
+
260
+ def test_set_correct_ttl
261
+ with_tmpfile('c.db') do |f|
262
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: nil)
263
+ refute_nil(s)
264
+ s = Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24)
265
+ refute_nil(s)
266
+ end
267
+ end
268
+
269
+ def test_set_incorrect_ttl
270
+ with_tmpfile('c.db') do |f|
271
+ ex =
272
+ assert_raises(ArgumentError) do
273
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 0)
274
+ end
275
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
276
+ ex =
277
+ assert_raises(ArgumentError) do
278
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: -10)
279
+ end
280
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
281
+ ex =
282
+ assert_raises(ArgumentError) do
283
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 10.0)
284
+ end
285
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
286
+ ex =
287
+ assert_raises(ArgumentError) do
288
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: '10')
289
+ end
290
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
291
+ ex =
292
+ assert_raises(ArgumentError) do
293
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: Object.new)
294
+ end
295
+ assert_equal('TTL can be nil or Integer > 0', ex.message)
296
+ end
297
+ end
298
+
299
+ def test_delete_keys_if_ttl_expired
300
+ with_tmpfile('c.db') do |f|
301
+ now = Time.now
302
+ Time.stub(:now, now) do
303
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
304
+ s.write('test1', 'value1')
305
+ s.write('test2', 'value2')
306
+ end
307
+ end
308
+ Time.stub(:now, now + (12 * 60 * 60)) do
309
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
310
+ s.write('test3', 'value3')
311
+ s.write('test4', 'value4')
312
+ end
313
+ end
314
+ Time.stub(:now, now + (24 * 60 * 60)) do
315
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
316
+ assert_equal('value1', s.read('test1'))
317
+ assert_equal('value2', s.read('test2'))
318
+ assert_equal('value3', s.read('test3'))
319
+ assert_equal('value4', s.read('test4'))
320
+ end
321
+ end
322
+ Time.stub(:now, now + (24 * 60 * 60) + 1) do
323
+ Fbe::Middleware::SqliteStore.new(f, '0.0.1', loog: fake_loog, ttl: 24).then do |s|
324
+ assert_nil(s.read('test1'))
325
+ assert_nil(s.read('test2'))
326
+ assert_equal('value3', s.read('test3'))
327
+ assert_equal('value4', s.read('test4'))
328
+ end
329
+ end
330
+ end
331
+ end
332
+
240
333
  private
241
334
 
242
335
  def with_tmpfile(name = 'test.db', &)
@@ -6,10 +6,10 @@
6
6
  require 'faraday'
7
7
  require 'faraday/http_cache'
8
8
  require 'webmock'
9
- require_relative '../../test__helper'
10
9
  require_relative '../../../lib/fbe'
11
10
  require_relative '../../../lib/fbe/middleware'
12
11
  require_relative '../../../lib/fbe/middleware/trace'
12
+ require_relative '../../test__helper'
13
13
 
14
14
  # Test.
15
15
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -125,4 +125,48 @@ class TestConclude < Fbe::Test
125
125
  end
126
126
  assert_equal(2, fb.size)
127
127
  end
128
+
129
+ def test_stop_if_timeout_exceeded
130
+ $fb = Factbase.new
131
+ $fb.insert.then do |f|
132
+ f._id = 1
133
+ f.foo = 5
134
+ end
135
+ $fb.insert.then do |f|
136
+ f._id = 2
137
+ f.foo = 4
138
+ end
139
+ $fb.insert.then do |f|
140
+ f._id = 3
141
+ f.bar = 3
142
+ end
143
+ $fb.insert.then do |f|
144
+ f._id = 4
145
+ f.foo = 2
146
+ end
147
+ $fb.insert.then do |f|
148
+ f._id = 5
149
+ f.foo = 1
150
+ end
151
+ $global = {}
152
+ $options = Judges::Options.new({ 'testing' => true })
153
+ $loog = Loog::NULL
154
+ $judge = ''
155
+ total = 0
156
+ now = Time.now
157
+ time = Minitest::Mock.new
158
+ time.expect(:now, now)
159
+ time.expect(:now, now + 4)
160
+ time.expect(:now, now + 8)
161
+ time.expect(:now, now + 12)
162
+ Fbe.conclude(time: time) do
163
+ on '(exists foo)'
164
+ timeout 10
165
+ consider do |f|
166
+ total += f.foo
167
+ end
168
+ end
169
+ assert_equal(9, total)
170
+ time.verify
171
+ end
128
172
  end
@@ -4,10 +4,10 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'judges/options'
7
- require 'webmock/minitest'
8
7
  require 'loog'
9
- require_relative '../test__helper'
8
+ require 'webmock/minitest'
10
9
  require_relative '../../lib/fbe/github_graph'
10
+ require_relative '../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -3,11 +3,11 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require 'tmpdir'
7
6
  require 'factbase'
8
7
  require 'loog'
9
- require_relative '../test__helper'
8
+ require 'tmpdir'
10
9
  require_relative '../../lib/fbe/if_absent'
10
+ require_relative '../test__helper'
11
11
 
12
12
  # Test.
13
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -4,8 +4,8 @@
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
6
  require 'factbase'
7
- require_relative '../test__helper'
8
7
  require_relative '../../lib/fbe/just_one'
8
+ require_relative '../test__helper'
9
9
 
10
10
  # Test.
11
11
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -662,4 +662,51 @@ class TestOcto < Fbe::Test
662
662
  https://github.com/octokit/octokit.rb/blob/ea3413c3174571e87c83d358fc893cc7613091fa/lib/octokit/connection.rb#L109-L119
663
663
  MSG
664
664
  end
665
+
666
+ def test_octo_cache_still_available_on_duration_of_age
667
+ WebMock.disable_net_connect!
668
+ now = Time.now
669
+ age = 60
670
+ stub_request(:get, 'https://api.github.com/rate_limit')
671
+ .to_return(
672
+ status: 200, headers: { 'Content-Type' => 'application/json', 'X-RateLimit-Remaining' => '5000' },
673
+ body: { 'rate' => { 'limit' => 5000, 'remaining' => 5000, 'reset' => 1_672_531_200 } }.to_json
674
+ )
675
+ Dir.mktmpdir do |dir|
676
+ sqlite_cache = File.expand_path('t.db', dir)
677
+ o = Fbe.octo(loog: fake_loog, global: {}, options: Judges::Options.new({ 'sqlite_cache' => sqlite_cache }))
678
+ stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
679
+ status: 200,
680
+ body: { id: 798_641_472, name: 'factbase' }.to_json,
681
+ headers: {
682
+ 'Date' => now.httpdate,
683
+ 'Content-Type' => 'application/json; charset=utf-8',
684
+ 'Cache-Control' => "public, max-age=#{age}, s-maxage=#{age}",
685
+ 'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487bc"',
686
+ 'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
687
+ }
688
+ ).times(1).then.to_raise('no more request to /repositories/798641472')
689
+ assert_equal('factbase', o.repo(798_641_472)['name'])
690
+ Time.stub(:now, now + age - 1) do
691
+ assert_equal('factbase', o.repo(798_641_472)['name'])
692
+ end
693
+ stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
694
+ status: 200,
695
+ body: { id: 798_641_472, name: 'factbase_changed' }.to_json,
696
+ headers: {
697
+ 'Date' => (now + age).httpdate,
698
+ 'Content-Type' => 'application/json; charset=utf-8',
699
+ 'Cache-Control' => "public, max-age=#{age}, s-maxage=#{age}",
700
+ 'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487be"',
701
+ 'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
702
+ }
703
+ ).times(1).then.to_raise('no more request to /repositories/798641472')
704
+ Time.stub(:now, now + age) do
705
+ assert_equal('factbase_changed', o.repo(798_641_472)['name'])
706
+ end
707
+ Time.stub(:now, now + (2 * age) - 1) do
708
+ assert_equal('factbase_changed', o.repo(798_641_472)['name'])
709
+ end
710
+ end
711
+ end
665
712
  end
@@ -5,8 +5,8 @@
5
5
 
6
6
  require 'judges/options'
7
7
  require 'loog'
8
- require_relative '../test__helper'
9
8
  require_relative '../../lib/fbe/unmask_repos'
9
+ require_relative '../test__helper'
10
10
 
11
11
  # Test.
12
12
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
data/test/test__helper.rb CHANGED
@@ -32,8 +32,8 @@ require 'minitest/reporters'
32
32
  Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
33
33
 
34
34
  require 'loog'
35
- require 'minitest/stub_const'
36
35
  require 'minitest/autorun'
36
+ require 'minitest/stub_const'
37
37
  require 'webmock/minitest'
38
38
  require_relative '../lib/fbe'
39
39
 
data/test/test_fbe.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
4
  # SPDX-License-Identifier: MIT
5
5
 
6
- require_relative 'test__helper'
7
6
  require_relative '../lib/fbe'
7
+ require_relative 'test__helper'
8
8
 
9
9
  # Main module test.
10
10
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
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.2
4
+ version: 0.24.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko