Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Use `PATCH` not `POST` when enabling or disabling proxies when Toxiproxy supports `PATCH`.
([#186](https://github.com/Shopify/toxiproxy-ruby/pull/186), @brendo)
- Set HTTP timeout of 5s when communicating with Toxiproxy server.
([#85](https://github.com/Shopify/toxiproxy-ruby/pull/85), @casperisfine)

## [2.0.2] - 2022-09-02
### Fixed
- Fix uninitialized instance variable warning.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![Gem Version](https://badge.fury.io/rb/toxiproxy.svg)](https://badge.fury.io/rb/toxiproxy)
[![Test](https://github.com/Shopify/toxiproxy-ruby/actions/workflows/test.yml/badge.svg)](https://github.com/Shopify/toxiproxy-ruby/actions/workflows/test.yml)

`toxiproxy-ruby` `>= 1.x` is compatible with the Toxiproxy `2.x` series.
`toxiproxy-ruby` `0.x` is compatible with the Toxiproxy `1.x` series.
- `toxiproxy-ruby` `>= 1.x` is compatible with the Toxiproxy `2.x` series.
- `toxiproxy-ruby` `0.x` is compatible with the Toxiproxy `1.x` series.

[Toxiproxy](https://github.com/shopify/toxiproxy) is a proxy to simulate network
and system conditions. The Ruby API aims to make it simple to write tests that
Expand Down
2 changes: 1 addition & 1 deletion bin/start-toxiproxy.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash -e

VERSION='v2.4.0'
VERSION='v2.12.0'

if [[ "$OSTYPE" == "linux"* ]]; then
DOWNLOAD_TYPE="linux-amd64"
Expand Down
41 changes: 39 additions & 2 deletions lib/toxiproxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,11 @@ def down(&block)

# Disables a Toxiproxy. This will drop all active connections and stop the proxy from listening.
def disable
request = Net::HTTP::Post.new("/proxies/#{name}")
request = if server_supports_patch?
Net::HTTP::Patch.new("/proxies/#{name}")
else
Net::HTTP::Post.new("/proxies/#{name}")
end
request["Content-Type"] = "application/json"

hash = { enabled: false }
Expand All @@ -225,7 +229,11 @@ def disable

# Enables a Toxiproxy. This will cause the proxy to start listening again.
def enable
request = Net::HTTP::Post.new("/proxies/#{name}")
request = if server_supports_patch?
Net::HTTP::Patch.new("/proxies/#{name}")
else
Net::HTTP::Post.new("/proxies/#{name}")
end
request["Content-Type"] = "application/json"

hash = { enabled: true }
Expand Down Expand Up @@ -283,6 +291,35 @@ def toxics

private

def version_string
return @version_string if @version_string

version_response = self.class.version
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so i'm understanding this correctly - self.class.version contains a JSON blob that it gets from the server?

Copy link
Copy Markdown
Contributor Author

@brendo brendo Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! It turns out Toxiproxy.version is already exposed. This method will hit the underlying toxiproxy server's /version endpoint and return the result as a string.

def version
return false unless running?
request = Net::HTTP::Get.new("/version")
response = http_request(request)
assert_response(response)
response.body
end

That return will be a JSON blob, and while I wanted to parse and extract version from it all there that'd be a breaking change for anyone who is already doing that work. It's probably something we could change in a major release because while folks might be doing that, I think they'd prefer if the library did it for them :D

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

return false if version_response == false

@version_string = begin
JSON.parse(version_response)["version"]
rescue JSON::ParserError
false
end
end

# Check if the toxiproxy server version supports PATCH for enable/disable
def server_supports_patch?
version_str = version_string
return false if version_str == false

begin
# Use Gem::Version for proper version comparison
current_version = Gem::Version.new(version_str.sub(/^v/, "")) # Remove 'v' prefix if present
required_version = Gem::Version.new("2.6.0")
current_version >= required_version
rescue ArgumentError
# Invalid version format
false
end
end

def http_request(request)
self.class.http_request(request)
end
Expand Down
138 changes: 138 additions & 0 deletions test/toxiproxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,144 @@ def test_version
assert_instance_of(String, Toxiproxy.version)
end

def test_server_supports_patch_with_version_2_6_0
Toxiproxy.stub(:version, '{"version": "2.6.0"}') do
assert(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_server_supports_patch_with_version_2_7_0
Toxiproxy.stub(:version, '{"version": "2.7.0"}') do
assert(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_server_supports_patch_with_version_3_0_0
Toxiproxy.stub(:version, '{"version": "3.0.0"}') do
assert(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_does_not_support_patch_for_enable_disable_with_version_below_2_6_0
Toxiproxy.stub(:version, '{"version": "2.5.0"}') do
refute(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_does_not_support_patch_for_enable_disable_when_not_running
Toxiproxy.stub(:running?, false) do
refute(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_does_not_support_patch_for_enable_disable_with_invalid_version
Toxiproxy.stub(:version, "invalid") do
refute(Toxiproxy.new(upstream: "localhost:3306", name: "test").send(:server_supports_patch?))
end
end

def test_disable_uses_patch_when_version_supports_it
proxy = Toxiproxy.new(upstream: "localhost:3306", name: "test_proxy_patch")

# Mock version to return JSON with 2.6.0 (supports PATCH)
Toxiproxy.stub(:version, '{"version": "2.6.0"}') do
# Mock the http_request method to capture the request type
request_captured = nil
proxy.stub(:http_request, ->(req) {
request_captured = req
double = Object.new
double.define_singleton_method(:value) do
nil
end
double
}) do
proxy.disable
end

# Verify PATCH was used
assert_instance_of(Net::HTTP::Patch, request_captured)
assert_equal("/proxies/test_proxy_patch", request_captured.path)
assert_equal({ enabled: false }.to_json, request_captured.body)
end
end

def test_enable_uses_patch_when_version_supports_it
proxy = Toxiproxy.new(upstream: "localhost:3306", name: "test_proxy_patch_enable")

# Mock version to return JSON with 2.6.0 (supports PATCH)
Toxiproxy.stub(:version, '{"version": "2.6.0"}') do
# Mock the http_request method to capture the request type
request_captured = nil
proxy.stub(:http_request, ->(req) {
request_captured = req
double = Object.new
double.define_singleton_method(:value) do
nil
end
double
}) do
proxy.enable
end

# Verify PATCH was used
assert_instance_of(Net::HTTP::Patch, request_captured)
assert_equal("/proxies/test_proxy_patch_enable", request_captured.path)
assert_equal({ enabled: true }.to_json, request_captured.body)
end
end

def test_disable_uses_post_when_version_does_not_support_patch
proxy = Toxiproxy.new(upstream: "localhost:3306", name: "test_proxy_post")

# Mock version to return JSON with 2.5.0 (does not support PATCH)

Toxiproxy.stub(:version, '{"version": "2.5.0"}') do
# Mock the http_request method to capture the request type
request_captured = nil
proxy.stub(:http_request, ->(req) {
request_captured = req
double = Object.new
double.define_singleton_method(:value) do
nil
end
double
}) do
proxy.disable
end

# Verify POST was used
assert_instance_of(Net::HTTP::Post, request_captured)
assert_equal("/proxies/test_proxy_post", request_captured.path)
assert_equal({ enabled: false }.to_json, request_captured.body)
end
end

def test_enable_uses_post_when_version_does_not_support_patch
proxy = Toxiproxy.new(upstream: "localhost:3306", name: "test_proxy_post_enable")

# Mock version to return JSON with 2.5.0 (does not support PATCH)

Toxiproxy.stub(:version, '{"version": "2.5.0"}') do
# Mock the http_request method to capture the request type
request_captured = nil
proxy.stub(:http_request, ->(req) {
request_captured = req
double = Object.new
double.define_singleton_method(:value) do
nil
end
double
}) do
proxy.enable
end

# Verify POST was used
assert_instance_of(Net::HTTP::Post, request_captured)
assert_equal("/proxies/test_proxy_post_enable", request_captured.path)
assert_equal({ enabled: true }.to_json, request_captured.body)
end
end

def test_multiple_of_same_toxic_type
with_tcpserver(receive: true) do |port|
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
Expand Down