fbe 0.24.3 → 0.25.0
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 +4 -4
- data/.rubocop.yml +2 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/Rakefile +1 -1
- data/lib/fbe/conclude.rb +1 -1
- data/lib/fbe/fb.rb +2 -2
- data/lib/fbe/just_one.rb +1 -1
- data/lib/fbe/middleware/formatter.rb +16 -0
- data/lib/fbe/middleware/sqlite_store.rb +62 -5
- data/lib/fbe/octo.rb +4 -1
- data/lib/fbe.rb +1 -1
- data/test/fbe/middleware/test_formatter.rb +33 -12
- data/test/fbe/middleware/test_rate_limit.rb +1 -1
- data/test/fbe/middleware/test_sqlite_store.rb +201 -2
- data/test/fbe/middleware/test_trace.rb +1 -1
- data/test/fbe/test_github_graph.rb +2 -2
- data/test/fbe/test_if_absent.rb +2 -2
- data/test/fbe/test_just_one.rb +1 -1
- data/test/fbe/test_octo.rb +49 -0
- data/test/fbe/test_unmask_repos.rb +1 -1
- data/test/test__helper.rb +1 -1
- data/test/test_fbe.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7615e3d4257252fcc987acca6caeb717c328a79edfd563eb67e704be741cef2
|
4
|
+
data.tar.gz: 3a256df746d860b44a2d6f3d44cad34690dce87ca9743227b1e114edfcdff5bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bf3c8e87ea0f0bc52a51d7f89c60bc1dc5e67b78eccac3ce9de0ac80f73406b4b8fec508d7c83c10b42a07fbe594505d3a5cddc80fd8a35e00c292a80eaf911
|
7
|
+
data.tar.gz: 2b4b12a737a6f810d304727f649faa7dceaf224c317c7d79d01705af5442a9510ede60d9f63f9afc433b6b3ce3a4bd49a7c30934d2ee9b6c90d26aebbb1a9d84
|
data/.rubocop.yml
CHANGED
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', '~>
|
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 (
|
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 (~>
|
280
|
+
simplecov-cobertura (~> 3.0)
|
281
281
|
veils (~> 0.4)
|
282
282
|
webmock (~> 3.25)
|
283
283
|
yard (~> 0.9)
|
data/Rakefile
CHANGED
data/lib/fbe/conclude.rb
CHANGED
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,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,11 @@ 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
|
-
# @
|
57
|
-
|
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
|
58
|
+
# @raise [ArgumentError] If path is nil/empty, directory doesn't exist, version is nil/empty,
|
59
|
+
# or ttl is not nil or not Integer or not positive
|
60
|
+
def initialize(path, version, loog: Loog::NULL, maxsize: '10Mb', maxvsize: '10Kb', ttl: nil, cache_min_age: nil)
|
58
61
|
raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
|
59
62
|
dir = File.dirname(path)
|
60
63
|
raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
|
@@ -64,6 +67,12 @@ class Fbe::Middleware::SqliteStore
|
|
64
67
|
@loog = loog
|
65
68
|
@maxsize = Filesize.from(maxsize.to_s).to_i
|
66
69
|
@maxvsize = Filesize.from(maxvsize.to_s).to_i
|
70
|
+
raise ArgumentError, 'TTL can be nil or Integer > 0' if !ttl.nil? && !(ttl.is_a?(Integer) && ttl.positive?)
|
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
|
67
76
|
end
|
68
77
|
|
69
78
|
# Read a value from the cache.
|
@@ -102,12 +111,32 @@ class Fbe::Middleware::SqliteStore
|
|
102
111
|
req = JSON.parse(vv[0])
|
103
112
|
req['method'] != 'get'
|
104
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
|
105
134
|
value = Zlib::Deflate.deflate(JSON.dump(value))
|
106
135
|
return if value.bytesize > @maxvsize
|
107
136
|
perform do |t|
|
108
137
|
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
|
138
|
+
INSERT INTO cache(key, value, touched_at, created_at) VALUES(?1, ?2, ?3, ?3)
|
139
|
+
ON CONFLICT(key) DO UPDATE SET value = ?2, touched_at = ?3, created_at = ?3
|
111
140
|
SQL
|
112
141
|
end
|
113
142
|
nil
|
@@ -153,10 +182,29 @@ class Fbe::Middleware::SqliteStore
|
|
153
182
|
SQL
|
154
183
|
t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
|
155
184
|
t.execute 'DROP TABLE cache_old;'
|
185
|
+
t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
|
156
186
|
t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
|
157
187
|
end
|
158
188
|
d.execute 'VACUUM;'
|
159
189
|
end
|
190
|
+
if d.execute("SELECT 1 FROM pragma_table_info('cache') WHERE name = 'created_at';").dig(0, 0) != 1
|
191
|
+
d.transaction do |t|
|
192
|
+
t.execute 'ALTER TABLE cache ADD COLUMN created_at TEXT;'
|
193
|
+
t.execute 'UPDATE cache set created_at = ?;', [Time.now.utc.iso8601]
|
194
|
+
t.execute 'ALTER TABLE cache RENAME TO cache_old;'
|
195
|
+
t.execute <<~SQL
|
196
|
+
CREATE TABLE IF NOT EXISTS cache(
|
197
|
+
key TEXT UNIQUE NOT NULL, value TEXT, touched_at TEXT NOT NULL, created_at TEXT NOT NULL
|
198
|
+
);
|
199
|
+
SQL
|
200
|
+
t.execute 'INSERT INTO cache SELECT * FROM cache_old;'
|
201
|
+
t.execute 'DROP TABLE cache_old;'
|
202
|
+
t.execute 'CREATE INDEX IF NOT EXISTS cache_key_idx ON cache(key);'
|
203
|
+
t.execute 'CREATE INDEX IF NOT EXISTS cache_touched_at_idx ON cache(touched_at);'
|
204
|
+
t.execute 'CREATE INDEX IF NOT EXISTS cache_created_at_idx ON cache(created_at);'
|
205
|
+
end
|
206
|
+
d.execute 'VACUUM;'
|
207
|
+
end
|
160
208
|
found = d.execute("SELECT value FROM meta WHERE key = 'version' LIMIT 1;").dig(0, 0)
|
161
209
|
if found != @version
|
162
210
|
@loog.info("Version mismatch in SQLite cache: stored '#{found}' != current '#{@version}', cleaning up")
|
@@ -166,6 +214,15 @@ class Fbe::Middleware::SqliteStore
|
|
166
214
|
end
|
167
215
|
d.execute 'VACUUM;'
|
168
216
|
end
|
217
|
+
unless @ttl.nil?
|
218
|
+
d.transaction do |t|
|
219
|
+
t.execute <<~SQL, [(Time.now.utc - (@ttl * 60 * 60)).iso8601]
|
220
|
+
DELETE FROM cache
|
221
|
+
WHERE key IN (SELECT key FROM cache WHERE (created_at < ?));
|
222
|
+
SQL
|
223
|
+
end
|
224
|
+
d.execute 'VACUUM;'
|
225
|
+
end
|
169
226
|
if File.size(@path) > @maxsize
|
170
227
|
@loog.info(
|
171
228
|
"SQLite cache file size (#{Filesize.from(File.size(@path).to_s).pretty} bytes) exceeds " \
|
data/lib/fbe/octo.rb
CHANGED
@@ -93,7 +93,10 @@ 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
|
-
|
96
|
+
cache_min_age = options.sqlite_cache_min_age&.to_i
|
97
|
+
store = Fbe::Middleware::SqliteStore.new(
|
98
|
+
options.sqlite_cache, Fbe::VERSION, loog:, maxsize:, maxvsize:, ttl: 24, cache_min_age:
|
99
|
+
)
|
97
100
|
loog.info(
|
98
101
|
"Using HTTP cache in SQLite file: #{store.path} (" \
|
99
102
|
"#{File.exist?(store.path) ? Filesize.from(File.size(store.path).to_s).pretty : 'file is absent'}, " \
|
data/lib/fbe.rb
CHANGED
@@ -5,10 +5,11 @@
|
|
5
5
|
|
6
6
|
require 'faraday'
|
7
7
|
require 'loog'
|
8
|
-
|
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(
|
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
|
-
|
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,187 @@ 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
|
+
|
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
|
+
|
240
421
|
private
|
241
422
|
|
242
423
|
def with_tmpfile(name = 'test.db', &)
|
@@ -244,4 +425,22 @@ class SqliteStoreTest < Fbe::Test
|
|
244
425
|
yield File.expand_path(name, dir)
|
245
426
|
end
|
246
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
|
247
446
|
end
|
@@ -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)
|
@@ -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
|
-
|
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)
|
data/test/fbe/test_if_absent.rb
CHANGED
@@ -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
|
-
|
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)
|
data/test/fbe/test_just_one.rb
CHANGED
data/test/fbe/test_octo.rb
CHANGED
@@ -709,4 +709,53 @@ class TestOcto < Fbe::Test
|
|
709
709
|
end
|
710
710
|
end
|
711
711
|
end
|
712
|
+
|
713
|
+
def test_octo_with_set_sqlite_cache_min_age
|
714
|
+
WebMock.disable_net_connect!
|
715
|
+
now = Time.now
|
716
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
717
|
+
.to_return(
|
718
|
+
status: 200, headers: { 'Content-Type' => 'application/json', 'X-RateLimit-Remaining' => '5000' },
|
719
|
+
body: { 'rate' => { 'limit' => 5000, 'remaining' => 5000, 'reset' => 1_672_531_200 } }.to_json
|
720
|
+
)
|
721
|
+
Dir.mktmpdir do |dir|
|
722
|
+
sqlite_cache = File.expand_path('t.db', dir)
|
723
|
+
options = Judges::Options.new({ 'sqlite_cache' => sqlite_cache, 'sqlite_cache_min_age' => 120 })
|
724
|
+
o = Fbe.octo(loog: fake_loog, global: {}, options:)
|
725
|
+
stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
|
726
|
+
status: 200,
|
727
|
+
body: { id: 798_641_472, name: 'factbase' }.to_json,
|
728
|
+
headers: {
|
729
|
+
'Date' => now.httpdate,
|
730
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
731
|
+
'Cache-Control' => 'public, max-age=60, s-maxage=60',
|
732
|
+
'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487bc"',
|
733
|
+
'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
|
734
|
+
}
|
735
|
+
).times(1).then.to_raise('no more request to /repositories/798641472')
|
736
|
+
Time.stub(:now, now) do
|
737
|
+
assert_equal('factbase', o.repo(798_641_472)['name'])
|
738
|
+
end
|
739
|
+
Time.stub(:now, now + 50) do
|
740
|
+
assert_equal('factbase', o.repo(798_641_472)['name'])
|
741
|
+
end
|
742
|
+
Time.stub(:now, now + 100) do
|
743
|
+
assert_equal('factbase', o.repo(798_641_472)['name'])
|
744
|
+
end
|
745
|
+
stub_request(:get, 'https://api.github.com/repositories/798641472').to_return(
|
746
|
+
status: 200,
|
747
|
+
body: { id: 798_641_472, name: 'factbase_changed' }.to_json,
|
748
|
+
headers: {
|
749
|
+
'Date' => (now + 120).httpdate,
|
750
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
751
|
+
'Cache-Control' => 'public, max-age=60, s-maxage=60',
|
752
|
+
'Etag' => 'W/"f5f1ea995fd7266816f681aca5a81f539420c469070a47568bebdaa3055487bc"',
|
753
|
+
'Last-Modified' => 'Fri, 04 Jul 2025 13:39:42 GMT'
|
754
|
+
}
|
755
|
+
)
|
756
|
+
Time.stub(:now, now + 120) do
|
757
|
+
assert_equal('factbase_changed', o.repo(798_641_472)['name'])
|
758
|
+
end
|
759
|
+
end
|
760
|
+
end
|
712
761
|
end
|
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)
|