Skip to content

Commit 76021b4

Browse files
afurmcrmne
andauthored
Add specs for RubyLLM utils (#494)
## What this does adds focused specs for the helper methods in RubyLLM::Utils so their behaviour around key coercion, cloning, and deep conversion stays stable ## Type of change - [x] Bug fix ## Scope check - [x] I read the [Contributing Guide](https://github.com/crmne/ruby_llm/blob/main/CONTRIBUTING.md) - [x] This aligns with RubyLLM's focus on **LLM communication** - [x] This isn't application-specific logic that belongs in user code - [x] This benefits most users, not just my specific use case ## Quality check - [x] I ran `overcommit --install` and all hooks pass - [x] I tested my changes thoroughly - [ ] For provider changes: Re-recorded VCR cassettes with `bundle exec rake vcr:record[provider_name]` - [x] All tests pass: `bundle exec rspec` - [ ] I updated documentation if needed - [x] I didn't modify auto-generated files manually (`models.json`, `aliases.json`) ## API changes - [ ] Breaking change - [ ] New public methods/classes - [ ] Changed method signatures - [x] No API changes ## Related issues <!-- none --> Co-authored-by: Carmine Paolino <[email protected]>
1 parent db07aa3 commit 76021b4

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

spec/ruby_llm/utils_spec.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe RubyLLM::Utils do
6+
describe '.hash_get' do
7+
it 'fetches a value using a symbol when the hash key is stored as a string' do
8+
hash = { 'name' => 'RubyLLM' }
9+
10+
expect(described_class.hash_get(hash, :name)).to eq('RubyLLM')
11+
end
12+
13+
it 'fetches a value using a string when the hash key is stored as a symbol' do
14+
hash = { name: 'RubyLLM' }
15+
16+
expect(described_class.hash_get(hash, 'name')).to eq('RubyLLM')
17+
end
18+
end
19+
20+
describe '.to_safe_array' do
21+
it 'returns the same array instance when the input is already an array' do
22+
items = [1, 2, 3]
23+
24+
expect(described_class.to_safe_array(items)).to equal(items)
25+
end
26+
27+
it 'wraps hashes in an array' do
28+
hash = { key: 'value' }
29+
30+
expect(described_class.to_safe_array(hash)).to eq([hash])
31+
end
32+
33+
it 'wraps non-collection values in an array' do
34+
expect(described_class.to_safe_array('value')).to eq(['value'])
35+
end
36+
end
37+
38+
describe '.deep_merge' do
39+
it 'merges nested hashes without mutating the originals' do
40+
original = { config: { retries: 3, timeout: 5 }, mode: :safe }
41+
overrides = { config: { timeout: 10 }, verbose: true }
42+
43+
result = described_class.deep_merge(original, overrides)
44+
45+
expect(result).to eq(
46+
config: { retries: 3, timeout: 10 },
47+
mode: :safe,
48+
verbose: true
49+
)
50+
expect(original).to eq(config: { retries: 3, timeout: 5 }, mode: :safe)
51+
expect(overrides).to eq(config: { timeout: 10 }, verbose: true)
52+
end
53+
end
54+
55+
describe '.deep_dup' do
56+
it 'duplicates nested arrays and hashes' do
57+
original = {
58+
metadata: {
59+
tags: %w[ruby llm],
60+
info: { version: '1.0.0' }
61+
}
62+
}
63+
64+
duplicate = described_class.deep_dup(original)
65+
66+
expect(duplicate).to eq(original)
67+
expect(duplicate).not_to equal(original)
68+
expect(duplicate[:metadata]).not_to equal(original[:metadata])
69+
expect(duplicate[:metadata][:tags]).not_to equal(original[:metadata][:tags])
70+
expect(duplicate[:metadata][:info]).not_to equal(original[:metadata][:info])
71+
end
72+
end
73+
74+
describe '.deep_stringify_keys' do
75+
it 'converts nested keys and symbol values to strings' do
76+
data = {
77+
config: {
78+
retries: 3,
79+
mode: :safe
80+
},
81+
'files' => [{ path: '/tmp/file.txt' }]
82+
}
83+
84+
expect(described_class.deep_stringify_keys(data)).to eq(
85+
'config' => {
86+
'retries' => 3,
87+
'mode' => 'safe'
88+
},
89+
'files' => [{ 'path' => '/tmp/file.txt' }]
90+
)
91+
end
92+
end
93+
94+
describe '.deep_symbolize_keys' do
95+
it 'converts nested string keys to symbols and preserves non-convertible keys' do
96+
data = {
97+
'config' => {
98+
'retries' => 3,
99+
'mode' => 'safe',
100+
'options' => [{ 'path' => '/tmp/file.txt' }]
101+
},
102+
42 => 'answer'
103+
}
104+
105+
result = described_class.deep_symbolize_keys(data)
106+
107+
expect(result[:config][:retries]).to eq(3)
108+
expect(result[:config][:mode]).to eq('safe')
109+
expect(result[:config][:options].first[:path]).to eq('/tmp/file.txt')
110+
expect(result[42]).to eq('answer')
111+
end
112+
end
113+
end

0 commit comments

Comments
 (0)