astronomer 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/History.md +160 -0
- data/Makefile +8 -0
- data/README.md +91 -0
- data/Rakefile +7 -0
- data/analytics-ruby.gemspec +23 -0
- data/astronomer-2.0.13.gem +0 -0
- data/lib/segment.rb +1 -0
- data/lib/segment/analytics.rb +31 -0
- data/lib/segment/analytics/client.rb +359 -0
- data/lib/segment/analytics/defaults.rb +20 -0
- data/lib/segment/analytics/logging.rb +35 -0
- data/lib/segment/analytics/request.rb +82 -0
- data/lib/segment/analytics/response.rb +16 -0
- data/lib/segment/analytics/utils.rb +88 -0
- data/lib/segment/analytics/version.rb +5 -0
- data/lib/segment/analytics/worker.rb +60 -0
- data/spec/segment/analytics/client_spec.rb +302 -0
- data/spec/segment/analytics/request_spec.rb +191 -0
- data/spec/segment/analytics/response_spec.rb +30 -0
- data/spec/segment/analytics/worker_spec.rb +96 -0
- data/spec/segment/analytics_spec.rb +106 -0
- data/spec/spec_helper.rb +81 -0
- metadata +143 -0
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module Segment
         | 
| 2 | 
            +
              class Analytics
         | 
| 3 | 
            +
                module Defaults
         | 
| 4 | 
            +
                  module Request
         | 
| 5 | 
            +
                    HOST = 'api.astronomer.io'
         | 
| 6 | 
            +
                    PORT = 443
         | 
| 7 | 
            +
                    PATH = '/v1/import'
         | 
| 8 | 
            +
                    SSL = true
         | 
| 9 | 
            +
                    HEADERS = { :accept => 'application/json' }
         | 
| 10 | 
            +
                    RETRIES = 4
         | 
| 11 | 
            +
                    BACKOFF = 30.0
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  module Queue
         | 
| 15 | 
            +
                    BATCH_SIZE = 100
         | 
| 16 | 
            +
                    MAX_SIZE = 10000
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Segment
         | 
| 4 | 
            +
              class Analytics
         | 
| 5 | 
            +
                module Logging
         | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    def logger
         | 
| 8 | 
            +
                      @logger ||= if defined?(Rails)
         | 
| 9 | 
            +
                                    Rails.logger
         | 
| 10 | 
            +
                                  else
         | 
| 11 | 
            +
                                    logger = Logger.new STDOUT
         | 
| 12 | 
            +
                                    logger.progname = 'Segment::Analytics'
         | 
| 13 | 
            +
                                    logger
         | 
| 14 | 
            +
                                  end
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def logger= logger
         | 
| 18 | 
            +
                      @logger = logger
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def self.included base
         | 
| 23 | 
            +
                    class << base
         | 
| 24 | 
            +
                      def logger
         | 
| 25 | 
            +
                        Logging.logger
         | 
| 26 | 
            +
                      end
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def logger
         | 
| 31 | 
            +
                    Logging.logger
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            require 'segment/analytics/defaults'
         | 
| 2 | 
            +
            require 'segment/analytics/utils'
         | 
| 3 | 
            +
            require 'segment/analytics/response'
         | 
| 4 | 
            +
            require 'segment/analytics/logging'
         | 
| 5 | 
            +
            require 'net/http'
         | 
| 6 | 
            +
            require 'net/https'
         | 
| 7 | 
            +
            require 'json'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Segment
         | 
| 10 | 
            +
              class Analytics
         | 
| 11 | 
            +
                class Request
         | 
| 12 | 
            +
                  include Segment::Analytics::Defaults::Request
         | 
| 13 | 
            +
                  include Segment::Analytics::Utils
         | 
| 14 | 
            +
                  include Segment::Analytics::Logging
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # public: Creates a new request object to send analytics batch
         | 
| 17 | 
            +
                  #
         | 
| 18 | 
            +
                  def initialize(options = {})
         | 
| 19 | 
            +
                    options[:host] ||= HOST
         | 
| 20 | 
            +
                    options[:port] ||= PORT
         | 
| 21 | 
            +
                    options[:ssl] ||= SSL
         | 
| 22 | 
            +
                    options[:headers] ||= HEADERS
         | 
| 23 | 
            +
                    @path = options[:path] || PATH
         | 
| 24 | 
            +
                    @retries = options[:retries] || RETRIES
         | 
| 25 | 
            +
                    @backoff = options[:backoff] || BACKOFF
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    http = Net::HTTP.new(options[:host], options[:port])
         | 
