Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Introduce with_fake_seam_connect test helper to allow testing against fake-seam-connect node package #160

Merged
merged 10 commits into from
Nov 18, 2024
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
uses: ./.github/actions/setup
with:
ruby_version: ${{ matrix.ruby }}
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Test
run: bundle exec rake test
lint:
Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ GEM
thor (1.3.2)
unicode-display_width (2.6.0)
uri (1.0.1)
webmock (3.0.1)
addressable (>= 2.3.6)
webmock (3.24.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff
hashdiff (>= 0.4.0, < 2.0.0)

PLATFORMS
ruby
Expand All @@ -127,7 +127,7 @@ DEPENDENCIES
simplecov (~> 0.21)
simplecov-console (~> 0.9)
standard (~> 1.3)
webmock (~> 3.0.0)
webmock (~> 3.24.0)

BUNDLED WITH
2.5.16
127 changes: 126 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"pregenerate": "markdown-toc -i README.md --bullets '-'",
"generate": "node generate-routes.js",
"postgenerate": "rake format",
"format": "prettier --write --ignore-path .gitignore ."
"format": "prettier --write --ignore-path .gitignore .",
"start": "fake-seam-connect --seed"
},
"devDependencies": {
"@seamapi/fake-seam-connect": "1.72.1",
"@seamapi/nextlove-sdk-generator": "1.14.10",
"@seamapi/types": "1.286.1",
"del": "^7.1.0",
Expand Down
2 changes: 1 addition & 1 deletion seam.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "simplecov", "~> 0.21"
spec.add_development_dependency "simplecov-console", "~> 0.9"
spec.add_development_dependency "standard", "~> 1.3"
spec.add_development_dependency "webmock", "~> 3.0.0"
spec.add_development_dependency "webmock", "~> 3.24.0"
end
18 changes: 18 additions & 0 deletions spec/integration/basic_usage_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

RSpec.describe "Basic SDK Usage" do
it "can list devices" do
with_fake_seam_connect do |_, endpoint, seed|
seam = Seam.new(api_key: seed["seam_apikey1_token"], endpoint: endpoint)
devices = seam.devices.list
expect(devices).to be_a(Array)
expect(devices).not_to be_nil
expect(devices.length).to be > 0

device = devices.first
expect(device).to be_a(Seam::Resources::Device)
expect(device.device_id).to be_a(String)
expect(device.created_at).to be_a(Time)
end
end
end
101 changes: 98 additions & 3 deletions spec/support/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,106 @@
# frozen_string_literal: true
require "socket"
require "json"
require "net/http"
require "uri"

module Helpers
DEFAULT_TIMEOUT = 30
MAX_ATTEMPTS = 5

def stub_seam_request(method, path, response, status: 200, headers: {})
stub_request(
method,
"#{Seam::DEFAULT_ENDPOINT}#{path}"
).to_return(status: status, body: response.to_json,
headers: {"Content-Type" => "application/json"}.merge(headers))
).to_return(
status: status,
body: response.to_json,
headers: {"Content-Type" => "application/json"}.merge(headers)
)
end

def with_fake_seam_connect
port = find_available_port
ENV["PORT"] = port.to_s
endpoint = "http://localhost:#{port}"

pid = start_server
WebMock.disable_net_connect!(allow_localhost: true)

wait_for_server(endpoint)
seed = get_seed(endpoint)
seam = initialize_seam_client(endpoint, seed)
verify_server_health!(endpoint)

yield seam, endpoint, seed
ensure
cleanup_server(pid)
end

private

def find_available_port
TCPServer.new("127.0.0.1", 0).tap do |server|
@port = server.addr[1]
server.close
end
@port
end

def start_server
Process.spawn("npm run start", pgroup: true)
end

def initialize_seam_client(endpoint, seed)
Seam.new(
endpoint: endpoint,
api_key: seed["seam_apikey1_token"]
)
end

def verify_server_health!(endpoint)
uri = URI.parse("#{endpoint}/health")
response = Net::HTTP.get_response(uri)
raise "Fake test server not healthy" unless response.is_a?(Net::HTTPSuccess)
end

def wait_for_server(endpoint, max_attempts: MAX_ATTEMPTS, timeout: DEFAULT_TIMEOUT)
start_time = Time.now
attempts = 0

begin
uri = URI.parse("#{endpoint}/health")
http = Net::HTTP.new(uri.host, uri.port)
http.read_timeout = 5
http.open_timeout = 5
response = http.get(uri.path)
raise unless response.is_a?(Net::HTTPSuccess)
rescue => e
attempts += 1
if attempts < max_attempts && (Time.now - start_time) < timeout
sleep(1)
retry
else
raise "Fake test server failed to start after #{attempts} attempts or #{timeout}s timeout: #{e.message}"
end
end
end

def get_seed(endpoint)
uri = URI.parse("#{endpoint}/_fake/default_seed")
response = Net::HTTP.get(uri)
JSON.parse(response)
rescue => e
raise "Failed to get seed from fake test server: #{e.message}"
end

def cleanup_server(pid)
return unless pid

begin
Process.kill("-TERM", Process.getpgid(pid))
Process.wait(pid)
rescue Errno::ESRCH, Errno::ECHILD
# Process already terminated
end
end
end