Skip to content

Commit d254368

Browse files
architectureclaude
andcommitted
test: expand test coverage from 65 to 134 tests (347 assertions)
New test files: - http_client_headers_test.rb: default headers, API key injection, JSON/form body prep, timeout config, request_options passthrough, response parsing, error handling - operation_executor_test.rb: path substitution and URL encoding, query building (params, literals, nil/OMIT skipping), JSON body with literals and assignments, form body, all file entry types (param, literal, tuple, list), streaming dispatch, headers - resources_test.rb: spec loading, class generation from spec, operation method generation, child resource accessors, caching, deep nesting, all 24 namespace accessors on Client Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a909a87 commit d254368

File tree

4 files changed

+978
-3
lines changed

4 files changed

+978
-3
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,16 @@ ruby -Ilib:test test/client_test.rb
323323
ruby -Ilib:test test/environment_test.rb
324324
```
325325

326-
**Test Coverage:**
326+
**Test Coverage (134 tests, 347 assertions):**
327327
- `operation_serialization_test.rb` - Tests request serialization for various operations
328-
- `http_client_test.rb` - Tests HTTP client behavior and file upload handling
328+
- `operation_executor_test.rb` - Tests path building, query/body/file resolution, streaming dispatch, request_options forwarding
329+
- `http_client_test.rb` - Tests file upload handling, redirect following, streaming cleanup
330+
- `http_client_headers_test.rb` - Tests default headers, API key injection, JSON/form body prep, timeouts, response parsing, error handling
331+
- `resources_test.rb` - Tests spec loading, class generation, operation methods, child resource accessors, caching, deep nesting
329332
- `utils_test.rb` - Tests utility functions (deep_dup, assign_path, deep_compact, etc.)
330333
- `upload_test.rb` - Tests Upload helper methods for files, bytes, strings, and IO
331334
- `errors_test.rb` - Tests error classes and their attributes
332-
- `client_test.rb` - Tests client initialization and resource caching
335+
- `client_test.rb` - Tests client initialization, resource caching, all namespace accessors, custom environments
333336
- `environment_test.rb` - Tests environment URL resolution
334337

335338
Launch IRB with the project on the load path:

test/http_client_headers_test.rb

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
require "test_helper"
2+
require "stringio"
3+
require "ostruct"
4+
5+
class HttpClientHeadersAndResponseTest < Minitest::Test
6+
FakeResponse = Struct.new(:status, :body, :headers) do
7+
def success?
8+
status && status >= 200 && status < 300
9+
end
10+
end
11+
12+
class CapturingConnection
13+
attr_reader :requests
14+
15+
def initialize(response)
16+
@response = response
17+
@requests = []
18+
end
19+
20+
def run_request(method, path, body, headers)
21+
request = OpenStruct.new(params: {}, options: OpenStruct.new)
22+
yield request if block_given?
23+
@requests << { method: method, path: path, body: body, headers: headers, params: request.params, timeout: request.options.timeout }
24+
@response
25+
end
26+
end
27+
28+
def make_client(api_key: "sk-test", headers: {}, timeout: nil, response: nil)
29+
response ||= FakeResponse.new(200, '{"ok":true}', {})
30+
client = ElevenLabs::HTTPClient.new(base_url: "https://api.example.com", api_key: api_key, headers: headers, timeout: timeout)
31+
connection = CapturingConnection.new(response)
32+
client.instance_variable_set(:@connection, connection)
33+
[client, connection]
34+
end
35+
36+
# --- Default headers ---
37+
38+
def test_default_headers_include_user_agent
39+
client, conn = make_client
40+
client.request(method: "GET", path: "/v1/test", headers: {})
41+
42+
sent = conn.requests.last[:headers]
43+
assert_equal "elevenlabs-ruby/#{ElevenLabs::VERSION}", sent["User-Agent"]
44+
end
45+
46+
def test_default_headers_include_fern_metadata
47+
client, conn = make_client
48+
client.request(method: "GET", path: "/v1/test", headers: {})
49+
50+
sent = conn.requests.last[:headers]
51+
assert_equal "Ruby", sent["X-Fern-Language"]
52+
assert_equal "elevenlabs-ruby", sent["X-Fern-SDK-Name"]
53+
assert_equal ElevenLabs::VERSION, sent["X-Fern-SDK-Version"]
54+
end
55+
56+
# --- API key injection ---
57+
58+
def test_api_key_injected_as_header
59+
client, conn = make_client(api_key: "my-secret-key")
60+
client.request(method: "GET", path: "/v1/test", headers: {})
61+
62+
sent = conn.requests.last[:headers]
63+
assert_equal "my-secret-key", sent["xi-api-key"]
64+
end
65+
66+
def test_no_api_key_header_when_nil
67+
client, conn = make_client(api_key: nil)
68+
client.request(method: "GET", path: "/v1/test", headers: {})
69+
70+
sent = conn.requests.last[:headers]
71+
refute sent.key?("xi-api-key")
72+
end
73+
74+
# --- Base headers merged ---
75+
76+
def test_base_headers_merged_into_request
77+
client, conn = make_client(headers: { "X-Custom" => "value" })
78+
client.request(method: "GET", path: "/v1/test", headers: {})
79+
80+
sent = conn.requests.last[:headers]
81+
assert_equal "value", sent["X-Custom"]
82+
# default headers still present
83+
assert_equal "Ruby", sent["X-Fern-Language"]
84+
end
85+
86+
# --- Per-request headers ---
87+
88+
def test_per_request_headers_override_defaults
89+
client, conn = make_client
90+
client.request(method: "GET", path: "/v1/test", headers: { "User-Agent" => "custom-agent" })
91+
92+
sent = conn.requests.last[:headers]
93+
assert_equal "custom-agent", sent["User-Agent"]
94+
end
95+
96+
# --- Additional headers from request_options ---
97+
98+
def test_additional_headers_from_request_options
99+
client, conn = make_client
100+
client.request(
101+
method: "GET",
102+
path: "/v1/test",
103+
headers: {},
104+
request_options: { additional_headers: { "X-Trace-Id" => "abc123" } }
105+
)
106+
107+
sent = conn.requests.last[:headers]
108+
assert_equal "abc123", sent["X-Trace-Id"]
109+
end
110+
111+
def test_additional_headers_symbol_keys_converted
112+
client, conn = make_client
113+
client.request(
114+
method: "GET",
115+
path: "/v1/test",
116+
headers: {},
117+
request_options: { additional_headers: { x_trace: "abc" } }
118+
)
119+
120+
sent = conn.requests.last[:headers]
121+
assert_equal "abc", sent["x_trace"]
122+
end
123+
124+
# --- JSON body preparation ---
125+
126+
def test_json_body_serialized_with_content_type
127+
client, conn = make_client
128+
client.request(method: "POST", path: "/v1/test", json: { "name" => "hello" }, headers: {})
129+
130+
req = conn.requests.last
131+
assert_equal '{"name":"hello"}', req[:body]
132+
assert_equal "application/json", req[:headers]["Content-Type"]
133+
end
134+
135+
def test_json_content_type_not_overridden_if_already_set
136+
client, conn = make_client
137+
client.request(method: "POST", path: "/v1/test", json: { "a" => 1 }, headers: { "Content-Type" => "application/json; charset=utf-8" })
138+
139+
req = conn.requests.last
140+
assert_equal "application/json; charset=utf-8", req[:headers]["Content-Type"]
141+
end
142+
143+
# --- Form body ---
144+
145+
def test_form_body_passed_directly
146+
client, conn = make_client
147+
client.request(method: "POST", path: "/v1/test", form: { "field" => "value" }, headers: {})
148+
149+
req = conn.requests.last
150+
assert_equal({ "field" => "value" }, req[:body])
151+
refute req[:headers].key?("Content-Type"), "form body should not set explicit Content-Type"
152+
end
153+
154+
# --- No body ---
155+
156+
def test_nil_body_when_no_json_or_form
157+
client, conn = make_client
158+
client.request(method: "GET", path: "/v1/test", headers: {})
159+
160+
req = conn.requests.last
161+
assert_nil req[:body]
162+
end
163+
164+
# --- Timeout ---
165+
166+
def test_default_timeout_is_240
167+
client, conn = make_client(timeout: nil)
168+
client.request(method: "GET", path: "/v1/test", headers: {})
169+
170+
assert_equal 240, conn.requests.last[:timeout]
171+
end
172+
173+
def test_custom_timeout_from_constructor
174+
client, conn = make_client(timeout: 30)
175+
client.request(method: "GET", path: "/v1/test", headers: {})
176+
177+
assert_equal 30, conn.requests.last[:timeout]
178+
end
179+
180+
def test_request_options_timeout_overrides_constructor
181+
client, conn = make_client(timeout: 30)
182+
client.request(method: "GET", path: "/v1/test", headers: {}, request_options: { timeout_in_seconds: 5 })
183+
184+
assert_equal 5, conn.requests.last[:timeout]
185+
end
186+
187+
# --- Additional query parameters ---
188+
189+
def test_additional_query_parameters_from_request_options
190+
client, conn = make_client
191+
client.request(
192+
method: "GET",
193+
path: "/v1/test",
194+
query: { "page" => 1 },
195+
headers: {},
196+
request_options: { additional_query_parameters: { "debug" => true } }
197+
)
198+
199+
params = conn.requests.last[:params]
200+
assert_equal 1, params["page"]
201+
assert_equal true, params["debug"]
202+
end
203+
204+
def test_nil_query_values_compacted
205+
client, conn = make_client
206+
client.request(method: "GET", path: "/v1/test", query: { "a" => 1, "b" => nil }, headers: {})
207+
208+
params = conn.requests.last[:params]
209+
assert_equal 1, params["a"]
210+
refute params.key?("b")
211+
end
212+
213+
# --- JSON response parsing ---
214+
215+
def test_json_response_parsed
216+
client, = make_client(response: FakeResponse.new(200, '{"items":[1,2,3]}', {}))
217+
result = client.request(method: "GET", path: "/v1/test", headers: {})
218+
219+
assert_equal({ "items" => [1, 2, 3] }, result)
220+
end
221+
222+
def test_non_json_response_returned_as_string
223+
client, = make_client(response: FakeResponse.new(200, "plain text body", {}))
224+
result = client.request(method: "GET", path: "/v1/test", headers: {})
225+
226+
assert_equal "plain text body", result
227+
end
228+
229+
def test_empty_response_returns_nil
230+
client, = make_client(response: FakeResponse.new(200, "", {}))
231+
result = client.request(method: "GET", path: "/v1/test", headers: {})
232+
233+
assert_nil result
234+
end
235+
236+
def test_nil_response_body_returns_nil
237+
client, = make_client(response: FakeResponse.new(200, nil, {}))
238+
result = client.request(method: "GET", path: "/v1/test", headers: {})
239+
240+
assert_nil result
241+
end
242+
243+
# --- Error handling ---
244+
245+
def test_http_error_raised_on_4xx
246+
client, = make_client(response: FakeResponse.new(422, '{"detail":"invalid"}', {}))
247+
248+
err = assert_raises(ElevenLabs::HTTPError) do
249+
client.request(method: "POST", path: "/v1/test", headers: {})
250+
end
251+
assert_equal 422, err.status
252+
assert_equal({ "detail" => "invalid" }, err.body)
253+
end
254+
255+
def test_http_error_with_non_json_body
256+
client, = make_client(response: FakeResponse.new(500, "Internal Server Error", {}))
257+
258+
err = assert_raises(ElevenLabs::HTTPError) do
259+
client.request(method: "GET", path: "/v1/test", headers: {})
260+
end
261+
assert_equal 500, err.status
262+
assert_equal "Internal Server Error", err.body
263+
end
264+
265+
def test_http_error_on_stream_non_success
266+
response = FakeResponse.new(401, '{"detail":"unauthorized"}', {})
267+
client = ElevenLabs::HTTPClient.new(base_url: "https://api.example.com", api_key: "bad", headers: {})
268+
conn = CapturingConnection.new(response)
269+
client.instance_variable_set(:@connection, conn)
270+
271+
enum = client.stream(method: "GET", path: "/v1/test", headers: {})
272+
err = assert_raises(ElevenLabs::HTTPError) { enum.each {} }
273+
assert_equal 401, err.status
274+
end
275+
276+
# --- Force multipart ---
277+
278+
def test_force_multipart_sends_form_as_multipart
279+
client, conn = make_client
280+
client.request(
281+
method: "POST",
282+
path: "/v1/test",
283+
form: { "field" => "value" },
284+
headers: {},
285+
force_multipart: true
286+
)
287+
288+
req = conn.requests.last
289+
# When force_multipart is true, form data is passed as body (multipart hash)
290+
assert_equal({ "field" => "value" }, req[:body])
291+
end
292+
end

0 commit comments

Comments
 (0)