| 28 | 
            +
                    http.use_ssl = options[:ssl]
         | 
| 29 | 
            +
                    http.read_timeout = 8
         | 
| 30 | 
            +
                    http.open_timeout = 4
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    @http = http
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # public: Posts the app_id and batch of messages to the API.
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # returns - Response of the status and error if it exists
         | 
| 38 | 
            +
                  def post(app_id, batch)
         | 
| 39 | 
            +
                    status, error = nil, nil
         | 
| 40 | 
            +
                    remaining_retries = @retries
         | 
| 41 | 
            +
                    backoff = @backoff
         | 
| 42 | 
            +
                    headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' }
         | 
| 43 | 
            +
                    begin
         | 
| 44 | 
            +
                      payload = JSON.generate :sentAt => datetime_in_iso8601(Time.new), :batch => batch
         | 
| 45 | 
            +
                      request = Net::HTTP::Post.new(@path, headers)
         | 
| 46 | 
            +
                      request.basic_auth app_id, nil
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      if self.class.stub
         | 
| 49 | 
            +
                        status = 200
         | 
| 50 | 
            +
                        error = nil
         | 
| 51 | 
            +
                        logger.debug "stubbed request to #{@path}: app id = #{app_id}, payload = #{payload}"
         | 
| 52 | 
            +
                      else
         | 
| 53 | 
            +
                        res = @http.request(request, payload)
         | 
| 54 | 
            +
                        status = res.code.to_i
         | 
| 55 | 
            +
                        body = JSON.parse(res.body)
         | 
| 56 | 
            +
                        error = body["error"]
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    rescue Exception => e
         | 
| 59 | 
            +
                      unless (remaining_retries -=1).zero?
         | 
| 60 | 
            +
                        sleep(backoff)
         | 
| 61 | 
            +
                        retry
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      logger.error e.message
         | 
| 65 | 
            +
                      e.backtrace.each { |line| logger.error line }
         | 
| 66 | 
            +
                      status = -1
         | 
| 67 | 
            +
                      error = "Connection error: #{e}"
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    Response.new status, error
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  class << self
         | 
| 74 | 
            +
                    attr_accessor :stub
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    def stub
         | 
| 77 | 
            +
                      @stub || ENV['STUB']
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module Segment
         | 
| 2 | 
            +
              class Analytics
         | 
| 3 | 
            +
                class Response
         | 
| 4 | 
            +
                  attr_reader :status, :error
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  # public: Simple class to wrap responses from the API
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  def initialize(status = 200, error = nil)
         | 
| 10 | 
            +
                    @status = status
         | 
| 11 | 
            +
                    @error  = error
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            require 'securerandom'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Segment
         | 
| 4 | 
            +
              class Analytics
         | 
| 5 | 
            +
                module Utils
         | 
| 6 | 
            +
                  extend self
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  # public: Return a new hash with keys converted from strings to symbols
         | 
| 9 | 
            +
                  #
         | 
| 10 | 
            +
                  def symbolize_keys(hash)
         | 
| 11 | 
            +
                    hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # public: Convert hash keys from strings to symbols in place
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  def symbolize_keys!(hash)
         | 
| 17 | 
            +
                    hash.replace symbolize_keys hash
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  # public: Return a new hash with keys as strings
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  def stringify_keys(hash)
         | 
| 23 | 
            +
                    hash.inject({}) { |memo, (k,v)| memo[k.to_s] = v; memo }
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # public: Returns a new hash with all the date values in the into iso8601
         | 
| 27 | 
            +
                  #         strings
         | 
| 28 | 
            +
                  #
         | 
| 29 | 
            +
                  def isoify_dates(hash)
         | 
| 30 | 
            +
                    hash.inject({}) { |memo, (k, v)|
         | 
| 31 | 
            +
                      memo[k] = datetime_in_iso8601(v)
         | 
| 32 | 
            +
                      memo
         | 
| 33 | 
            +
                    }
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  # public: Converts all the date values in the into iso8601 strings in place
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  def isoify_dates!(hash)
         | 
| 39 | 
            +
                    hash.replace isoify_dates hash
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  # public: Returns a uid string
         | 
| 43 | 
            +
                  #
         | 
| 44 | 
            +
                  def uid
         | 
| 45 | 
            +
                    arr = SecureRandom.random_bytes(16).unpack("NnnnnN")
         | 
| 46 | 
            +
                    arr[2] = (arr[2] & 0x0fff) | 0x4000
         | 
