fbe 0.16.1 → 0.18.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/.gitignore +1 -0
- data/.rubocop.yml +2 -1
- data/Gemfile.lock +5 -0
- data/Rakefile +1 -1
- data/fbe.gemspec +1 -0
- data/lib/fbe/middleware/sqlite_store.rb +83 -0
- data/lib/fbe/middleware/trace.rb +58 -0
- data/lib/fbe/octo.rb +38 -4
- data/lib/fbe.rb +1 -1
- data/test/fbe/middleware/test_sqlite_store.rb +35 -0
- data/test/fbe/middleware/test_trace.rb +106 -0
- data/test/fbe/test_fb.rb +22 -0
- data/test/fbe/test_octo.rb +68 -0
- metadata +19 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a99af1e0cf0824c00460e0166b3b9882c40b49421989e94c5abcda5d1766042
|
4
|
+
data.tar.gz: e7963ae2eb4bdb52745a10dc5f4d240abf768549f1461098c93e7dc81986936a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f90f11a2f21822ab37f5b9300ee1dd35858f9ded52baed36404af1bc27d1ebf9880e720a08b0caff73e932fefad80716bddcdafb87f0e8b7c4c5b2f84014e6c
|
7
|
+
data.tar.gz: 01612e41f4b1d8432b55dff2a4ce3b77af9686a9f9b6b1e3c9ff099f10ec447ee1b7594bf9808297d3a799656f92909a2112f7234d6a603199bd5c2c154fe6a5
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -14,6 +14,8 @@ plugins:
|
|
14
14
|
- rubocop-minitest
|
15
15
|
- rubocop-performance
|
16
16
|
- rubocop-rake
|
17
|
+
Minitest/MultipleAssertions:
|
18
|
+
Max: 10
|
17
19
|
Minitest/EmptyLineBeforeAssertionMethods:
|
18
20
|
Enabled: false
|
19
21
|
Style/GlobalVars:
|
@@ -54,4 +56,3 @@ Metrics/ParameterLists:
|
|
54
56
|
Enabled: false
|
55
57
|
Layout/MultilineAssignmentLayout:
|
56
58
|
Enabled: true
|
57
|
-
require: []
|
data/Gemfile.lock
CHANGED
@@ -17,6 +17,7 @@ PATH
|
|
17
17
|
obk (> 0)
|
18
18
|
octokit (~> 10.0)
|
19
19
|
others (> 0)
|
20
|
+
sqlite3 (~> 2.6)
|
20
21
|
tago (> 0)
|
21
22
|
verbose (> 0)
|
22
23
|
|
@@ -217,6 +218,10 @@ GEM
|
|
217
218
|
simplecov (~> 0.19)
|
218
219
|
simplecov-html (0.13.1)
|
219
220
|
simplecov_json_formatter (0.1.4)
|
221
|
+
sqlite3 (2.6.0-arm64-darwin)
|
222
|
+
sqlite3 (2.6.0-x64-mingw-ucrt)
|
223
|
+
sqlite3 (2.6.0-x86_64-darwin)
|
224
|
+
sqlite3 (2.6.0-x86_64-linux-gnu)
|
220
225
|
strscan (3.1.5)
|
221
226
|
tago (0.1.0)
|
222
227
|
timeout (0.4.3)
|
data/Rakefile
CHANGED
data/fbe.gemspec
CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
|
|
37
37
|
s.add_dependency 'obk', '>0'
|
38
38
|
s.add_dependency 'octokit', '~>10.0'
|
39
39
|
s.add_dependency 'others', '>0'
|
40
|
+
s.add_dependency 'sqlite3', '~> 2.6'
|
40
41
|
s.add_dependency 'tago', '>0'
|
41
42
|
s.add_dependency 'verbose', '>0'
|
42
43
|
s.metadata['rubygems_mfa_required'] = 'true'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'sqlite3'
|
8
|
+
require_relative '../../fbe'
|
9
|
+
require_relative '../../fbe/middleware'
|
10
|
+
|
11
|
+
# Persisted SQLite store for Faraday::HttpCache
|
12
|
+
#
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
14
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
15
|
+
# License:: MIT
|
16
|
+
class Fbe::Middleware::SqliteStore
|
17
|
+
def initialize(path)
|
18
|
+
raise ArgumentError, 'Database path cannot be nil or empty' if path.nil? || path.empty?
|
19
|
+
dir = File.dirname(path)
|
20
|
+
raise ArgumentError, "Directory #{dir} does not exist" unless File.directory?(dir)
|
21
|
+
@path = path
|
22
|
+
open
|
23
|
+
prepare
|
24
|
+
at_exit { close }
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(key)
|
28
|
+
value = perform { _1.execute('SELECT value FROM cache WHERE key = ? LIMIT 1', [key]) }.dig(0, 0)
|
29
|
+
JSON.parse(value) if value
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(key)
|
33
|
+
perform { _1.execute('DELETE FROM cache WHERE key = ?', [key]) }
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def write(key, value)
|
38
|
+
value = JSON.dump(value)
|
39
|
+
perform do |tdb|
|
40
|
+
tdb.execute(<<~SQL, [key, value])
|
41
|
+
INSERT INTO cache(key, value) VALUES(?1, ?2)
|
42
|
+
ON CONFLICT(key) DO UPDATE SET value = ?2
|
43
|
+
SQL
|
44
|
+
end
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def open
|
49
|
+
return if @db
|
50
|
+
@db = SQLite3::Database.new(@path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def prepare
|
54
|
+
perform do |tdb|
|
55
|
+
tdb.execute 'CREATE TABLE IF NOT EXISTS cache(key TEXT UNIQUE NOT NULL, value TEXT);'
|
56
|
+
tdb.execute 'CREATE INDEX IF NOT EXISTS key_idx ON cache(key);'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def close
|
61
|
+
return if !@db || @db.closed?
|
62
|
+
@db.close
|
63
|
+
@db = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def clear
|
67
|
+
perform { _1.execute 'DELETE FROM cache;' }
|
68
|
+
end
|
69
|
+
|
70
|
+
def drop
|
71
|
+
perform { _1.execute 'DROP TABLE IF EXISTS cache;' }
|
72
|
+
end
|
73
|
+
|
74
|
+
def all
|
75
|
+
perform { _1.execute('SELECT key, value FROM cache') }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def perform(&)
|
81
|
+
@db.transaction(&)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'faraday'
|
7
|
+
require_relative '../../fbe'
|
8
|
+
require_relative '../../fbe/middleware'
|
9
|
+
|
10
|
+
# Faraday middleware that traces all API calls.
|
11
|
+
#
|
12
|
+
# This middleware records all HTTP requests and responses in a trace array,
|
13
|
+
# capturing method, URL, status, and timing information for debugging and
|
14
|
+
# monitoring purposes.
|
15
|
+
#
|
16
|
+
# @example Usage in Faraday middleware stack
|
17
|
+
# trace = []
|
18
|
+
# connection = Faraday.new do |f|
|
19
|
+
# f.use Fbe::Middleware::Trace, trace
|
20
|
+
# end
|
21
|
+
# connection.get('/api/endpoint')
|
22
|
+
# trace.first[:method] #=> :get
|
23
|
+
# trace.first[:url] #=> 'https://example.com/api/endpoint'
|
24
|
+
# trace.first[:status] #=> 200
|
25
|
+
#
|
26
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
27
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
28
|
+
# License:: MIT
|
29
|
+
class Fbe::Middleware::Trace < Faraday::Middleware
|
30
|
+
# Initializes the trace middleware.
|
31
|
+
#
|
32
|
+
# @param [Object] app The next middleware in the stack
|
33
|
+
# @param [Array] trace The array to store trace entries
|
34
|
+
def initialize(app, trace)
|
35
|
+
super(app)
|
36
|
+
@trace = trace
|
37
|
+
end
|
38
|
+
|
39
|
+
# Processes the HTTP request and records trace information.
|
40
|
+
#
|
41
|
+
# @param [Faraday::Env] env The request environment
|
42
|
+
# @return [Faraday::Response] The response from the next middleware
|
43
|
+
def call(env)
|
44
|
+
started = Time.now
|
45
|
+
entry = {
|
46
|
+
method: env.method,
|
47
|
+
url: env.url.to_s,
|
48
|
+
started_at: started
|
49
|
+
}
|
50
|
+
@app.call(env).on_complete do |response_env|
|
51
|
+
finished = Time.now
|
52
|
+
entry[:status] = response_env.status
|
53
|
+
entry[:finished_at] = finished
|
54
|
+
entry[:duration] = finished - started
|
55
|
+
@trace << entry
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/fbe/octo.rb
CHANGED
@@ -3,16 +3,20 @@
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
4
|
# SPDX-License-Identifier: MIT
|
5
5
|
|
6
|
+
require 'json'
|
6
7
|
require 'decoor'
|
7
8
|
require 'faraday/http_cache'
|
8
9
|
require 'faraday/retry'
|
9
10
|
require 'loog'
|
10
11
|
require 'obk'
|
11
12
|
require 'octokit'
|
13
|
+
require 'uri'
|
12
14
|
require 'verbose'
|
13
15
|
require_relative '../fbe'
|
14
16
|
require_relative 'middleware'
|
15
17
|
require_relative 'middleware/formatter'
|
18
|
+
require_relative 'middleware/trace'
|
19
|
+
require_relative 'middleware/sqlite_store'
|
16
20
|
|
17
21
|
# Makes a call to the GitHub API.
|
18
22
|
#
|
@@ -30,6 +34,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
30
34
|
raise 'The $loog is not set' if loog.nil?
|
31
35
|
global[:octo] ||=
|
32
36
|
begin
|
37
|
+
trace = []
|
33
38
|
if options.testing.nil?
|
34
39
|
o = Octokit::Client.new
|
35
40
|
token = options.github_token
|
@@ -50,8 +55,10 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
50
55
|
loog.warn('The GitHub API token is an empty string, won\'t use it')
|
51
56
|
else
|
52
57
|
o = Octokit::Client.new(access_token: token)
|
53
|
-
loog.info(
|
54
|
-
|
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
|
+
)
|
55
62
|
end
|
56
63
|
o.auto_paginate = true
|
57
64
|
o.per_page = 100
|
@@ -73,9 +80,18 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
73
80
|
methods: [:get],
|
74
81
|
backoff_factor: 2
|
75
82
|
)
|
76
|
-
|
83
|
+
serializer = Marshal
|
84
|
+
if options.sqlite_cache
|
85
|
+
store = Fbe::Middleware::SqliteStore.new(options.sqlite_cache)
|
86
|
+
serializer = JSON
|
87
|
+
end
|
88
|
+
builder.use(
|
89
|
+
Faraday::HttpCache,
|
90
|
+
store: store, serializer: serializer, shared_cache: false, logger: Loog::NULL
|
91
|
+
)
|
77
92
|
builder.use(Octokit::Response::RaiseError)
|
78
93
|
builder.use(Faraday::Response::Logger, loog, formatter: Fbe::Middleware::Formatter)
|
94
|
+
builder.use(Fbe::Middleware::Trace, trace)
|
79
95
|
builder.adapter(Faraday.default_adapter)
|
80
96
|
end
|
81
97
|
o.middleware = stack
|
@@ -84,7 +100,25 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
84
100
|
loog.debug('The connection to GitHub API is mocked')
|
85
101
|
o = Fbe::FakeOctokit.new
|
86
102
|
end
|
87
|
-
decoor(o, loog:) do
|
103
|
+
decoor(o, loog:, trace:) do
|
104
|
+
def print_trace!
|
105
|
+
if @trace.empty?
|
106
|
+
@loog.debug('GitHub API trace is empty')
|
107
|
+
else
|
108
|
+
grouped =
|
109
|
+
@trace.group_by do |entry|
|
110
|
+
uri = URI.parse(entry[:url])
|
111
|
+
"#{uri.scheme}://#{uri.host}#{uri.path}"
|
112
|
+
end
|
113
|
+
message = grouped
|
114
|
+
.sort_by { |_path, entries| -entries.count }
|
115
|
+
.map { |path, entries| " #{path}: #{entries.count}" }
|
116
|
+
.join("\n")
|
117
|
+
@loog.info("GitHub API trace (#{grouped.count} URLs vs #{@trace.count} requests):\n#{message}")
|
118
|
+
@trace.clear
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
88
122
|
def off_quota(threshold: 50)
|
89
123
|
left = @origin.rate_limit.remaining
|
90
124
|
if left < threshold
|
data/lib/fbe.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require_relative '../../test__helper'
|
7
|
+
require_relative '../../../lib/fbe/middleware'
|
8
|
+
require_relative '../../../lib/fbe/middleware/sqlite_store'
|
9
|
+
|
10
|
+
# Test.
|
11
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
12
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
13
|
+
# License:: MIT
|
14
|
+
class SqliteStoreTest < Fbe::Test
|
15
|
+
def test_sqlite_store
|
16
|
+
Dir.mktmpdir do |dir|
|
17
|
+
store = Fbe::Middleware::SqliteStore.new(File.expand_path('test.db', dir))
|
18
|
+
assert_nil(store.read('my_key'))
|
19
|
+
assert_nil(store.delete('my_key'))
|
20
|
+
assert_nil(store.write('my_key', 'some value'))
|
21
|
+
assert_equal('some value', store.read('my_key'))
|
22
|
+
assert_nil(store.write('my_key', 'some value 2'))
|
23
|
+
assert_equal('some value 2', store.read('my_key'))
|
24
|
+
assert_nil(store.delete('my_key'))
|
25
|
+
assert_nil(store.read('my_key'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_sqlite_store_empty_all
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
store = Fbe::Middleware::SqliteStore.new(File.expand_path('test.db', dir))
|
32
|
+
assert_empty(store.all)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
|
6
|
+
require 'faraday'
|
7
|
+
require 'webmock'
|
8
|
+
require_relative '../../test__helper'
|
9
|
+
require_relative '../../../lib/fbe'
|
10
|
+
require_relative '../../../lib/fbe/middleware'
|
11
|
+
require_relative '../../../lib/fbe/middleware/trace'
|
12
|
+
|
13
|
+
# Test.
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
15
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
16
|
+
# License:: MIT
|
17
|
+
class TraceTest < Fbe::Test
|
18
|
+
def test_traces_successful_request
|
19
|
+
trace = []
|
20
|
+
stub_request(:get, 'http://example.com/test')
|
21
|
+
.to_return(status: 200, body: 'success')
|
22
|
+
conn =
|
23
|
+
Faraday.new do |f|
|
24
|
+
f.use Fbe::Middleware::Trace, trace
|
25
|
+
f.adapter :net_http
|
26
|
+
end
|
27
|
+
conn.get('http://example.com/test')
|
28
|
+
assert_equal 1, trace.size
|
29
|
+
entry = trace.first
|
30
|
+
assert_equal :get, entry[:method]
|
31
|
+
assert_equal 'http://example.com/test', entry[:url]
|
32
|
+
assert_equal 200, entry[:status]
|
33
|
+
assert_instance_of Time, entry[:started_at]
|
34
|
+
assert_instance_of Time, entry[:finished_at]
|
35
|
+
assert_instance_of Float, entry[:duration]
|
36
|
+
assert_operator entry[:duration], :>=, 0
|
37
|
+
assert_operator entry[:finished_at], :>=, entry[:started_at]
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_traces_multiple_requests
|
41
|
+
trace = []
|
42
|
+
stub_request(:get, 'http://example.com/endpoint1').to_return(status: 200)
|
43
|
+
stub_request(:post, 'http://example.com/endpoint2').to_return(status: 201)
|
44
|
+
stub_request(:delete, 'http://example.com/endpoint3').to_return(status: 404)
|
45
|
+
conn =
|
46
|
+
Faraday.new do |f|
|
47
|
+
f.use Fbe::Middleware::Trace, trace
|
48
|
+
f.adapter :net_http
|
49
|
+
end
|
50
|
+
conn.get('http://example.com/endpoint1')
|
51
|
+
conn.post('http://example.com/endpoint2')
|
52
|
+
conn.delete('http://example.com/endpoint3')
|
53
|
+
assert_equal 3, trace.size
|
54
|
+
assert_equal :get, trace[0][:method]
|
55
|
+
assert_equal 200, trace[0][:status]
|
56
|
+
assert_equal :post, trace[1][:method]
|
57
|
+
assert_equal 201, trace[1][:status]
|
58
|
+
assert_equal :delete, trace[2][:method]
|
59
|
+
assert_equal 404, trace[2][:status]
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_traces_error_responses
|
63
|
+
trace = []
|
64
|
+
stub_request(:get, 'http://example.com/error').to_return(status: 500, body: 'error')
|
65
|
+
conn =
|
66
|
+
Faraday.new do |f|
|
67
|
+
f.use Fbe::Middleware::Trace, trace
|
68
|
+
f.adapter :net_http
|
69
|
+
end
|
70
|
+
conn.get('http://example.com/error')
|
71
|
+
assert_equal 1, trace.size
|
72
|
+
entry = trace.first
|
73
|
+
assert_equal 500, entry[:status]
|
74
|
+
assert_equal 'http://example.com/error', entry[:url]
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_handles_connection_errors
|
78
|
+
trace = []
|
79
|
+
stub_request(:get, 'http://example.com/timeout').to_timeout
|
80
|
+
conn =
|
81
|
+
Faraday.new do |f|
|
82
|
+
f.use Fbe::Middleware::Trace, trace
|
83
|
+
f.adapter :net_http
|
84
|
+
end
|
85
|
+
assert_raises(Faraday::ConnectionFailed) do
|
86
|
+
conn.get('http://example.com/timeout')
|
87
|
+
end
|
88
|
+
assert_equal 0, trace.size
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_preserves_request_with_query_params
|
92
|
+
trace = []
|
93
|
+
stub_request(:get, 'http://example.com/search').with(query: { 'q' => 'test', 'page' => '2' }).to_return(status: 200)
|
94
|
+
conn =
|
95
|
+
Faraday.new do |f|
|
96
|
+
f.use Fbe::Middleware::Trace, trace
|
97
|
+
f.adapter :net_http
|
98
|
+
end
|
99
|
+
conn.get('http://example.com/search?q=test&page=2')
|
100
|
+
assert_equal 1, trace.size
|
101
|
+
url = trace.first[:url]
|
102
|
+
assert url.start_with?('http://example.com/search?')
|
103
|
+
assert_includes url, 'q=test'
|
104
|
+
assert_includes url, 'page=2'
|
105
|
+
end
|
106
|
+
end
|
data/test/fbe/test_fb.rb
CHANGED
@@ -27,6 +27,28 @@ class TestFb < Fbe::Test
|
|
27
27
|
assert_includes(stdout, 'Inserted new fact #1', stdout)
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_defends_against_improper_facts
|
31
|
+
$fb = Factbase.new
|
32
|
+
$global = {}
|
33
|
+
$options = Judges::Options.new
|
34
|
+
$loog = Loog::Buffer.new
|
35
|
+
assert_raises(StandardError, 'issue without repository') do
|
36
|
+
Fbe.fb.txn do |fbt|
|
37
|
+
f = fbt.insert
|
38
|
+
f.what = 'issue-was-opened'
|
39
|
+
f.issue = 42
|
40
|
+
f.where = 'github'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
assert_raises(StandardError, 'repository without where') do
|
44
|
+
Fbe.fb.txn do |fbt|
|
45
|
+
f = fbt.insert
|
46
|
+
f.what = 'issue-was-opened'
|
47
|
+
f.repository = 44
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
30
52
|
def test_increment_id_in_transaction
|
31
53
|
$fb = Factbase.new
|
32
54
|
$global = {}
|
data/test/fbe/test_octo.rb
CHANGED
@@ -322,4 +322,72 @@ class TestOcto < Fbe::Test
|
|
322
322
|
}
|
323
323
|
end
|
324
324
|
end
|
325
|
+
|
326
|
+
def test_print_trace
|
327
|
+
loog = Loog::Buffer.new
|
328
|
+
WebMock.disable_net_connect!
|
329
|
+
stub_request(:get, 'https://api.github.com/user/123').to_return(
|
330
|
+
status: 200,
|
331
|
+
body: '{"id":123,"login":"test"}'
|
332
|
+
)
|
333
|
+
stub_request(:get, 'https://api.github.com/repos/foo/bar').to_return(
|
334
|
+
status: 200,
|
335
|
+
body: '{"id":456,"full_name":"foo/bar"}'
|
336
|
+
)
|
337
|
+
octo = Fbe.octo(loog:, global: {}, options: Judges::Options.new)
|
338
|
+
octo.user(123)
|
339
|
+
octo.repository('foo/bar')
|
340
|
+
octo.repository('foo/bar')
|
341
|
+
octo.print_trace!
|
342
|
+
output = loog.to_s
|
343
|
+
assert_includes output, 'GitHub API trace (2 URLs vs 3 requests)'
|
344
|
+
assert_includes output, 'https://api.github.com/user/123: 1'
|
345
|
+
assert_includes output, 'https://api.github.com/repos/foo/bar: 2'
|
346
|
+
repo_index = output.index('https://api.github.com/repos/foo/bar: 2')
|
347
|
+
user_index = output.index('https://api.github.com/user/123: 1')
|
348
|
+
assert_operator repo_index, :<, user_index, 'URLs should be sorted by request count (highest first)'
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_trace_gets_cleared_after_print
|
352
|
+
WebMock.disable_net_connect!
|
353
|
+
stub_request(:get, 'https://api.github.com/user/456').to_return(
|
354
|
+
status: 200,
|
355
|
+
body: '{"id":456,"login":"testuser"}'
|
356
|
+
)
|
357
|
+
first_loog = Loog::Buffer.new
|
358
|
+
octo = Fbe.octo(loog: first_loog, global: {}, options: Judges::Options.new)
|
359
|
+
octo.user(456)
|
360
|
+
octo.print_trace!
|
361
|
+
first_output = first_loog.to_s
|
362
|
+
assert_includes first_output, 'GitHub API trace'
|
363
|
+
second_loog = Loog::Buffer.new
|
364
|
+
octo.instance_variable_set(:@loog, second_loog)
|
365
|
+
octo.print_trace!
|
366
|
+
second_output = second_loog.to_s
|
367
|
+
assert_includes second_output, 'GitHub API trace is empty'
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_sqlite_store
|
371
|
+
WebMock.disable_net_connect!
|
372
|
+
Dir.mktmpdir do |dir|
|
373
|
+
global = {}
|
374
|
+
sqlite_cache = File.expand_path('test.db', dir)
|
375
|
+
o = Fbe.octo(loog: Loog::NULL, global:, options: Judges::Options.new({ 'sqlite_cache' => sqlite_cache }))
|
376
|
+
stub = stub_request(:get, 'https://api.github.com/user/42').to_return(
|
377
|
+
status: 200,
|
378
|
+
body: { login: 'user1' }.to_json,
|
379
|
+
headers: {
|
380
|
+
'Content-Type' => 'application/json',
|
381
|
+
'Cache-Control' => 'public, max-age=60, s-maxage=60',
|
382
|
+
'Etag' => 'W/"2ff9dd4c3153f006830b2b8b721f6a4bb400a1eb81a2e1fa0a3b846ad349b9ec"',
|
383
|
+
'Last-Modified' => 'Wed, 01 May 2025 20:00:00 GMT'
|
384
|
+
}
|
385
|
+
)
|
386
|
+
assert_equal('user1', o.user_name_by_id(42))
|
387
|
+
WebMock.remove_request_stub(stub)
|
388
|
+
global = {}
|
389
|
+
o = Fbe.octo(loog: Loog::NULL, global:, options: Judges::Options.new({ 'sqlite_cache' => sqlite_cache }))
|
390
|
+
assert_equal('user1', o.user_name_by_id(42))
|
391
|
+
end
|
392
|
+
end
|
325
393
|
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.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -219,6 +219,20 @@ dependencies:
|
|
219
219
|
- - ">"
|
220
220
|
- !ruby/object:Gem::Version
|
221
221
|
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: sqlite3
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '2.6'
|
229
|
+
type: :runtime
|
230
|
+
prerelease: false
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - "~>"
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '2.6'
|
222
236
|
- !ruby/object:Gem::Dependency
|
223
237
|
name: tago
|
224
238
|
requirement: !ruby/object:Gem::Requirement
|
@@ -307,6 +321,8 @@ files:
|
|
307
321
|
- lib/fbe/just_one.rb
|
308
322
|
- lib/fbe/middleware.rb
|
309
323
|
- lib/fbe/middleware/formatter.rb
|
324
|
+
- lib/fbe/middleware/sqlite_store.rb
|
325
|
+
- lib/fbe/middleware/trace.rb
|
310
326
|
- lib/fbe/octo.rb
|
311
327
|
- lib/fbe/overwrite.rb
|
312
328
|
- lib/fbe/pmp.rb
|
@@ -318,6 +334,8 @@ files:
|
|
318
334
|
- renovate.json
|
319
335
|
- rules/basic.fe
|
320
336
|
- test/fbe/middleware/test_formatter.rb
|
337
|
+
- test/fbe/middleware/test_sqlite_store.rb
|
338
|
+
- test/fbe/middleware/test_trace.rb
|
321
339
|
- test/fbe/test_award.rb
|
322
340
|
- test/fbe/test_bylaws.rb
|
323
341
|
- test/fbe/test_conclude.rb
|