Skip to content
This repository has been archived by the owner on Feb 28, 2023. It is now read-only.

Add support for idempotency_key and timestamp #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
11 changes: 10 additions & 1 deletion lib/heap/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def add_user_properties(identity, properties)
# each value must be a Number or String with fewer than 1024 characters
# @return [HeapAPI::Client] self
# @see https://heapanalytics.com/docs/server-side#track
def track(event, identity, properties = nil)
def track(event, identity, properties = nil, options = {})
options ||= {}
ensure_valid_app_id!

event_name = event.to_s
Expand All @@ -123,6 +124,14 @@ def track(event, identity, properties = nil)
ensure_valid_properties! properties
end

unless options[:timestamp].nil?
body[:timestamp] = ensure_valid_timestamp!(options[:timestamp])
end

unless options[:idempotency_key].nil?
body[:idempotency_key] = ensure_valid_idempotency_key!(options[:idempotency_key])
end

response = connection.post '/api/track', body,
'User-Agent' => user_agent
raise HeapAPI::ApiError.new(response) unless response.success?
Expand Down
48 changes: 48 additions & 0 deletions lib/heap/validations.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Internal methods used to validate API input.
require 'date'

class HeapAPI::Client
# Makes sure that the client's app_id property is set.
Expand Down Expand Up @@ -43,6 +44,45 @@ def ensure_valid_identity!(identity)
end
private :ensure_valid_identity!

# Validates timestamp, making sure it's a valid iso8061 timestamp or
# number of milliseconds since epoch
#
# @param [String|Integer|DateTime|Time] timestamp
# @raise ArgumentError if timestamp is of an invalid type
# @return [String] unix epoch milliseconds or iso8061
def ensure_valid_timestamp!(timestamp)
if timestamp.kind_of?(Time)
timestamp = timestamp.to_datetime
end
if timestamp.kind_of?(DateTime)
timestamp = timestamp.strftime('%Q').to_i
end
if timestamp.kind_of?(String) && iso8601?(timestamp)
timestamp
elsif timestamp.kind_of?(Integer)
timestamp.to_s
else
raise ArgumentError,
"Unsupported timestamp format #{timestamp.inspect}. " +
"Must be iso8601 or unix epoch milliseconds."
end
end
private :ensure_valid_timestamp!

# Validate idempotency_key, making sure it's a string
#
# @param [String|Integer] idempotency_key
# @raise ArgumentError if identity is of an invalid type or too long.
# @return [String] stringified idempotency_key
def ensure_valid_idempotency_key!(idempotency_key)
unless idempotency_key.kind_of?(String) || idempotency_key.kind_of?(Integer)
raise ArgumentError, "Unsupported idempotency key format for " +
"#{idempotency_key.inspect}. Must be string or integer"
end
idempotency_key.to_s
end
private :ensure_valid_idempotency_key!

# Validates a bag of properties sent to a Heap server-side API.
#
# @param [Hash<String, String|Number>] properties key-value property bag;
Expand Down Expand Up @@ -74,4 +114,12 @@ def ensure_valid_properties!(properties)
end
end
private :ensure_valid_properties!

def iso8601?(string)
Time.iso8601(string)
true
rescue ArgumentError
false
end
private :iso8601?
end
88 changes: 88 additions & 0 deletions test/client_track_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,33 @@ def test_track_with_array_property_value
exception.message
end

def test_track_with_non_date_timestamp
exception = assert_raises ArgumentError do
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :timestamp => 'foobar'
end
assert_equal ArgumentError, exception.class
assert_equal "Unsupported timestamp format #{"foobar".inspect}. Must be iso8601 or unix epoch milliseconds.",
exception.message
end

def test_track_with_array_timestamp
exception = assert_raises ArgumentError do
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :timestamp => []
end
assert_equal ArgumentError, exception.class
assert_equal 'Unsupported timestamp format []. Must be iso8601 or unix epoch milliseconds.',
exception.message
end

def test_track_with_array_idempotency_key
exception = assert_raises ArgumentError do
@heap.track 'test_track_with_array_property_value', 'test-identity', {}, :idempotency_key => []
end
assert_equal ArgumentError, exception.class
assert_equal 'Unsupported idempotency key format for []. Must be string or integer',
exception.message
end

def test_track
@stubs.post '/api/track' do |env|
golden_body = {
Expand Down Expand Up @@ -174,6 +201,67 @@ def test_track_with_properties
'test-identity','foo' => 'bar', :heap => :hurray)
end

def test_track_with_timestamp
@stubs.post '/api/track' do |env|
golden_body = {
'app_id' => 'test-app-id',
'identity' => 'test-identity',
'event' => 'test_track_with_timestamp',
'properties' => {},
'timestamp' => '1524038400000'
}
assert_equal 'application/json', env[:request_headers]['Content-Type']
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
assert_equal golden_body, JSON.parse(env[:body])

[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
end

assert_equal @heap, @heap.track('test_track_with_timestamp',
'test-identity', {}, :timestamp => Time.parse("2018-04-18 08:00:00 UTC"))
end

def test_track_with_iso8601_timestamp
timestamp = "2018-04-18T22:42:38+03:00"
@stubs.post '/api/track' do |env|
golden_body = {
'app_id' => 'test-app-id',
'identity' => 'test-identity',
'event' => 'test_track_with_iso8601_timestamp',
'properties' => {},
'timestamp' => timestamp
}
assert_equal 'application/json', env[:request_headers]['Content-Type']
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
assert_equal golden_body, JSON.parse(env[:body])

[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
end

assert_equal @heap, @heap.track('test_track_with_iso8601_timestamp',
'test-identity', {}, :timestamp => timestamp)
end

def test_track_with_idempotency_key
@stubs.post '/api/track' do |env|
golden_body = {
'app_id' => 'test-app-id',
'identity' => 'test-identity',
'event' => 'test_track_with_idempotency_key',
'properties' => {},
'idempotency_key' => 'foobar35214532512'
}
assert_equal 'application/json', env[:request_headers]['Content-Type']
assert_equal @heap.user_agent, env[:request_headers]['User-Agent']
assert_equal golden_body, JSON.parse(env[:body])

[200, { 'Content-Type' => 'text/plain; encoding=utf8' }, '']
end

assert_equal @heap, @heap.track('test_track_with_idempotency_key',
'test-identity', {}, :idempotency_key => 'foobar35214532512')
end

def test_track_error
@stubs.post '/api/track' do |env|
[400, { 'Content-Type' => 'text/plain; encoding=utf8' }, 'Bad request']
Expand Down