Skip to content

Commit f206415

Browse files
redoxcursoragent
andcommitted
fix(client): reject non-hash batch items with an indexed error
Raise ArgumentError like events[0] must be a Hash instead of NoMethodError on malformed entries. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9d2be76 commit f206415

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

‎lib/altertable/client.rb‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ def try_require(gem_name)
9999
def map_batch(items, name, &block)
100100
raise ArgumentError, "#{name} must be a non-empty Array" unless items.is_a?(Array) && !items.empty?
101101

102+
items.each_with_index do |item, index|
103+
raise ArgumentError, "#{name}[#{index}] must be a Hash" unless item.is_a?(Hash)
104+
end
105+
102106
items.map(&block)
103107
end
104108

‎spec/altertable/client_batch_spec.rb‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,33 @@ def post(path, body: nil, params: {})
107107
it "raises ArgumentError when event is missing from a payload" do
108108
expect { client.track([{ distinct_id: "u1" }]) }.to raise_error(ArgumentError, /event/)
109109
end
110+
111+
it "raises ArgumentError when a batch item is not a Hash" do
112+
expect { client.track([nil]) }.to raise_error(ArgumentError, "events[0] must be a Hash")
113+
expect { client.track([{ event: "signup", distinct_id: "u1" }, "nope"]) }.to raise_error(ArgumentError, "events[1] must be a Hash")
114+
expect(adapter.calls).to be_empty
115+
end
116+
117+
it "accepts string-key hashes in a batch" do
118+
client.track([
119+
{
120+
"event" => "signup",
121+
"distinct_id" => "u1",
122+
"properties" => { "plan" => "pro" },
123+
"timestamp" => "2025-06-15T14:30:00.000Z",
124+
"anonymous_id" => "anon-1"
125+
}
126+
])
127+
128+
payload = adapter.calls.first[:body].first
129+
expect(payload).to include(
130+
"event" => "signup",
131+
"distinct_id" => "u1",
132+
"anonymous_id" => "anon-1",
133+
"timestamp" => "2025-06-15T14:30:00.000Z"
134+
)
135+
expect(payload["properties"]).to include("plan" => "pro")
136+
end
110137
end
111138

112139
describe "#identify" do
@@ -144,6 +171,18 @@ def post(path, body: nil, params: {})
144171
it "raises ArgumentError for an empty list" do
145172
expect { client.identify([]) }.to raise_error(ArgumentError, /empty/)
146173
end
174+
175+
it "raises ArgumentError when a batch item is not a Hash" do
176+
expect { client.identify([nil]) }.to raise_error(ArgumentError, "identifies[0] must be a Hash")
177+
end
178+
179+
it "accepts string-key hashes in a batch" do
180+
client.identify([
181+
{ "user_id" => "u1", "traits" => { "email" => "a@example.com" }, "timestamp" => "2025-06-15T14:30:00.000Z" }
182+
])
183+
184+
expect(adapter.calls.first[:body].first).to include("distinct_id" => "u1", "timestamp" => "2025-06-15T14:30:00.000Z")
185+
end
147186
end
148187

149188
describe "#alias" do
@@ -180,5 +219,17 @@ def post(path, body: nil, params: {})
180219
it "raises ArgumentError for an empty list" do
181220
expect { client.alias([]) }.to raise_error(ArgumentError, /empty/)
182221
end
222+
223+
it "raises ArgumentError when a batch item is not a Hash" do
224+
expect { client.alias([nil]) }.to raise_error(ArgumentError, "aliases[0] must be a Hash")
225+
end
226+
227+
it "accepts string-key hashes in a batch" do
228+
client.alias([
229+
{ "distinct_id" => "anon-1", "new_user_id" => "u1", "timestamp" => "2025-06-15T14:30:00.000Z" }
230+
])
231+
232+
expect(adapter.calls.first[:body].first).to include("distinct_id" => "anon-1", "new_user_id" => "u1")
233+
end
183234
end
184235
end

0 commit comments

Comments
 (0)