fbe 0.19.1 → 0.19.3
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/lib/fbe/middleware/sqlite_store.rb +14 -27
- data/lib/fbe/octo.rb +22 -12
- data/lib/fbe.rb +1 -1
- data/test/fbe/middleware/test_sqlite_store.rb +98 -14
- data/test/fbe/test_octo.rb +20 -5
- 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: df9755e6d4d7bf5ae3a716031216786929ddfcdf328a3f026b308e9bbfc93f92
|
4
|
+
data.tar.gz: ed1cfe9f9877a3e3e1b77f30f4b9c8ca525885306d100058d5397019ca1005e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bfc015d2f0c47035fae903627404da6fafcd823c14abdcdc8a37694fea73bbb4edd1f1467497379fff51e62388b741a205b28bcd0ee8a4b5c28aceaf0f6c22c
|
7
|
+
data.tar.gz: dc92c946e0d1bd14deb3d29a73b36f9317d7e06597ac2ec4fc05030ed9afea415830a71f101eb54ddd2793a02a3c6b480d2a2b95482f3deb0d62903230b1432e
|
@@ -21,9 +21,6 @@ class Fbe::Middleware::SqliteStore
|
|
21
21
|
dir = File.dirname(path)
|
22
22
|
raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
|
23
23
|
@path = File.absolute_path(path)
|
24
|
-
open
|
25
|
-
prepare
|
26
|
-
at_exit { close }
|
27
24
|
end
|
28
25
|
|
29
26
|
def read(key)
|
@@ -37,9 +34,13 @@ class Fbe::Middleware::SqliteStore
|
|
37
34
|
end
|
38
35
|
|
39
36
|
def write(key, value)
|
37
|
+
return if value.is_a?(Array) && value.any? do |vv|
|
38
|
+
req = JSON.parse(vv[0])
|
39
|
+
req['url'].include?('?') || req['method'] != 'get'
|
40
|
+
end
|
40
41
|
value = JSON.dump(value)
|
41
|
-
perform do |
|
42
|
-
|
42
|
+
perform do |t|
|
43
|
+
t.execute(<<~SQL, [key, value])
|
43
44
|
INSERT INTO cache(key, value) VALUES(?1, ?2)
|
44
45
|
ON CONFLICT(key) DO UPDATE SET value = ?2
|
45
46
|
SQL
|
@@ -47,32 +48,10 @@ class Fbe::Middleware::SqliteStore
|
|
47
48
|
nil
|
48
49
|
end
|
49
50
|
|
50
|
-
def open
|
51
|
-
return if @db
|
52
|
-
@db = SQLite3::Database.new(@path)
|
53
|
-
end
|
54
|
-
|
55
|
-
def prepare
|
56
|
-
perform do |tdb|
|
57
|
-
tdb.execute 'CREATE TABLE IF NOT EXISTS cache(key TEXT UNIQUE NOT NULL, value TEXT);'
|
58
|
-
tdb.execute 'CREATE INDEX IF NOT EXISTS key_idx ON cache(key);'
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def close
|
63
|
-
return if !@db || @db.closed?
|
64
|
-
@db.close
|
65
|
-
@db = nil
|
66
|
-
end
|
67
|
-
|
68
51
|
def clear
|
69
52
|
perform { _1.execute 'DELETE FROM cache;' }
|
70
53
|
end
|
71
54
|
|
72
|
-
def drop
|
73
|
-
perform { _1.execute 'DROP TABLE IF EXISTS cache;' }
|
74
|
-
end
|
75
|
-
|
76
55
|
def all
|
77
56
|
perform { _1.execute('SELECT key, value FROM cache') }
|
78
57
|
end
|
@@ -80,6 +59,14 @@ class Fbe::Middleware::SqliteStore
|
|
80
59
|
private
|
81
60
|
|
82
61
|
def perform(&)
|
62
|
+
@db ||=
|
63
|
+
SQLite3::Database.new(@path).tap do |d|
|
64
|
+
d.transaction do |t|
|
65
|
+
t.execute 'CREATE TABLE IF NOT EXISTS cache(key TEXT UNIQUE NOT NULL, value TEXT);'
|
66
|
+
t.execute 'CREATE INDEX IF NOT EXISTS key_idx ON cache(key);'
|
67
|
+
end
|
68
|
+
at_exit { @db&.close }
|
69
|
+
end
|
83
70
|
@db.transaction(&)
|
84
71
|
end
|
85
72
|
end
|
data/lib/fbe/octo.rb
CHANGED
@@ -47,7 +47,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
47
47
|
loog.debug("The 'GITHUB_TOKEN' environment was provided")
|
48
48
|
end
|
49
49
|
else
|
50
|
-
loog.debug("The 'github_token' option was provided")
|
50
|
+
loog.debug("The 'github_token' option was provided (#{token.length} chars)")
|
51
51
|
end
|
52
52
|
if token.nil?
|
53
53
|
loog.warn('Accessing GitHub API without a token!')
|
@@ -55,10 +55,6 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
55
55
|
loog.warn('The GitHub API token is an empty string, won\'t use it')
|
56
56
|
else
|
57
57
|
o = Octokit::Client.new(access_token: token)
|
58
|
-
loog.info(
|
59
|
-
"Accessing GitHub API with a token (#{token.length} chars, ending by #{token[-4..].inspect}, " \
|
60
|
-
"#{Octokit::Client.new(access_token: token).rate_limit.remaining} quota remaining)"
|
61
|
-
)
|
62
58
|
end
|
63
59
|
o.auto_paginate = true
|
64
60
|
o.per_page = 100
|
@@ -80,16 +76,24 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
80
76
|
methods: [:get],
|
81
77
|
backoff_factor: 2
|
82
78
|
)
|
83
|
-
serializer = Marshal
|
84
79
|
if options.sqlite_cache
|
85
80
|
store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache)
|
86
|
-
|
87
|
-
|
81
|
+
loog.info(
|
82
|
+
"Using HTTP cache in SQLite file: #{store.path} (" \
|
83
|
+
"#{File.exist?(store.path) ? "#{File.size(store.path)} bytes" : 'file is absent'}" \
|
84
|
+
')'
|
85
|
+
)
|
86
|
+
builder.use(
|
87
|
+
Faraday::HttpCache,
|
88
|
+
store:, serializer: JSON, shared_cache: false, logger: Loog::NULL
|
89
|
+
)
|
90
|
+
else
|
91
|
+
loog.info("No HTTP cache in SQLite file, because 'sqlite_cache' option is not provided")
|
92
|
+
builder.use(
|
93
|
+
Faraday::HttpCache,
|
94
|
+
serializer: Marshal, shared_cache: false, logger: Loog::NULL
|
95
|
+
)
|
88
96
|
end
|
89
|
-
builder.use(
|
90
|
-
Faraday::HttpCache,
|
91
|
-
store: store, serializer: serializer, shared_cache: false, logger: Loog::NULL
|
92
|
-
)
|
93
97
|
builder.use(Octokit::Response::RaiseError)
|
94
98
|
builder.use(Faraday::Response::Logger, loog, formatter: Fbe::Middleware::Formatter)
|
95
99
|
builder.use(Fbe::Middleware::Trace, trace)
|
@@ -97,6 +101,12 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
97
101
|
end
|
98
102
|
o.middleware = stack
|
99
103
|
o = Verbose.new(o, log: loog)
|
104
|
+
unless token.nil? || token.empty?
|
105
|
+
loog.info(
|
106
|
+
"Accessing GitHub API with a token (#{token.length} chars, ending by #{token[-4..].inspect}, " \
|
107
|
+
"#{o.rate_limit.remaining} quota remaining)"
|
108
|
+
)
|
109
|
+
end
|
100
110
|
else
|
101
111
|
loog.debug('The connection to GitHub API is mocked')
|
102
112
|
o = Fbe::FakeOctokit.new
|
data/lib/fbe.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 'qbash'
|
6
7
|
require_relative '../../test__helper'
|
7
8
|
require_relative '../../../lib/fbe/middleware'
|
8
9
|
require_relative '../../../lib/fbe/middleware/sqlite_store'
|
@@ -12,24 +13,107 @@ require_relative '../../../lib/fbe/middleware/sqlite_store'
|
|
12
13
|
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
13
14
|
# License:: MIT
|
14
15
|
class SqliteStoreTest < Fbe::Test
|
15
|
-
def
|
16
|
-
|
17
|
-
store = Fbe::Middleware::SqliteStore.new(
|
18
|
-
|
19
|
-
assert_nil(store.
|
20
|
-
assert_nil(store.
|
21
|
-
|
22
|
-
assert_nil(store.write(
|
23
|
-
assert_equal(
|
24
|
-
|
25
|
-
assert_nil(store.
|
16
|
+
def test_simple_caching_algorithm
|
17
|
+
with_tmpfile('x.db') do |f|
|
18
|
+
store = Fbe::Middleware::SqliteStore.new(f)
|
19
|
+
k = 'some-key'
|
20
|
+
assert_nil(store.read(k))
|
21
|
+
assert_nil(store.delete(k))
|
22
|
+
v1 = 'first value to save'
|
23
|
+
assert_nil(store.write(k, v1))
|
24
|
+
assert_equal(v1, store.read(k))
|
25
|
+
v2 = 'another value to save'
|
26
|
+
assert_nil(store.write(k, v2))
|
27
|
+
assert_equal(v2, store.read(k))
|
28
|
+
assert_nil(store.delete(k))
|
29
|
+
assert_nil(store.read(k))
|
30
|
+
assert_path_exists(f)
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
29
|
-
def
|
30
|
-
|
31
|
-
store = Fbe::Middleware::SqliteStore.new(
|
34
|
+
def test_returns_empty_list
|
35
|
+
with_tmpfile('b.db') do |f|
|
36
|
+
store = Fbe::Middleware::SqliteStore.new(f)
|
37
|
+
assert_empty(store.all)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_clear_all_keys
|
42
|
+
with_tmpfile('a.db') do |f|
|
43
|
+
store = Fbe::Middleware::SqliteStore.new(f)
|
44
|
+
k = 'a key'
|
45
|
+
store.write(k, 'some value')
|
46
|
+
store.clear
|
47
|
+
assert_empty(store.all)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_empty_all_if_not_written
|
52
|
+
with_tmpfile do |f|
|
53
|
+
store = Fbe::Middleware::SqliteStore.new(f)
|
32
54
|
assert_empty(store.all)
|
33
55
|
end
|
34
56
|
end
|
57
|
+
|
58
|
+
def test_wrong_db_path
|
59
|
+
assert_raises(ArgumentError) do
|
60
|
+
Fbe::Middleware::SqliteStore.new(nil).read('my_key')
|
61
|
+
end
|
62
|
+
assert_raises(ArgumentError) do
|
63
|
+
Fbe::Middleware::SqliteStore.new('').read('my_key')
|
64
|
+
end
|
65
|
+
assert_raises(ArgumentError) do
|
66
|
+
Fbe::Middleware::SqliteStore.new('/fakepath/fakefolder/test.db').read('my_key')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_not_db_file
|
71
|
+
with_tmpfile do |f|
|
72
|
+
File.binwrite(f, Array.new(20) { rand(0..255) }.pack('C*'))
|
73
|
+
ex =
|
74
|
+
assert_raises(SQLite3::NotADatabaseException) do
|
75
|
+
Fbe::Middleware::SqliteStore.new(f).read('my_key')
|
76
|
+
end
|
77
|
+
assert_match('file is not a database', ex.message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_defer_db_close_callback
|
82
|
+
txt = <<~RUBY
|
83
|
+
require 'tempfile'
|
84
|
+
require 'sqlite3'
|
85
|
+
require 'fbe/middleware/sqlite_store'
|
86
|
+
|
87
|
+
SQLite3::Database.class_eval do
|
88
|
+
prepend(Module.new do
|
89
|
+
def close
|
90
|
+
super
|
91
|
+
puts 'closed sqlite after process exit'
|
92
|
+
end
|
93
|
+
end)
|
94
|
+
end
|
95
|
+
|
96
|
+
Tempfile.open('test.db') do |f|
|
97
|
+
Fbe::Middleware::SqliteStore.new(f.path).then do |s|
|
98
|
+
s.write('my_key', 'my_value')
|
99
|
+
s.read('my_key')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
RUBY
|
103
|
+
out =
|
104
|
+
qbash(
|
105
|
+
'bundle exec ruby ' \
|
106
|
+
"-I#{Shellwords.escape(File.expand_path('../../../lib', __dir__))} " \
|
107
|
+
"-e #{Shellwords.escape(txt)} 2>&1"
|
108
|
+
)
|
109
|
+
assert_match('closed sqlite after process exit', out)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def with_tmpfile(name = 'test.db', &)
|
115
|
+
Dir.mktmpdir do |dir|
|
116
|
+
yield File.expand_path(name, dir)
|
117
|
+
end
|
118
|
+
end
|
35
119
|
end
|
data/test/fbe/test_octo.rb
CHANGED
@@ -138,10 +138,8 @@ class TestOcto < Fbe::Test
|
|
138
138
|
body: '{}', headers: { 'X-RateLimit-Remaining' => '1234' }
|
139
139
|
)
|
140
140
|
buf = Loog::Buffer.new
|
141
|
-
|
141
|
+
Fbe.octo(loog: buf, global: {}, options: Judges::Options.new({ 'github_token' => 'secret_github_token' }))
|
142
142
|
assert_match(/Accessing GitHub API with a token \(19 chars, ending by "oken", 1234 quota remaining\)/, buf.to_s)
|
143
|
-
assert_nil(o.last_response, 'Not to be requests until initialize main Octokit client, ' \
|
144
|
-
'because middleware cached after first request and not apply after')
|
145
143
|
end
|
146
144
|
|
147
145
|
def test_retrying
|
@@ -258,7 +256,10 @@ class TestOcto < Fbe::Test
|
|
258
256
|
end
|
259
257
|
|
260
258
|
def test_reads_quota
|
261
|
-
WebMock.
|
259
|
+
WebMock.disable_net_connect!
|
260
|
+
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
261
|
+
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '222' } }
|
262
|
+
)
|
262
263
|
o = Fbe.octo(loog: Loog::VERBOSE, global: {}, options: Judges::Options.new({ 'github_api_pause' => 0.01 }))
|
263
264
|
refute_nil(o.off_quota?)
|
264
265
|
end
|
@@ -367,7 +368,7 @@ class TestOcto < Fbe::Test
|
|
367
368
|
assert_includes second_output, 'GitHub API trace is empty'
|
368
369
|
end
|
369
370
|
|
370
|
-
def
|
371
|
+
def test_works_via_sqlite_store
|
371
372
|
WebMock.disable_net_connect!
|
372
373
|
Dir.mktmpdir do |dir|
|
373
374
|
global = {}
|
@@ -390,4 +391,18 @@ class TestOcto < Fbe::Test
|
|
390
391
|
assert_equal('user1', o.user_name_by_id(42))
|
391
392
|
end
|
392
393
|
end
|
394
|
+
|
395
|
+
def test_through_sqlite_store_when_broken_token
|
396
|
+
WebMock.disable_net_connect!
|
397
|
+
Dir.mktmpdir do |dir|
|
398
|
+
global = {}
|
399
|
+
file = File.expand_path('test.db', dir)
|
400
|
+
stub_request(:get, 'https://api.github.com/user/4242').to_return(status: 401)
|
401
|
+
o = Fbe.octo(loog: Loog::NULL, global:, options: Judges::Options.new({ 'sqlite_cache' => file }))
|
402
|
+
assert_raises(StandardError) do
|
403
|
+
assert_equal('user1', o.user_name_by_id(4242))
|
404
|
+
end
|
405
|
+
assert_path_exists(file)
|
406
|
+
end
|
407
|
+
end
|
393
408
|
end
|