Skip to content

Commit 9a90b80

Browse files
committed
Update code to work with new docker compose
1 parent bfbc60f commit 9a90b80

File tree

5 files changed

+217
-29
lines changed

5 files changed

+217
-29
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
name: ci
22

33
on:
4-
push:
5-
workflow_dispatch:
4+
- push
5+
- workflow_dispatch
6+
- pull_request
67

78
env:
89
COMPOSE_DOCKER_CLI_BUILD: 1
@@ -37,7 +38,7 @@ jobs:
3738
database-password: 'root'
3839
database-port: '3306'
3940
docker-compose-file: 'docker-compose.ci.mysql.yml'
40-
- ruby-version: '2.4.2'
41+
- ruby-version: '3.3.5'
4142
database-adapter: 'postgresql'
4243
database-user: 'postgres'
4344
database-password: 'postgres'
@@ -57,7 +58,7 @@ jobs:
5758
docker-compose-file: 'docker-compose.ci.postgresql.yml'
5859
steps:
5960
- name: Checkout code
60-
uses: actions/checkout@v2
61+
uses: actions/checkout@v4
6162
- name: Set up Ruby
6263
uses: ruby/setup-ruby@v1
6364
with:
@@ -66,8 +67,8 @@ jobs:
6667
- name: Start stack
6768
run: |
6869
cd docker
69-
docker-compose -p it -f ${{ matrix.docker-compose-file }} up --no-start
70-
docker start it_db_1
70+
docker compose -p it -f ${{ matrix.docker-compose-file }} up --no-start
71+
docker start it-db-1
7172
- name: Wait for MySQL
7273
if: ${{ matrix.docker-compose-file == 'docker-compose.ci.mysql.yml' }}
7374
run: |
@@ -100,7 +101,7 @@ jobs:
100101
# Sometimes it gets stuck (if Kill Bill starts when the DB isn't ready?)
101102
timeout-minutes: 4
102103
run: |
103-
docker start it_killbill_1
104+
docker start it-killbill-1
104105
count=0
105106
until $(curl --connect-timeout 10 --max-time 30 --output /dev/null --silent --fail http://${KB_ADDRESS}:${KB_PORT}/1.0/healthcheck); do
106107
if [[ "$count" == "180" ]]; then
@@ -148,10 +149,10 @@ jobs:
148149
echo "[DEBUG] docker ps -a"
149150
docker ps -a
150151
echo "[DEBUG] killbill env"
151-
docker exec it_killbill_1 env || true
152+
docker exec it-killbill-1 env || true
152153
echo "[DEBUG] db env"
153-
docker exec it_db_1 env || true
154+
docker exec it-db-1 env || true
154155
echo "[DEBUG] killbill logs"
155-
docker logs -t --details it_killbill_1 || true
156+
docker logs -t --details it-killbill-1 || true
156157
echo "[DEBUG] db logs"
157-
docker logs -t --details it_db_1 || true
158+
docker logs -t --details it-db-1 || true

docker/docker-compose.ci.mysql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: '3.8'
33
services:
44
killbill:
55
network_mode: host
6-
image: killbill/killbill:0.22.20
6+
image: killbill/killbill:0.24.0
77
environment:
88
- KILLBILL_CATALOG_URI=SpyCarAdvanced.xml
99
- KILLBILL_DAO_URL=jdbc:mysql://127.0.0.1:3306/killbill
@@ -16,6 +16,6 @@ services:
1616
- db
1717
db:
1818
network_mode: host
19-
image: killbill/mariadb:0.22
19+
image: killbill/mariadb:0.24
2020
environment:
2121
- MYSQL_ROOT_PASSWORD=root

docker/docker-compose.ci.postgresql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: '3.8'
33
services:
44
killbill:
55
network_mode: host
6-
image: killbill/killbill:0.22.20
6+
image: killbill/killbill:0.24.0
77
environment:
88
- KILLBILL_CATALOG_URI=SpyCarAdvanced.xml
99
- KILLBILL_DAO_URL=jdbc:postgresql://127.0.0.1:5432/killbill
@@ -16,6 +16,6 @@ services:
1616
- db
1717
db:
1818
network_mode: host
19-
image: killbill/postgresql:0.22
19+
image: killbill/postgresql:0.24
2020
environment:
2121
- POSTGRES_PASSWORD=postgres

lib/killbill_client/api/net_http_adapter.rb

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,75 @@ def build_uri(relative_uri, options)
4040
uri_parts = relative_uri.split('?', 2)
4141
path_part = uri_parts[0]
4242
query_part = uri_parts[1]
43-
unsafe_regex = /[^a-zA-Z0-9\-_.!~*'():\/]/
44-
# Only encode the path part, not the query string
45-
encoded_path = unsafe_regex.match?(path_part) ? CGI.escape(path_part) : path_part
46-
47-
# Reconstruct the relative_uri with encoded path
48-
encoded_relative_uri = query_part ? "#{encoded_path}?#{query_part}" : encoded_path
43+
44+
# Check if this is an absolute URI (has scheme) by looking for protocol pattern
45+
is_absolute_uri = path_part.match?(/\A[a-z][a-z0-9+.-]*:\/\//i)
46+
47+
if is_absolute_uri
48+
# This is an absolute URI, parse it carefully
49+
begin
50+
# Parse the URI components manually to handle spaces properly
51+
if path_part.match(/\A([a-z][a-z0-9+.-]*):\/\/([^\/]+)(\/.*)?/i)
52+
scheme = $1
53+
authority = $2 # host:port
54+
path = $3 || '/'
55+
56+
# Encode only the path segments, not the scheme or authority
57+
if path && path != '/'
58+
path_segments = path.split('/')
59+
encoded_segments = path_segments.map do |segment|
60+
# Skip encoding if the segment is already encoded (contains %XX patterns)
61+
if segment.match?(/%[0-9A-Fa-f]{2}/)
62+
segment
63+
else
64+
unsafe_regex = /[^a-zA-Z0-9\-_.!~*'()]/
65+
if unsafe_regex.match?(segment)
66+
CGI.escape(segment).gsub('+', '%20')
67+
else
68+
segment
69+
end
70+
end
71+
end
72+
encoded_path = encoded_segments.join('/')
73+
else
74+
encoded_path = path
75+
end
76+
77+
encoded_relative_uri = "#{scheme}://#{authority}#{encoded_path}"
78+
encoded_relative_uri += "?#{query_part}" if query_part
79+
else
80+
# Fallback: treat as relative if parsing fails
81+
is_absolute_uri = false
82+
end
83+
rescue
84+
# Fallback: treat as relative if any error occurs
85+
is_absolute_uri = false
86+
end
87+
end
88+
89+
unless is_absolute_uri
90+
# This is a relative URI, encode path segments individually
91+
path_segments = path_part.split('/')
92+
encoded_segments = path_segments.map do |segment|
93+
# Skip encoding if the segment is already encoded (contains %XX patterns)
94+
if segment.match?(/%[0-9A-Fa-f]{2}/)
95+
segment
96+
else
97+
# Only encode segments that contain unsafe characters
98+
unsafe_regex = /[^a-zA-Z0-9\-_.!~*'()]/
99+
if unsafe_regex.match?(segment)
100+
# Use CGI.escape and replace + with %20 for URL path encoding
101+
CGI.escape(segment).gsub('+', '%20')
102+
else
103+
segment
104+
end
105+
end
106+
end
107+
encoded_path = encoded_segments.join('/')
108+
encoded_relative_uri = query_part ? "#{encoded_path}?#{query_part}" : encoded_path
109+
end
49110

50-
if URI(encoded_relative_uri).scheme.nil?
111+
if !is_absolute_uri
51112
uri = (options[:base_uri] || KillBillClient::API.base_uri)
52113
uri = URI.parse(uri) unless uri.is_a?(URI)
53114
# Note: make sure to keep the full path (if any) from URI::HTTP, for non-ROOT deployments
@@ -80,17 +141,25 @@ def encode_params(options = {})
80141
# so remove with from global hash and insert them under :params
81142
plugin_properties = options.delete :pluginProperty
82143
if plugin_properties && plugin_properties.size > 0
144+
options[:params] ||= {}
83145
options[:params][:pluginProperty] = plugin_properties.map { |p| "#{CGI.escape p.key.to_s}=#{CGI.escape p.value.to_s}" }
84146
end
85147

86148
control_plugin_names = options.delete(:controlPluginNames)
87-
options[:params][:controlPluginName] = control_plugin_names if control_plugin_names
149+
if control_plugin_names
150+
options[:params] ||= {}
151+
options[:params][:controlPluginName] = control_plugin_names
152+
end
88153

89154
return nil unless (options[:params] && !options[:params].empty?)
90155

91-
options[:params][:withStackTrace] = true if (options[:return_full_stacktraces] || KillBillClient.return_full_stacktraces)
156+
if (options[:return_full_stacktraces] || KillBillClient.return_full_stacktraces)
157+
options[:params][:withStackTrace] = true
158+
end
159+
160+
pairs = options[:params].filter_map { |key, value|
161+
next if value.nil?
92162

93-
pairs = options[:params].map { |key, value|
94163
# If the value is an array, we 'demultiplex' into several
95164
if value.is_a? Array
96165
internal_pairs = value.map do |simple_value|
@@ -102,6 +171,7 @@ def encode_params(options = {})
102171
end
103172
}
104173
pairs.flatten!
174+
return nil if pairs.empty?
105175
"?#{pairs.join '&'}"
106176
end
107177

spec/killbill_client/http_adapter_spec.rb

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,11 @@
149149
end
150150

151151
context 'with path encoding' do
152-
it 'should handle properly encoded URIs (with double encoding)' do
152+
it 'should handle already encoded URIs without double encoding' do
153153
relative_uri = '/1.0/kb/accounts/my%20account%20with%20spaces'
154154
options = {}
155155
uri = http_adapter.send(:build_uri, relative_uri, options)
156-
expect(uri.to_s).to eq('http://example.com:8080/%2F1.0%2Fkb%2Faccounts%2Fmy%2520account%2520with%2520spaces')
156+
expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/my%20account%20with%20spaces')
157157
end
158158

159159
it 'should not encode safe characters in path' do
@@ -163,11 +163,11 @@
163163
expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/abc-1234')
164164
end
165165

166-
it 'should handle properly encoded URI with query string (with double encoding)' do
166+
it 'should handle already encoded URI with query string without double encoding' do
167167
relative_uri = '/1.0/kb/accounts/my%20account?search=test%20value'
168168
options = {}
169169
uri = http_adapter.send(:build_uri, relative_uri, options)
170-
expect(uri.to_s).to eq('http://example.com:8080/%2F1.0%2Fkb%2Faccounts%2Fmy%2520account?search=test%20value')
170+
expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/my%20account?search=test%20value')
171171
end
172172
end
173173

@@ -238,6 +238,123 @@
238238
expect(uri.query).to include('controlPluginName=plugin2')
239239
end
240240
end
241+
242+
context 'with spaces in path segments' do
243+
it 'should encode spaces in relative URI path segments' do
244+
relative_uri = '/1.0/kb/accounts/search/Kill Bill Client'
245+
options = {}
246+
uri = http_adapter.send(:build_uri, relative_uri, options)
247+
expect(uri.to_s).to eq('http://example.com:8080/1.0/kb/accounts/search/Kill%20Bill%20Client')
248+
end
249+
250+
it 'should handle absolute URI with spaces in path' do
251+
relative_uri = 'http://127.0.0.1:8080/1.0/kb/accounts/search/Kill Bill Client'
252+
options = {}
253+
uri = http_adapter.send(:build_uri, relative_uri, options)
254+
expect(uri.to_s).to eq('http://127.0.0.1:8080/1.0/kb/accounts/search/Kill%20Bill%20Client')
255+
end
256+
257+
it 'should preserve scheme and host when encoding spaces in absolute URI' do
258+
relative_uri = 'https://api.example.com:9090/search/My Test Account'
259+
options = {}
260+
uri = http_adapter.send(:build_uri, relative_uri, options)
261+
expect(uri.to_s).to eq('https://api.example.com:9090/search/My%20Test%20Account')
262+
expect(uri.scheme).to eq('https')
263+
expect(uri.host).to eq('api.example.com')
264+
expect(uri.port).to eq(9090)
265+
end
266+
end
267+
268+
context 'with nil values in query parameters' do
269+
it 'should skip nil values in params' do
270+
relative_uri = '/1.0/kb/accounts'
271+
options = {
272+
params: {
273+
account: nil,
274+
withStackTrace: true,
275+
limit: 10
276+
}
277+
}
278+
uri = http_adapter.send(:build_uri, relative_uri, options)
279+
expect(uri.query).not_to include('account=')
280+
expect(uri.query).to include('withStackTrace=true')
281+
expect(uri.query).to include('limit=10')
282+
end
283+
284+
it 'should handle all nil values in params' do
285+
relative_uri = '/1.0/kb/accounts'
286+
options = {
287+
params: {
288+
account: nil,
289+
tenant: nil
290+
}
291+
}
292+
uri = http_adapter.send(:build_uri, relative_uri, options)
293+
expect(uri.query).to be_nil
294+
end
295+
296+
it 'should handle mixed nil and valid values' do
297+
relative_uri = '/1.0/kb/accounts'
298+
options = {
299+
params: {
300+
account: nil,
301+
withStackTrace: true,
302+
offset: 0,
303+
search: nil,
304+
limit: 50
305+
}
306+
}
307+
uri = http_adapter.send(:build_uri, relative_uri, options)
308+
expect(uri.query).not_to include('account=')
309+
expect(uri.query).not_to include('search=')
310+
expect(uri.query).to include('withStackTrace=true')
311+
expect(uri.query).to include('offset=0')
312+
expect(uri.query).to include('limit=50')
313+
end
314+
end
315+
end
316+
317+
describe '#encode_params' do
318+
let(:http_adapter) { DummyForHTTPAdapter.new }
319+
320+
it 'should filter out nil values from params' do
321+
options = {
322+
params: {
323+
account: nil,
324+
withStackTrace: true,
325+
limit: 10,
326+
search: nil
327+
}
328+
}
329+
query_string = http_adapter.send(:encode_params, options)
330+
expect(query_string).to include('withStackTrace=true')
331+
expect(query_string).to include('limit=10')
332+
expect(query_string).not_to include('account=')
333+
expect(query_string).not_to include('search=')
334+
end
335+
336+
it 'should return nil when all params are nil' do
337+
options = {
338+
params: {
339+
account: nil,
340+
tenant: nil
341+
}
342+
}
343+
query_string = http_adapter.send(:encode_params, options)
344+
expect(query_string).to be_nil
345+
end
346+
347+
it 'should return nil when params hash is empty' do
348+
options = { params: {} }
349+
query_string = http_adapter.send(:encode_params, options)
350+
expect(query_string).to be_nil
351+
end
352+
353+
it 'should handle options without params key' do
354+
options = { account: nil, withStackTrace: true }
355+
query_string = http_adapter.send(:encode_params, options)
356+
expect(query_string).to be_nil
357+
end
241358
end
242359
end
243360

0 commit comments

Comments
 (0)