fbe 0.23.4 → 0.23.6
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/rate_limit.rb +131 -0
- data/lib/fbe/octo.rb +2 -0
- data/lib/fbe.rb +1 -1
- data/test/fbe/middleware/test_rate_limit.rb +152 -0
- data/test/fbe/test_conclude.rb +1 -5
- data/test/fbe/test_octo.rb +13 -25
- 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: bed0dbc2fe40503e276fad9620585658b3781cc02ce1fd171136ec9cae18bb67
|
4
|
+
data.tar.gz: 7ff721a9a5550eb960e4f98be1148b68e67a0198f821ba7485683022b0af2d1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f9a87c2d309bd1806aff0184d1173f903b8e5612b6564de1e7b1a4fa9c932c88793f294e33a341255d810095a161d0bdb492492510ffd19c6ffae2c14724609
|
7
|
+
data.tar.gz: c91361e3272512623ca9f3c7f7b35c056bd55e37a3baf5635525974043fc62a1d80eac2dd533385bd124c91b0ccb54167319f2691e1e439be47c30c751ae3873
|
@@ -0,0 +1,131 @@
|
|
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 'json'
|
8
|
+
require_relative '../../fbe'
|
9
|
+
require_relative '../../fbe/middleware'
|
10
|
+
|
11
|
+
# Faraday middleware that caches GitHub API rate limit information.
|
12
|
+
#
|
13
|
+
# This middleware intercepts calls to the /rate_limit endpoint and caches
|
14
|
+
# the results locally. It tracks the remaining requests count and decrements
|
15
|
+
# it for each API call. Every 100 requests, it refreshes the cached data
|
16
|
+
# by allowing the request to pass through to the GitHub API.
|
17
|
+
#
|
18
|
+
# @example Usage in Faraday middleware stack
|
19
|
+
# connection = Faraday.new do |f|
|
20
|
+
# f.use Fbe::Middleware::RateLimit
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
24
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
25
|
+
# License:: MIT
|
26
|
+
class Fbe::Middleware::RateLimit < Faraday::Middleware
|
27
|
+
# Initializes the rate limit middleware.
|
28
|
+
#
|
29
|
+
# @param [Object] app The next middleware in the stack
|
30
|
+
def initialize(app)
|
31
|
+
super
|
32
|
+
@cached_response = nil
|
33
|
+
@remaining_count = nil
|
34
|
+
@request_counter = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
# Processes the HTTP request and handles rate limit caching.
|
38
|
+
#
|
39
|
+
# @param [Faraday::Env] env The request environment
|
40
|
+
# @return [Faraday::Response] The response from cache or the next middleware
|
41
|
+
def call(env)
|
42
|
+
if env.url.path == '/rate_limit'
|
43
|
+
handle_rate_limit_request(env)
|
44
|
+
else
|
45
|
+
track_request
|
46
|
+
@app.call(env)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Handles requests to the rate_limit endpoint.
|
53
|
+
#
|
54
|
+
# @param [Faraday::Env] env The request environment
|
55
|
+
# @return [Faraday::Response] Cached or fresh response
|
56
|
+
def handle_rate_limit_request(env)
|
57
|
+
if @cached_response.nil? || @request_counter >= 100
|
58
|
+
response = @app.call(env)
|
59
|
+
@cached_response = response.dup
|
60
|
+
@remaining_count = extract_remaining_count(response)
|
61
|
+
@request_counter = 0
|
62
|
+
response
|
63
|
+
else
|
64
|
+
response = @cached_response.dup
|
65
|
+
update_remaining_count(response)
|
66
|
+
Faraday::Response.new(response_env(env, response))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Tracks non-rate_limit requests and decrements counter.
|
71
|
+
def track_request
|
72
|
+
return if @remaining_count.nil?
|
73
|
+
@remaining_count -= 1 if @remaining_count.positive?
|
74
|
+
@request_counter += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
# Extracts the remaining count from the response body.
|
78
|
+
#
|
79
|
+
# @param [Faraday::Response] response The API response
|
80
|
+
# @return [Integer] The remaining requests count
|
81
|
+
def extract_remaining_count(response)
|
82
|
+
body = response.body
|
83
|
+
if body.is_a?(String)
|
84
|
+
begin
|
85
|
+
body = JSON.parse(body)
|
86
|
+
rescue JSON::ParserError
|
87
|
+
return 0
|
88
|
+
end
|
89
|
+
end
|
90
|
+
return 0 unless body.is_a?(Hash)
|
91
|
+
body.dig('rate', 'remaining') || 0
|
92
|
+
end
|
93
|
+
|
94
|
+
# Updates the remaining count in the response body.
|
95
|
+
#
|
96
|
+
# @param [Faraday::Response] response The cached response to update
|
97
|
+
def update_remaining_count(response)
|
98
|
+
body = response.body
|
99
|
+
original_was_string = body.is_a?(String)
|
100
|
+
if original_was_string
|
101
|
+
begin
|
102
|
+
body = JSON.parse(body)
|
103
|
+
rescue JSON::ParserError
|
104
|
+
return
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return unless body.is_a?(Hash) && body['rate']
|
108
|
+
body['rate']['remaining'] = @remaining_count
|
109
|
+
return unless original_was_string
|
110
|
+
response.instance_variable_set(:@body, body.to_json)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates a response environment for the cached response.
|
114
|
+
#
|
115
|
+
# @param [Faraday::Env] env The original request environment
|
116
|
+
# @param [Faraday::Response] response The cached response
|
117
|
+
# @return [Hash] Response environment hash
|
118
|
+
def response_env(env, response)
|
119
|
+
headers = response.headers.dup
|
120
|
+
headers['x-ratelimit-remaining'] = @remaining_count.to_s if @remaining_count
|
121
|
+
{
|
122
|
+
method: env.method,
|
123
|
+
url: env.url,
|
124
|
+
request_headers: env.request_headers,
|
125
|
+
request_body: env.request_body,
|
126
|
+
status: response.status,
|
127
|
+
response_headers: headers,
|
128
|
+
body: response.body
|
129
|
+
}
|
130
|
+
end
|
131
|
+
end
|
data/lib/fbe/octo.rb
CHANGED
@@ -20,6 +20,7 @@ require 'verbose'
|
|
20
20
|
require_relative '../fbe'
|
21
21
|
require_relative 'middleware'
|
22
22
|
require_relative 'middleware/formatter'
|
23
|
+
require_relative 'middleware/rate_limit'
|
23
24
|
require_relative 'middleware/sqlite_store'
|
24
25
|
require_relative 'middleware/trace'
|
25
26
|
|
@@ -107,6 +108,7 @@ def Fbe.octo(options: $options, global: $global, loog: $loog)
|
|
107
108
|
end
|
108
109
|
builder.use(Octokit::Response::RaiseError)
|
109
110
|
builder.use(Faraday::Response::Logger, loog, formatter: Fbe::Middleware::Formatter)
|
111
|
+
builder.use(Fbe::Middleware::RateLimit)
|
110
112
|
builder.use(Fbe::Middleware::Trace, trace)
|
111
113
|
builder.adapter(Faraday.default_adapter)
|
112
114
|
end
|
data/lib/fbe.rb
CHANGED
@@ -0,0 +1,152 @@
|
|
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/rate_limit'
|
12
|
+
|
13
|
+
# Test.
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
15
|
+
# Copyright:: Copyright (c) 2024-2025 Zerocracy
|
16
|
+
# License:: MIT
|
17
|
+
class RateLimitTest < Fbe::Test
|
18
|
+
def test_caches_rate_limit_response_on_first_call
|
19
|
+
rate_limit_response = {
|
20
|
+
'rate' => {
|
21
|
+
'limit' => 5000,
|
22
|
+
'remaining' => 4999,
|
23
|
+
'reset' => 1_672_531_200
|
24
|
+
}
|
25
|
+
}
|
26
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
27
|
+
.to_return(status: 200, body: rate_limit_response.to_json, headers: { 'Content-Type' => 'application/json' })
|
28
|
+
conn = create_connection
|
29
|
+
response = conn.get('/rate_limit')
|
30
|
+
assert_equal 200, response.status
|
31
|
+
assert_equal 4999, response.body['rate']['remaining']
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_returns_cached_response_on_subsequent_calls
|
35
|
+
rate_limit_response = {
|
36
|
+
'rate' => {
|
37
|
+
'limit' => 5000,
|
38
|
+
'remaining' => 4999,
|
39
|
+
'reset' => 1_672_531_200
|
40
|
+
}
|
41
|
+
}
|
42
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
43
|
+
.to_return(status: 200, body: rate_limit_response.to_json, headers: { 'Content-Type' => 'application/json' })
|
44
|
+
.times(1)
|
45
|
+
conn = create_connection
|
46
|
+
conn.get('/rate_limit')
|
47
|
+
response = conn.get('/rate_limit')
|
48
|
+
assert_equal 200, response.status
|
49
|
+
assert_equal 4999, response.body['rate']['remaining']
|
50
|
+
assert_requested :get, 'https://api.github.com/rate_limit', times: 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_decrements_remaining_count_for_non_rate_limit_requests
|
54
|
+
rate_limit_response = {
|
55
|
+
'rate' => {
|
56
|
+
'limit' => 5000,
|
57
|
+
'remaining' => 4999,
|
58
|
+
'reset' => 1_672_531_200
|
59
|
+
}
|
60
|
+
}
|
61
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
62
|
+
.to_return(status: 200, body: rate_limit_response.to_json, headers: { 'Content-Type' => 'application/json',
|
63
|
+
'X-RateLimit-Remaining' => '4999' })
|
64
|
+
stub_request(:get, 'https://api.github.com/user')
|
65
|
+
.to_return(status: 200, body: '{"login": "test"}', headers: { 'Content-Type' => 'application/json' })
|
66
|
+
conn = create_connection
|
67
|
+
conn.get('/rate_limit')
|
68
|
+
conn.get('/user')
|
69
|
+
response = conn.get('/rate_limit')
|
70
|
+
assert_equal 4998, response.body['rate']['remaining']
|
71
|
+
assert_equal '4998', response.headers['x-ratelimit-remaining']
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_refreshes_cache_after_hundred_requests
|
75
|
+
rate_limit_response = {
|
76
|
+
'rate' => {
|
77
|
+
'limit' => 5000,
|
78
|
+
'remaining' => 4999,
|
79
|
+
'reset' => 1_672_531_200
|
80
|
+
}
|
81
|
+
}
|
82
|
+
refreshed_response = {
|
83
|
+
'rate' => {
|
84
|
+
'limit' => 5000,
|
85
|
+
'remaining' => 4950,
|
86
|
+
'reset' => 1_672_531_200
|
87
|
+
}
|
88
|
+
}
|
89
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
90
|
+
.to_return(status: 200, body: rate_limit_response.to_json, headers: { 'Content-Type' => 'application/json' })
|
91
|
+
.then
|
92
|
+
.to_return(status: 200, body: refreshed_response.to_json, headers: { 'Content-Type' => 'application/json' })
|
93
|
+
stub_request(:get, 'https://api.github.com/user')
|
94
|
+
.to_return(status: 200, body: '{"login": "test"}', headers: { 'Content-Type' => 'application/json' })
|
95
|
+
.times(100)
|
96
|
+
conn = create_connection
|
97
|
+
conn.get('/rate_limit')
|
98
|
+
100.times { conn.get('/user') }
|
99
|
+
response = conn.get('/rate_limit')
|
100
|
+
assert_equal 4950, response.body['rate']['remaining']
|
101
|
+
assert_requested :get, 'https://api.github.com/rate_limit', times: 2
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_handles_response_without_rate_data
|
105
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
106
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
107
|
+
conn = create_connection
|
108
|
+
response = conn.get('/rate_limit')
|
109
|
+
assert_equal 200, response.status
|
110
|
+
assert_empty(response.body)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_ignores_non_hash_response_body
|
114
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
115
|
+
.to_return(status: 200, body: 'invalid json', headers: { 'Content-Type' => 'text/plain' })
|
116
|
+
conn = create_connection
|
117
|
+
response = conn.get('/rate_limit')
|
118
|
+
assert_equal 200, response.status
|
119
|
+
assert_equal 'invalid json', response.body
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_handles_zero_remaining_count
|
123
|
+
rate_limit_response = {
|
124
|
+
'rate' => {
|
125
|
+
'limit' => 5000,
|
126
|
+
'remaining' => 1,
|
127
|
+
'reset' => 1_672_531_200
|
128
|
+
}
|
129
|
+
}
|
130
|
+
stub_request(:get, 'https://api.github.com/rate_limit')
|
131
|
+
.to_return(status: 200, body: rate_limit_response.to_json, headers: { 'Content-Type' => 'application/json' })
|
132
|
+
stub_request(:get, 'https://api.github.com/user')
|
133
|
+
.to_return(status: 200, body: '{"login": "test"}', headers: { 'Content-Type' => 'application/json' })
|
134
|
+
.times(2)
|
135
|
+
conn = create_connection
|
136
|
+
conn.get('/rate_limit')
|
137
|
+
conn.get('/user')
|
138
|
+
conn.get('/user')
|
139
|
+
response = conn.get('/rate_limit')
|
140
|
+
assert_equal 0, response.body['rate']['remaining']
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def create_connection
|
146
|
+
Faraday.new(url: 'https://api.github.com') do |f|
|
147
|
+
f.use Fbe::Middleware::RateLimit
|
148
|
+
f.response :json
|
149
|
+
f.adapter :net_http
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/test/fbe/test_conclude.rb
CHANGED
@@ -79,11 +79,7 @@ class TestConclude < Fbe::Test
|
|
79
79
|
}
|
80
80
|
)
|
81
81
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
82
|
-
{ body: '
|
83
|
-
{ body: 'hm...', headers: { 'X-RateLimit-Remaining' => '777' } },
|
84
|
-
{ body: 'hm...', headers: { 'X-RateLimit-Remaining' => '777' } },
|
85
|
-
{ body: 'hm...', headers: { 'X-RateLimit-Remaining' => '999' } },
|
86
|
-
{ body: 'hm...', headers: { 'X-RateLimit-Remaining' => '9' } }
|
82
|
+
{ body: '{"rate":{"remaining":51}}', headers: { 'X-RateLimit-Remaining' => '51' } }
|
87
83
|
)
|
88
84
|
global = {}
|
89
85
|
o = Fbe.octo(loog: Loog::NULL, options:, global:)
|
data/test/fbe/test_octo.rb
CHANGED
@@ -99,7 +99,7 @@ class TestOcto < Fbe::Test
|
|
99
99
|
def test_caching
|
100
100
|
WebMock.disable_net_connect!
|
101
101
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
102
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '222' } }
|
102
|
+
{ body: '{"rate":{"remaining":222}}', headers: { 'X-RateLimit-Remaining' => '222' } }
|
103
103
|
)
|
104
104
|
global = {}
|
105
105
|
o = Fbe.octo(loog: Loog::NULL, global:, options: Judges::Options.new)
|
@@ -133,12 +133,10 @@ class TestOcto < Fbe::Test
|
|
133
133
|
def test_off_quota?
|
134
134
|
WebMock.disable_net_connect!
|
135
135
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
136
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '
|
137
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '333' } },
|
138
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '3' } }
|
136
|
+
{ body: '{"rate":{"remaining":50}}', headers: { 'X-RateLimit-Remaining' => '50' } }
|
139
137
|
)
|
140
138
|
stub_request(:get, 'https://api.github.com/user/42').to_return(
|
141
|
-
body: '', headers: { 'X-RateLimit-Remaining' => '
|
139
|
+
body: '', headers: { 'X-RateLimit-Remaining' => '49' }
|
142
140
|
)
|
143
141
|
o = Fbe.octo(loog: Loog::NULL, global: {}, options: Judges::Options.new)
|
144
142
|
refute_predicate(o, :off_quota?)
|
@@ -149,11 +147,7 @@ class TestOcto < Fbe::Test
|
|
149
147
|
def test_off_quota_twice
|
150
148
|
WebMock.disable_net_connect!
|
151
149
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
152
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '
|
153
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '333' } },
|
154
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '333' } },
|
155
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '5555' } },
|
156
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '5' } }
|
150
|
+
{ body: '{"rate":{"remaining":51}}', headers: { 'X-RateLimit-Remaining' => '51' } }
|
157
151
|
)
|
158
152
|
stub_request(:get, 'https://api.github.com/user/42').to_return(
|
159
153
|
{ body: '', headers: { 'X-RateLimit-Remaining' => '5555' } },
|
@@ -372,9 +366,9 @@ class TestOcto < Fbe::Test
|
|
372
366
|
loog = Loog::Buffer.new
|
373
367
|
WebMock.disable_net_connect!
|
374
368
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
375
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '222' } },
|
376
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '222' } },
|
377
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '222' } }
|
369
|
+
{ body: '{"rate":{"remaining":222}}', headers: { 'X-RateLimit-Remaining' => '222' } },
|
370
|
+
{ body: '{"rate":{"remaining":222}}', headers: { 'X-RateLimit-Remaining' => '222' } },
|
371
|
+
{ body: '{"rate":{"remaining":222}}', headers: { 'X-RateLimit-Remaining' => '222' } }
|
378
372
|
)
|
379
373
|
stub_request(:get, 'https://api.github.com/user/123').to_return do
|
380
374
|
{
|
@@ -396,9 +390,9 @@ class TestOcto < Fbe::Test
|
|
396
390
|
octo.repository('foo/bar')
|
397
391
|
octo.print_trace!(all: true, max: 9_999)
|
398
392
|
output = loog.to_s
|
399
|
-
assert_includes output, '3 URLs vs
|
400
|
-
assert_includes output, '
|
401
|
-
assert_includes output, '/rate_limit:
|
393
|
+
assert_includes output, '3 URLs vs 4 requests'
|
394
|
+
assert_includes output, '219 quota left'
|
395
|
+
assert_includes output, '/rate_limit: 1'
|
402
396
|
assert_includes output, '/user/123: 1'
|
403
397
|
assert_includes output, '/repos/foo/bar: 2'
|
404
398
|
repo_index = output.index('/repos/foo/bar: 2')
|
@@ -539,19 +533,13 @@ class TestOcto < Fbe::Test
|
|
539
533
|
def test_fetch_rate_limit_by_making_new_request
|
540
534
|
WebMock.disable_net_connect!
|
541
535
|
stub_request(:get, 'https://api.github.com/rate_limit').to_return(
|
542
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '321' } }
|
543
|
-
)
|
544
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '123' } }
|
545
|
-
).to_return(
|
546
|
-
{ body: '{}', headers: { 'X-RateLimit-Remaining' => '1' } }
|
547
|
-
).to_raise(StandardError.new('no more requests to https://api.github.com/rate_limit'))
|
536
|
+
{ body: '{"rate":{"remaining":321}}', headers: { 'X-RateLimit-Remaining' => '321' } }
|
537
|
+
)
|
548
538
|
loog = Loog::Buffer.new
|
549
539
|
o = Fbe.octo(loog:, global: {}, options: Judges::Options.new)
|
550
540
|
refute_predicate(o, :off_quota?)
|
551
541
|
assert_match(/321 GitHub API quota left/, loog.to_s)
|
552
542
|
o.print_trace!(all: true)
|
553
|
-
assert_match(/
|
554
|
-
assert_predicate(o, :off_quota?)
|
555
|
-
assert_match(/1 GitHub API quota left/, loog.to_s)
|
543
|
+
assert_match(/321 quota left/, loog.to_s)
|
556
544
|
end
|
557
545
|
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.23.
|
4
|
+
version: 0.23.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -363,6 +363,7 @@ files:
|
|
363
363
|
- lib/fbe/just_one.rb
|
364
364
|
- lib/fbe/middleware.rb
|
365
365
|
- lib/fbe/middleware/formatter.rb
|
366
|
+
- lib/fbe/middleware/rate_limit.rb
|
366
367
|
- lib/fbe/middleware/sqlite_store.rb
|
367
368
|
- lib/fbe/middleware/trace.rb
|
368
369
|
- lib/fbe/octo.rb
|
@@ -376,6 +377,7 @@ files:
|
|
376
377
|
- renovate.json
|
377
378
|
- rules/basic.fe
|
378
379
|
- test/fbe/middleware/test_formatter.rb
|
380
|
+
- test/fbe/middleware/test_rate_limit.rb
|
379
381
|
- test/fbe/middleware/test_sqlite_store.rb
|
380
382
|
- test/fbe/middleware/test_trace.rb
|
381
383
|
- test/fbe/test_award.rb
|