fbe 0.16.1 → 0.17.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/Rakefile +1 -1
- data/lib/fbe/middleware/trace.rb +58 -0
- data/lib/fbe/octo.rb +27 -3
- data/lib/fbe.rb +1 -1
- data/test/fbe/middleware/test_trace.rb +106 -0
- data/test/fbe/test_octo.rb +44 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f70d456f08cfacd13990d45fd62834c1856260dd096583909fe4aa2b113f080
|
4
|
+
data.tar.gz: edd2a32689116bf7ae2940458f2e77cf6d33027097e3a01985af685d78f666b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a5371878121330954776a6fc2d7cc32ef4207afd7c6e73b8ac4399c6af222abbfda3afd7d2636eadb7bf3f305984ed547d755666e02d38d3a54c0479c289e15
|
7
|
+
data.tar.gz: 76cf0627e9c1bd78b9ad1e348ff5c6fce2f0c9e8374d2a0b09ae93c1b4faa27b240446f30abb1fb7821bcf5c18b5bd118b7f54d3615843773ebe3b1edcbca4c8
|
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/Rakefile
CHANGED
@@ -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
@@ -9,10 +9,12 @@ require 'faraday/retry'
|
|
9
9
|
require 'loog'
|
10
10
|
require 'obk'
|
11
11
|
require 'octokit'
|
12
|
+
require 'uri'
|
12
13
|
require 'verbose'
|
13
14
|
require_relative '../fbe'
|
14
15
|
require_relative 'middleware'
|
15
16
|
require_relative 'middleware/formatter'
|
17
|
+
require_relative 'middleware/trace'
|
16
18
|
|
17
19
|
# Makes a call to the GitHub API.
|
18
20
|
#
|
@@ -30,6 +32,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
30
32
|
raise 'The $loog is not set' if loog.nil?
|
31
33
|
global[:octo] ||=
|
32
34
|
begin
|
35
|
+
trace = []
|
33
36
|
if options.testing.nil?
|
34
37
|
o = Octokit::Client.new
|
35
38
|
token = options.github_token
|
@@ -50,8 +53,10 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
50
53
|
loog.warn('The GitHub API token is an empty string, won\'t use it')
|
51
54
|
else
|
52
55
|
o = Octokit::Client.new(access_token: token)
|
53
|
-
loog.info(
|
54
|
-
|
56
|
+
loog.info(
|
57
|
+
"Accessing GitHub API with a token (#{token.length} chars, ending by #{token[-4..].inspect}, " \
|
58
|
+
"#{Octokit::Client.new(access_token: token).rate_limit.remaining} quota remaining)"
|
59
|
+
)
|
55
60
|
end
|
56
61
|
o.auto_paginate = true
|
57
62
|
o.per_page = 100
|
@@ -76,6 +81,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
76
81
|
builder.use(Faraday::HttpCache, serializer: Marshal, shared_cache: false, logger: Loog::NULL)
|
77
82
|
builder.use(Octokit::Response::RaiseError)
|
78
83
|
builder.use(Faraday::Response::Logger, loog, formatter: Fbe::Middleware::Formatter)
|
84
|
+
builder.use(Fbe::Middleware::Trace, trace)
|
79
85
|
builder.adapter(Faraday.default_adapter)
|
80
86
|
end
|
81
87
|
o.middleware = stack
|
@@ -84,7 +90,25 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
84
90
|
loog.debug('The connection to GitHub API is mocked')
|
85
91
|
o = Fbe::FakeOctokit.new
|
86
92
|
end
|
87
|
-
decoor(o, loog:) do
|
93
|
+
decoor(o, loog:, trace:) do
|
94
|
+
def print_trace!
|
95
|
+
if @trace.empty?
|
96
|
+
@loog.debug('GitHub API trace is empty')
|
97
|
+
else
|
98
|
+
grouped =
|
99
|
+
@trace.group_by do |entry|
|
100
|
+
uri = URI.parse(entry[:url])
|
101
|
+
"#{uri.scheme}://#{uri.host}#{uri.path}"
|
102
|
+
end
|
103
|
+
message = grouped
|
104
|
+
.sort_by { |_path, entries| -entries.count }
|
105
|
+
.map { |path, entries| " #{path}: #{entries.count}" }
|
106
|
+
.join("\n")
|
107
|
+
@loog.info("GitHub API trace (#{grouped.count} URLs vs #{@trace.count} requests):\n#{message}")
|
108
|
+
@trace.clear
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
88
112
|
def off_quota(threshold: 50)
|
89
113
|
left = @origin.rate_limit.remaining
|
90
114
|
if left < threshold
|
data/lib/fbe.rb
CHANGED
@@ -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_octo.rb
CHANGED
@@ -322,4 +322,48 @@ 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
|
325
369
|
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.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -307,6 +307,7 @@ files:
|
|
307
307
|
- lib/fbe/just_one.rb
|
308
308
|
- lib/fbe/middleware.rb
|
309
309
|
- lib/fbe/middleware/formatter.rb
|
310
|
+
- lib/fbe/middleware/trace.rb
|
310
311
|
- lib/fbe/octo.rb
|
311
312
|
- lib/fbe/overwrite.rb
|
312
313
|
- lib/fbe/pmp.rb
|
@@ -318,6 +319,7 @@ files:
|
|
318
319
|
- renovate.json
|
319
320
|
- rules/basic.fe
|
320
321
|
- test/fbe/middleware/test_formatter.rb
|
322
|
+
- test/fbe/middleware/test_trace.rb
|
321
323
|
- test/fbe/test_award.rb
|
322
324
|
- test/fbe/test_bylaws.rb
|
323
325
|
- test/fbe/test_conclude.rb
|