| 47 | 
            +
                    arr[3] = (arr[3] & 0x3fff) | 0x8000
         | 
| 48 | 
            +
                    "%08x-%04x-%04x-%04x-%04x%08x" % arr
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def datetime_in_iso8601 datetime
         | 
| 52 | 
            +
                    case datetime
         | 
| 53 | 
            +
                    when Time
         | 
| 54 | 
            +
                        time_in_iso8601 datetime
         | 
| 55 | 
            +
                    when DateTime
         | 
| 56 | 
            +
                        time_in_iso8601 datetime.to_time
         | 
| 57 | 
            +
                    when Date
         | 
| 58 | 
            +
                      date_in_iso8601 datetime
         | 
| 59 | 
            +
                    else
         | 
| 60 | 
            +
                      datetime
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def time_in_iso8601 time, fraction_digits = 3
         | 
| 65 | 
            +
                    fraction = if fraction_digits > 0
         | 
| 66 | 
            +
                                 (".%06i" % time.usec)[0, fraction_digits + 1]
         | 
| 67 | 
            +
                               end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(time, true, 'Z')}"
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def date_in_iso8601 date
         | 
| 73 | 
            +
                    date.strftime("%F")
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def formatted_offset time, colon = true, alternate_utc_string = nil
         | 
| 77 | 
            +
                    time.utc? && alternate_utc_string || seconds_to_utc_offset(time.utc_offset, colon)
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def seconds_to_utc_offset(seconds, colon = true)
         | 
| 81 | 
            +
                    (colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds < 0 ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
         | 
| 85 | 
            +
                  UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            require 'segment/analytics/defaults'
         | 
| 2 | 
            +
            require 'segment/analytics/utils'
         | 
| 3 | 
            +
            require 'segment/analytics/defaults'
         | 
| 4 | 
            +
            require 'segment/analytics/request'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Segment
         | 
| 7 | 
            +
              class Analytics
         | 
| 8 | 
            +
                class Worker
         | 
| 9 | 
            +
                  include Segment::Analytics::Utils
         | 
| 10 | 
            +
                  include Segment::Analytics::Defaults
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # public: Creates a new worker
         | 
| 13 | 
            +
                  #
         | 
| 14 | 
            +
                  # The worker continuously takes messages off the queue
         | 
| 15 | 
            +
                  # and makes requests to the segment.io api
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # queue   - Queue synchronized between client and worker
         | 
| 18 | 
            +
                  # app_id  - String of the project's app_id
         | 
| 19 | 
            +
                  # options - Hash of worker options
         | 
| 20 | 
            +
                  #           batch_size - Fixnum of how many items to send in a batch
         | 
| 21 | 
            +
                  #           on_error   - Proc of what to do on an error
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  def initialize(queue, app_id, options = {})
         | 
| 24 | 
            +
                    symbolize_keys! options
         | 
| 25 | 
            +
                    @queue = queue
         | 
| 26 | 
            +
                    @app_id = app_id
         | 
| 27 | 
            +
                    @batch_size = options[:batch_size] || Queue::BATCH_SIZE
         | 
| 28 | 
            +
                    @on_error = options[:on_error] || Proc.new { |status, error| }
         | 
| 29 | 
            +
                    @batch = []
         | 
| 30 | 
            +
                    @lock = Mutex.new
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # public: Continuously runs the loop to check for new events
         | 
| 34 | 
            +
                  #
         | 
| 35 | 
            +
                  def run
         | 
| 36 | 
            +
                    until Thread.current[:should_exit]
         | 
| 37 | 
            +
                      return if @queue.empty?
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      @lock.synchronize do
         | 
| 40 | 
            +
                        until @batch.length >= @batch_size || @queue.empty?
         | 
| 41 | 
            +
                          @batch << @queue.pop
         | 
| 42 | 
            +
                        end
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      res = Request.new.post @app_id, @batch
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      @lock.synchronize { @batch.clear }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      @on_error.call res.status, res.error unless res.status == 200
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  # public: Check whether we have outstanding requests.
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  def is_requesting?
         | 
| 56 | 
            +
                    @lock.synchronize { !@batch.empty? }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,302 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Segment
         | 
| 4 | 
            +
              class Analytics
         | 
| 5 | 
            +
                describe Client do
         | 
| 6 | 
            +
                  let(:client) { Client.new :write_key => WRITE_KEY }
         | 
| 7 | 
            +
                  let(:queue) { client.instance_variable_get :@queue }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  describe '#initialize' do
         | 
| 10 | 
            +
                    it 'errors if no write_key is supplied' do
         | 
| 11 | 
            +
                      expect { Client.new }.to raise_error(ArgumentError)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    it 'does not error if a write_key is supplied' do
         | 
| 15 | 
            +
                      expect do
         | 
| 16 | 
            +
                        Client.new :write_key => WRITE_KEY
         | 
| 17 | 
            +
                      end.to_not raise_error
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    it 'does not error if a write_key is supplied as a string' do
         | 
| 21 | 
            +
                      expect do
         | 
| 22 | 
            +
                        Client.new 'write_key' => WRITE_KEY
         | 
| 23 | 
            +
                      end.to_not raise_error
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  describe '#track' do
         | 
| 28 | 
            +
                    it 'errors without an event' do
         | 
| 29 | 
            +
                      expect { client.track(:user_id => 'user') }.to raise_error(ArgumentError)
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    it 'errors without a user_id' do
         | 
| 33 | 
            +
                      expect { client.track(:event => 'Event') }.to raise_error(ArgumentError)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    it 'errors if properties is not a hash' do
         | 
| 37 | 
            +
                      expect {
         | 
| 38 | 
            +
                        client.track({
         | 
| 39 | 
            +
                          :user_id => 'user',
         | 
| 40 | 
            +
                          :event => 'Event',
         | 
| 41 | 
            +
                          :properties => [1,2,3]
         | 
| 42 | 
            +
                        })
         | 
| 43 | 
            +
                      }.to raise_error(ArgumentError)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    it 'uses the timestamp given' do
         | 
| 47 | 
            +
                      time = Time.parse("1990-07-16 13:30:00.123 UTC")
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      client.track({
         | 
| 50 | 
            +
                        :event => 'testing the timestamp',
         | 
| 51 | 
            +
                        :user_id => 'joe',
         | 
| 52 | 
            +
                        :timestamp => time
         | 
| 53 | 
            +
                      })
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      msg = queue.pop
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      expect(Time.parse(msg[:timestamp])).to eq(time)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    it 'does not error with the required options' do
         | 
| 61 | 
            +
                      expect do
         | 
| 62 | 
            +
                        client.track Queued::TRACK
         | 
| 63 | 
            +
                        queue.pop
         | 
| 64 | 
            +
                      end.to_not raise_error
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    it 'does not error when given string keys' do
         | 
| 68 | 
            +
                      expect do
         | 
| 69 | 
            +
                        client.track Utils.stringify_keys(Queued::TRACK)
         | 
| 70 | 
            +
                        queue.pop
         | 
| 71 | 
            +
                      end.to_not raise_error
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    it 'converts time and date traits into iso8601 format' do
         | 
| 75 | 
            +
                      client.track({
         | 
| 76 | 
            +
                        :user_id => 'user',
         | 
| 77 | 
            +
                        :event => 'Event',
         | 
| 78 | 
            +
                        :properties => {
         | 
| 79 | 
            +
                          :time => Time.utc(2013),
         | 
| 80 | 
            +
                          :time_with_zone =>  Time.zone.parse('2013-01-01'),
         | 
| 81 | 
            +
                          :date_time => DateTime.new(2013,1,1),
         | 
| 82 | 
            +
                          :date => Date.new(2013,1,1),
         | 
| 83 | 
            +
                          :nottime => 'x'
         | 
| 84 | 
            +
                        }
         | 
| 85 | 
            +
                      })
         | 
| 86 | 
            +
                      message = queue.pop
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                      expect(message[:properties][:time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 89 | 
            +
                      expect(message[:properties][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 90 | 
            +
                      expect(message[:properties][:date_time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 91 | 
            +
                      expect(message[:properties][:date]).to eq('2013-01-01')
         | 
| 92 | 
            +
                      expect(message[:properties][:nottime]).to eq('x')
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
             | 
| 97 | 
            +
                  describe '#identify' do
         | 
| 98 | 
            +
                    it 'errors without any user id' do
         | 
| 99 | 
            +
                      expect { client.identify({}) }.to raise_error(ArgumentError)
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    it 'does not error with the required options' do
         | 
| 103 | 
            +
                      expect do
         | 
| 104 | 
            +
                        client.identify Queued::IDENTIFY
         | 
| 105 | 
            +
                        queue.pop
         | 
| 106 | 
            +
                      end.to_not raise_error
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    it 'does not error with the required options as strings' do
         | 
| 110 | 
            +
                      expect do
         | 
| 111 | 
            +
                        client.identify Utils.stringify_keys(Queued::IDENTIFY)
         | 
| 112 | 
            +
                        queue.pop
         | 
| 113 | 
            +
                      end.to_not raise_error
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    it 'converts time and date traits into iso8601 format' do
         | 
| 117 | 
            +
                      client.identify({
         | 
| 118 | 
            +
                        :user_id => 'user',
         | 
| 119 | 
            +
                        :traits => {
         | 
| 120 | 
            +
                          :time => Time.utc(2013),
         | 
| 121 | 
            +
                          :time_with_zone =>  Time.zone.parse('2013-01-01'),
         | 
| 122 | 
            +
                          :date_time => DateTime.new(2013,1,1),
         | 
| 123 | 
            +
                          :date => Date.new(2013,1,1),
         | 
| 124 | 
            +
                          :nottime => 'x'
         | 
| 125 | 
            +
                        }
         | 
| 126 | 
            +
                      })
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                      message = queue.pop
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                      expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 131 | 
            +
                      expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 132 | 
            +
                      expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 133 | 
            +
                      expect(message[:traits][:date]).to eq('2013-01-01')
         | 
| 134 | 
            +
                      expect(message[:traits][:nottime]).to eq('x')
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  describe '#alias' do
         | 
| 139 | 
            +
                    it 'errors without from' do
         | 
| 140 | 
            +
                      expect { client.alias :user_id => 1234 }.to raise_error(ArgumentError)
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    it 'errors without to' do
         | 
| 144 | 
            +
                      expect { client.alias :previous_id => 1234 }.to raise_error(ArgumentError)
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    it 'does not error with the required options' do
         | 
| 148 | 
            +
                      expect { client.alias ALIAS }.to_not raise_error
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    it 'does not error with the required options as strings' do
         | 
| 152 | 
            +
                      expect do
         | 
| 153 | 
            +
                        client.alias Utils.stringify_keys(ALIAS)
         | 
| 154 | 
            +
                      end.to_not raise_error
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  describe '#group' do
         | 
| 159 | 
            +
                    after do
         | 
| 160 | 
            +
                      client.flush
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    it 'errors without group_id' do
         | 
| 164 | 
            +
                      expect { client.group :user_id => 'foo' }.to raise_error(ArgumentError)
         | 
| 165 | 
            +
                    end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    it 'errors without user_id' do
         | 
| 168 | 
            +
                      expect { client.group :group_id => 'foo' }.to raise_error(ArgumentError)
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    it 'does not error with the required options' do
         | 
| 172 | 
            +
                      client.group Queued::GROUP
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                    it 'does not error with the required options as strings' do
         | 
| 176 | 
            +
                      client.group Utils.stringify_keys(Queued::GROUP)
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    it 'converts time and date traits into iso8601 format' do
         | 
| 180 | 
            +
                      client.identify({
         | 
| 181 | 
            +
                        :user_id => 'user',
         | 
| 182 | 
            +
                        :group_id => 'group',
         | 
| 183 | 
            +
                        :traits => {
         | 
| 184 | 
            +
                          :time => Time.utc(2013),
         | 
| 185 | 
            +
                          :time_with_zone =>  Time.zone.parse('2013-01-01'),
         | 
| 186 | 
            +
                          :date_time => DateTime.new(2013,1,1),
         | 
| 187 | 
            +
                          :date => Date.new(2013,1,1),
         | 
| 188 | 
            +
                          :nottime => 'x'
         | 
| 189 | 
            +
                        }
         | 
| 190 | 
            +
                      })
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                      message = queue.pop
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                      expect(message[:traits][:time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 195 | 
            +
                      expect(message[:traits][:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 196 | 
            +
                      expect(message[:traits][:date_time]).to eq('2013-01-01T00:00:00.000Z')
         | 
| 197 | 
            +
                      expect(message[:traits][:date]).to eq('2013-01-01')
         | 
| 198 | 
            +
                      expect(message[:traits][:nottime]).to eq('x')
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  describe '#page' do
         | 
| 203 | 
            +
                    it 'errors without user_id' do
         | 
| 204 | 
            +
                      expect { client.page :name => 'foo' }.to raise_error(ArgumentError)
         | 
| 205 | 
            +
                    end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                    it 'does not error with the required options' do
         | 
| 208 | 
            +
                      expect { client.page Queued::PAGE }.to_not raise_error
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                    it 'does not error with the required options as strings' do
         | 
| 212 | 
            +
                      expect do
         | 
| 213 | 
            +
                        client.page Utils.stringify_keys(Queued::PAGE)
         | 
| 214 | 
            +
                      end.to_not raise_error
         | 
| 215 | 
            +
                    end
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  describe '#screen' do
         | 
| 219 | 
            +
                    it 'errors without user_id' do
         | 
| 220 | 
            +
                      expect { client.screen :name => 'foo' }.to raise_error(ArgumentError)
         | 
| 221 | 
            +
                    end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                    it 'does not error with the required options' do
         | 
| 224 | 
            +
                      expect { client.screen Queued::SCREEN }.to_not raise_error
         | 
| 225 | 
            +
                    end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                    it 'does not error with the required options as strings' do
         | 
| 228 | 
            +
                      expect do
         | 
| 229 | 
            +
                        client.screen Utils.stringify_keys(Queued::SCREEN)
         | 
| 230 | 
            +
                      end.to_not raise_error
         | 
| 231 | 
            +
                    end
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  describe '#flush' do
         | 
| 235 | 
            +
                    it 'waits for the queue to finish on a flush' do
         | 
| 236 | 
            +
                      client.identify Queued::IDENTIFY
         | 
| 237 | 
            +
                      client.track Queued::TRACK
         | 
| 238 | 
            +
                      client.flush
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                      expect(client.queued_messages).to eq(0)
         | 
| 241 | 
            +
                    end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                    it 'completes when the process forks' do
         | 
| 244 | 
            +
                      client.identify Queued::IDENTIFY
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                      Process.fork do
         | 
| 247 | 
            +
                        client.track Queued::TRACK
         | 
| 248 | 
            +
                        client.flush
         | 
| 249 | 
            +
                        expect(client.queued_messages).to eq(0)
         | 
| 250 | 
            +
                      end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                      Process.wait
         | 
| 253 | 
            +
                    end unless defined? JRUBY_VERSION
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                  context 'common' do
         | 
| 257 | 
            +
                    check_property = proc { |msg, k, v| msg[k] && msg[k] == v }
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                    let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco" } }
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                    it 'does not convert ids given as fixnums to strings' do
         | 
| 262 | 
            +
                      [:track, :screen, :page, :identify].each do |s|
         | 
| 263 | 
            +
                        client.send(s, data)
         | 
| 264 | 
            +
                        message = queue.pop(true)
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                        expect(check_property.call(message, :userId, 1)).to eq(true)
         | 
| 267 | 
            +
                        expect(check_property.call(message, :anonymousId, 4)).to eq(true)
         | 
| 268 | 
            +
                      end
         | 
| 269 | 
            +
                    end
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                    context 'group' do
         | 
| 272 | 
            +
                      it 'does not convert ids given as fixnums to strings' do
         | 
| 273 | 
            +
                        client.group(data)
         | 
| 274 | 
            +
                        message = queue.pop(true)
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                        expect(check_property.call(message, :userId, 1)).to eq(true)
         | 
| 277 | 
            +
                        expect(check_property.call(message, :groupId, 2)).to eq(true)
         | 
| 278 | 
            +
                      end
         | 
| 279 | 
            +
                    end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                    context 'alias' do
         | 
| 282 | 
            +
                      it 'does not convert ids given as fixnums to strings' do
         | 
| 283 | 
            +
                        client.alias(data)
         | 
| 284 | 
            +
                        message = queue.pop(true)
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                        expect(check_property.call(message, :userId, 1)).to eq(true)
         | 
| 287 | 
            +
                        expect(check_property.call(message, :previousId, 3)).to eq(true)
         | 
| 288 | 
            +
                      end
         | 
| 289 | 
            +
                    end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    it 'sends integrations' do
         | 
| 292 | 
            +
                      [:track, :screen, :page, :group, :identify, :alias].each do |s|
         | 
| 293 | 
            +
                        client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => "coco barked", :name => "coco"
         | 
| 294 | 
            +
                        message = queue.pop(true)
         | 
| 295 | 
            +
                        expect(message[:integrations][:All]).to eq(true)
         | 
| 296 | 
            +
                        expect(message[:integrations][:Salesforce]).to eq(false)
         | 
| 297 | 
            +
                      end
         | 
| 298 | 
            +
                    end
         | 
| 299 | 
            +
                  end
         | 
| 300 | 
            +
                end
         | 
| 301 | 
            +
              end
         | 
| 302 | 
            +
            end
         |