koko-ai-ruby 0.0.2
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/Gemfile.lock +65 -0
- data/History.md +10 -0
- data/Makefile +5 -0
- data/README.md +90 -0
- data/RELEASING.md +10 -0
- data/Rakefile +7 -0
- data/koko-ai.gemspec +24 -0
- data/lib/koko/tracker/client.rb +102 -0
- data/lib/koko/tracker/defaults.rb +17 -0
- data/lib/koko/tracker/logging.rb +35 -0
- data/lib/koko/tracker/request.rb +79 -0
- data/lib/koko/tracker/response.rb +20 -0
- data/lib/koko/tracker/utils.rb +88 -0
- data/lib/koko/tracker/version.rb +5 -0
- data/lib/koko/tracker.rb +30 -0
- data/lib/koko-ai-ruby.rb +1 -0
- data/lib/koko.rb +1 -0
- data/spec/koko/tracking/client_spec.rb +126 -0
- data/spec/koko/tracking/request_spec.rb +152 -0
- data/spec/koko/tracking/response_spec.rb +53 -0
- data/spec/spec_helper.rb +85 -0
- metadata +183 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 43b75c6a75c5221b08a42c04b0e034b4911c0eaf
         | 
| 4 | 
            +
              data.tar.gz: cabf0e08433f832707323633f91218952817c034
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: b004b011c07415e812bffc8a379fc1bbeb790ad77f93137a4774dfc1c46fe275347dcc5c874000478758d0163a104571aeaeab172f06c4c640608a66ac456267
         | 
| 7 | 
            +
              data.tar.gz: 74617a6b797026b45ae707dabdab5da772752d6c9ee424c59df2e45b74478d99dc10de0bbca062e426f26dfe492d266a230653360c2295e6237bc0d646c0c398
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            PATH
         | 
| 2 | 
            +
              remote: .
         | 
| 3 | 
            +
              specs:
         | 
| 4 | 
            +
                koko-ai-ruby (0.0.1)
         | 
| 5 | 
            +
                  commander (~> 4.4)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            GEM
         | 
| 8 | 
            +
              remote: http://rubygems.org/
         | 
| 9 | 
            +
              specs:
         | 
| 10 | 
            +
                activesupport (3.2.22.5)
         | 
| 11 | 
            +
                  i18n (~> 0.6, >= 0.6.4)
         | 
| 12 | 
            +
                  multi_json (~> 1.0)
         | 
| 13 | 
            +
                addressable (2.5.1)
         | 
| 14 | 
            +
                  public_suffix (~> 2.0, >= 2.0.2)
         | 
| 15 | 
            +
                coderay (1.1.1)
         | 
| 16 | 
            +
                commander (4.4.3)
         | 
| 17 | 
            +
                  highline (~> 1.7.2)
         | 
| 18 | 
            +
                crack (0.4.3)
         | 
| 19 | 
            +
                  safe_yaml (~> 1.0.0)
         | 
| 20 | 
            +
                diff-lcs (1.3)
         | 
| 21 | 
            +
                hashdiff (0.3.4)
         | 
| 22 | 
            +
                highline (1.7.8)
         | 
| 23 | 
            +
                i18n (0.8.4)
         | 
| 24 | 
            +
                method_source (0.8.2)
         | 
| 25 | 
            +
                multi_json (1.12.1)
         | 
| 26 | 
            +
                pry (0.10.4)
         | 
| 27 | 
            +
                  coderay (~> 1.1.0)
         | 
| 28 | 
            +
                  method_source (~> 0.8.1)
         | 
| 29 | 
            +
                  slop (~> 3.4)
         | 
| 30 | 
            +
                public_suffix (2.0.5)
         | 
| 31 | 
            +
                rake (10.5.0)
         | 
| 32 | 
            +
                rspec (2.99.0)
         | 
| 33 | 
            +
                  rspec-core (~> 2.99.0)
         | 
| 34 | 
            +
                  rspec-expectations (~> 2.99.0)
         | 
| 35 | 
            +
                  rspec-mocks (~> 2.99.0)
         | 
| 36 | 
            +
                rspec-core (2.99.2)
         | 
| 37 | 
            +
                rspec-expectations (2.99.2)
         | 
| 38 | 
            +
                  diff-lcs (>= 1.1.3, < 2.0)
         | 
| 39 | 
            +
                rspec-mocks (2.99.4)
         | 
| 40 | 
            +
                safe_yaml (1.0.4)
         | 
| 41 | 
            +
                slop (3.6.0)
         | 
| 42 | 
            +
                thread_safe (0.3.6)
         | 
| 43 | 
            +
                timecop (0.8.1)
         | 
| 44 | 
            +
                tzinfo (1.2.1)
         | 
| 45 | 
            +
                  thread_safe (~> 0.1)
         | 
| 46 | 
            +
                webmock (3.0.1)
         | 
| 47 | 
            +
                  addressable (>= 2.3.6)
         | 
| 48 | 
            +
                  crack (>= 0.3.2)
         | 
| 49 | 
            +
                  hashdiff
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            PLATFORMS
         | 
| 52 | 
            +
              ruby
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            DEPENDENCIES
         | 
| 55 | 
            +
              activesupport (>= 3.0.0, < 4.0.0)
         | 
| 56 | 
            +
              koko-ai-ruby!
         | 
| 57 | 
            +
              pry (~> 0.10.4)
         | 
| 58 | 
            +
              rake (~> 10.3)
         | 
| 59 | 
            +
              rspec (~> 2.0)
         | 
| 60 | 
            +
              timecop (= 0.8.1)
         | 
| 61 | 
            +
              tzinfo (= 1.2.1)
         | 
| 62 | 
            +
              webmock (~> 3.0.1)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            BUNDLED WITH
         | 
| 65 | 
            +
               1.14.3
         | 
    
        data/History.md
    ADDED
    
    
    
        data/Makefile
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            koko-ai-ruby
         | 
| 2 | 
            +
            ==============
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            koko-ai-ruby is a ruby client for https://docs.koko.ai
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ## Install
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            Into Gemfile from rubygems.org:
         | 
| 9 | 
            +
            ```ruby
         | 
| 10 | 
            +
            gem 'koko-ai'
         | 
| 11 | 
            +
            ```
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            Into environment gems from rubygems.org:
         | 
| 14 | 
            +
            ```ruby
         | 
| 15 | 
            +
            gem install 'koko-ai'
         | 
| 16 | 
            +
            ```
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ## Usage
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            Create an instance of the client:
         | 
| 21 | 
            +
            ```ruby
         | 
| 22 | 
            +
            koko = Koko::Tracker.new(auth: 'YOUR_AUTH_KEY')
         | 
| 23 | 
            +
            ```
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            Track content, see more [here](https://docs.koko.ai/#track-endpoints).
         | 
| 26 | 
            +
            ```ruby
         | 
| 27 | 
            +
            koko.track_content(id: "123",
         | 
| 28 | 
            +
                               created_at: Time.now,
         | 
| 29 | 
            +
                               user_id: "123",
         | 
| 30 | 
            +
                               type: "post",
         | 
| 31 | 
            +
                               context_id: "123",
         | 
| 32 | 
            +
                               content_type: "text",
         | 
| 33 | 
            +
                               content: { text: "Some content" })
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            koko.track_flag(id: "123",
         | 
| 36 | 
            +
                            flagger_id: "123",
         | 
| 37 | 
            +
                            type: "spam",
         | 
| 38 | 
            +
                            created_at: Time.now,
         | 
| 39 | 
            +
                            content: {"id":"123"})
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            koko.track_moderation(id: "123",
         | 
| 42 | 
            +
                                  type: "user_warned",
         | 
| 43 | 
            +
                                  created_at: Time.now,
         | 
| 44 | 
            +
                                  content: { id:"123" })
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ```
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            To get behavorial classifications when tracking content, pass a block with a
         | 
| 49 | 
            +
            single parameter which will be populated with the results. This block will be
         | 
| 50 | 
            +
            called synchronously.
         | 
| 51 | 
            +
            ```ruby
         | 
| 52 | 
            +
            koko.track_content(id: "123",
         | 
| 53 | 
            +
                               created_at: "2016-08-29T09:12:33.001Z",
         | 
| 54 | 
            +
                               user_id: "123",
         | 
| 55 | 
            +
                               type: "post",
         | 
| 56 | 
            +
                               context_id: "123",
         | 
| 57 | 
            +
                               content_type: "text",
         | 
| 58 | 
            +
                               content: { text: "Some content" }) do |classification|
         | 
| 59 | 
            +
              crisis_confidence = classification.find { |c| c['label'] == 'crisis' }['confidence']
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
            ```
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ## Testing
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            You can use the `stub` option to Koko::Tracker.new to cause all requests to be stubbed, making it easier to test with this library.
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            ## License
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            ```
         | 
| 70 | 
            +
            WWWWWW||WWWWWW
         | 
| 71 | 
            +
             W W W||W W W
         | 
| 72 | 
            +
                  ||
         | 
| 73 | 
            +
                ( OO )__________
         | 
| 74 | 
            +
                 /  |           \
         | 
| 75 | 
            +
                /o o|    MIT     \
         | 
| 76 | 
            +
                \___/||_||__||_|| *
         | 
| 77 | 
            +
                     || ||  || ||
         | 
| 78 | 
            +
                    _||_|| _||_||
         | 
| 79 | 
            +
                   (__|__|(__|__|
         | 
| 80 | 
            +
            ```
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            (The MIT License)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            Copyright (c) 2017 Koko Inc. <us@itskoko.com>
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/RELEASING.md
    ADDED
    
    | @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            Releasing
         | 
| 2 | 
            +
            =========
         | 
| 3 | 
            +
             | 
| 4 | 
            +
             1. Verify everything works with `make test build`.
         | 
| 5 | 
            +
             2. Bump version in [`version.rb`](https://github.com/itskoko/koko-ai-ruby/blob/master/lib/koko/version.rb).
         | 
| 6 | 
            +
             3. Update [`History.md`](https://github.com/itskoko/koko-ai-ruby/blob/master/History.md).
         | 
| 7 | 
            +
             4. Commit and tag `git commit -am "Release {version}" && git tag -a {version} -m "Version {version}"`.
         | 
| 8 | 
            +
             5. Build the gem with the tagged version `make build`.
         | 
| 9 | 
            +
             6. Upload to RubyGems with `gem push koko-ai-{version}.gem`.
         | 
| 10 | 
            +
             7. Upload to Github with `git push -u origin master && git push --tags`.
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/koko-ai.gemspec
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require File.expand_path('../lib/koko/tracker/version', __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Gem::Specification.new do |spec|
         | 
| 4 | 
            +
              spec.name = 'koko-ai-ruby'
         | 
| 5 | 
            +
              spec.version = Koko::Tracker::VERSION
         | 
| 6 | 
            +
              spec.files = Dir.glob('**/*')
         | 
| 7 | 
            +
              spec.require_paths = ['lib']
         | 
| 8 | 
            +
              spec.summary = 'Koko AI Client'
         | 
| 9 | 
            +
              spec.description = 'The Koko AI ruby client library'
         | 
| 10 | 
            +
              spec.authors = ['Koko']
         | 
| 11 | 
            +
              spec.email = 'us@itskoko.com'
         | 
| 12 | 
            +
              spec.homepage = 'https://github.com/itskoko/koko-ai-ruby'
         | 
| 13 | 
            +
              spec.license = 'MIT'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              spec.add_dependency 'commander', '~> 4.4'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              spec.add_development_dependency 'rake', '~> 10.3'
         | 
| 18 | 
            +
              spec.add_development_dependency 'rspec', '~> 2.0'
         | 
| 19 | 
            +
              spec.add_development_dependency 'tzinfo', '1.2.1'
         | 
| 20 | 
            +
              spec.add_development_dependency 'timecop', '0.8.1'
         | 
| 21 | 
            +
              spec.add_development_dependency 'pry', '~> 0.10.4'
         | 
| 22 | 
            +
              spec.add_development_dependency 'webmock', '~> 3.0.1'
         | 
| 23 | 
            +
              spec.add_development_dependency 'activesupport', '>= 3.0.0', '<4.0.0'
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            require 'time'
         | 
| 2 | 
            +
            require 'koko/tracker/utils'
         | 
| 3 | 
            +
            require 'koko/tracker/defaults'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Koko
         | 
| 6 | 
            +
              class Tracker
         | 
| 7 | 
            +
                class Client
         | 
| 8 | 
            +
                  include Koko::Tracker::Utils
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # public: Creates a new client
         | 
| 11 | 
            +
                  #
         | 
| 12 | 
            +
                  # attrs - Hash
         | 
| 13 | 
            +
                  #           :auth           - String of your authorization key
         | 
| 14 | 
            +
                  #           :max_queue_size - Fixnum of the max calls to remain queued (optional)
         | 
| 15 | 
            +
                  #           :on_error       - Proc which handles error calls from the API
         | 
| 16 | 
            +
                  def initialize attrs = {}
         | 
| 17 | 
            +
                    symbolize_keys! attrs
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    @auth = attrs[:auth]
         | 
| 20 | 
            +
                    @options = attrs
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    check_auth!
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  # public: Track content
         | 
| 26 | 
            +
                  #
         | 
| 27 | 
            +
                  # attrs - Hash (see https://docs.koko.ai/#track-endpoints)
         | 
| 28 | 
            +
                  def track_content attrs, &block
         | 
| 29 | 
            +
                    symbolize_keys! attrs
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    timestamp = attrs[:created_at] || Time.new
         | 
| 32 | 
            +
                    check_timestamp! timestamp
         | 
| 33 | 
            +
                    attrs[:created_at] = timestamp.to_f
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    response = handle_response(Request.new(path: '/track/content').post(@auth, attrs))
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    if block
         | 
| 38 | 
            +
                      block.call(response.body)
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    true
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  # public: Track flag
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  # attrs - Hash (see https://docs.koko.ai/#track-endpoints)
         | 
| 47 | 
            +
                  def track_flag attrs
         | 
| 48 | 
            +
                    symbolize_keys! attrs
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    timestamp = attrs[:created_at] || Time.new
         | 
| 51 | 
            +
                    check_timestamp! timestamp
         | 
| 52 | 
            +
                    attrs[:created_at] = timestamp.to_f
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    handle_response(Request.new(path: '/track/flag').post(@auth, attrs))
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    true
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  # public: Track moderation
         | 
| 60 | 
            +
                  #
         | 
| 61 | 
            +
                  # attrs - Hash (see https://docs.koko.ai/#track-endpoints)
         | 
| 62 | 
            +
                  def track_moderation attrs
         | 
| 63 | 
            +
                    symbolize_keys! attrs
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    timestamp = attrs[:created_at] || Time.new
         | 
| 66 | 
            +
                    check_timestamp! timestamp
         | 
| 67 | 
            +
                    attrs[:created_at] = timestamp.to_f
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    handle_response(Request.new(path: '/track/moderation').post(@auth, attrs))
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    true
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  private
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  # private: Checks that the auth is properly initialized
         | 
| 77 | 
            +
                  def check_auth!
         | 
| 78 | 
            +
                    fail ArgumentError, 'Auth must be initialized' if @auth.nil?
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # private: Ensure response is valid
         | 
| 82 | 
            +
                  def handle_response(response)
         | 
| 83 | 
            +
                    unless response.valid?
         | 
| 84 | 
            +
                      if response.status >= 400 && response.status < 500
         | 
| 85 | 
            +
                        raise ArgumentError.new("Invalid request: #{response.body}")
         | 
| 86 | 
            +
                      elsif response.status >= 500 && response.status < 600
         | 
| 87 | 
            +
                        raise RuntimeError.new(response.body)
         | 
| 88 | 
            +
                      else
         | 
| 89 | 
            +
                        raise RuntimeError.new("Unaccepted http code: #{response.status}")
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    response
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  # private: Checks the timstamp option to make sure it is a Time.
         | 
| 97 | 
            +
                  def check_timestamp!(timestamp)
         | 
| 98 | 
            +
                    fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Koko
         | 
| 2 | 
            +
              class Tracker
         | 
| 3 | 
            +
                module Defaults
         | 
| 4 | 
            +
                  module Request
         | 
| 5 | 
            +
                    class << self
         | 
| 6 | 
            +
                      attr_accessor :host, :port, :path, :ssl, :headers
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    self.host = 'api.koko.ai'
         | 
| 10 | 
            +
                    self.port = 443
         | 
| 11 | 
            +
                    self.path = '/'
         | 
| 12 | 
            +
                    self.ssl = true
         | 
| 13 | 
            +
                    self.headers = { :accept => 'application/json' }
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Koko
         | 
| 4 | 
            +
              class Tracker
         | 
| 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 = 'Koko::Tracker'
         | 
| 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,79 @@ | |
| 1 | 
            +
            require 'koko/tracker/defaults'
         | 
| 2 | 
            +
            require 'koko/tracker/utils'
         | 
| 3 | 
            +
            require 'koko/tracker/response'
         | 
| 4 | 
            +
            require 'koko/tracker/logging'
         | 
| 5 | 
            +
            require 'net/http'
         | 
| 6 | 
            +
            require 'net/https'
         | 
| 7 | 
            +
            require 'json'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Koko
         | 
| 10 | 
            +
              class Tracker
         | 
| 11 | 
            +
                class Request
         | 
| 12 | 
            +
                  include Koko::Tracker::Utils
         | 
| 13 | 
            +
                  include Koko::Tracker::Logging
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  attr_reader :http
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # public: Creates a new request object to send analytics batch
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  def initialize(options = {})
         | 
| 20 | 
            +
                    options[:host] ||= Defaults::Request.host
         | 
| 21 | 
            +
                    options[:port] ||= Defaults::Request.port
         | 
| 22 | 
            +
                    options[:ssl] ||= Defaults::Request.ssl
         | 
| 23 | 
            +
                    options[:headers] ||= Defaults::Request.headers
         | 
| 24 | 
            +
                    @path = options[:path] || Defaults::Request.path
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    http = Net::HTTP.new(options[:host], options[:port])
         | 
| 27 | 
            +
                    http.use_ssl = options[:ssl]
         | 
| 28 | 
            +
                    http.read_timeout = 8
         | 
| 29 | 
            +
                    http.open_timeout = 4
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    @http = http
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  # public: Posts the write key and batch of messages to the API.
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  # returns - Response of the status and error if it exists
         | 
| 37 | 
            +
                  def post(auth, body)
         | 
| 38 | 
            +
                    status = nil
         | 
| 39 | 
            +
                    headers = { 'Content-Type' => 'application/json' }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    begin
         | 
| 42 | 
            +
                      payload = JSON.generate body
         | 
| 43 | 
            +
                      request = Net::HTTP::Post.new(@path, headers)
         | 
| 44 | 
            +
                      request['authorization'] = auth
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      response = nil
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      if self.class.stub
         | 
| 49 | 
            +
                        status = 200
         | 
| 50 | 
            +
                        logger.debug "stubbed request to #{@path}: auth = #{auth}, payload = #{payload}"
         | 
| 51 | 
            +
                      else
         | 
| 52 | 
            +
                        res = @http.request(request, payload)
         | 
| 53 | 
            +
                        status = res.code.to_i
         | 
| 54 | 
            +
                        if status < 500
         | 
| 55 | 
            +
                          response = JSON.parse(res.body)
         | 
| 56 | 
            +
                        else
         | 
| 57 | 
            +
                          response = res.body
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    rescue Exception => e
         | 
| 61 | 
            +
                      logger.error e.message
         | 
| 62 | 
            +
                      e.backtrace.each { |line| logger.error line }
         | 
| 63 | 
            +
                      status = -1
         | 
| 64 | 
            +
                      response = "Connection error: #{e}"
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    Response.new status, response
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  class << self
         | 
| 71 | 
            +
                    attr_accessor :stub
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def stub
         | 
| 74 | 
            +
                      @stub || ENV['STUB']
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module Koko
         | 
| 2 | 
            +
              class Tracker
         | 
| 3 | 
            +
                class Response
         | 
| 4 | 
            +
                  attr_reader :status, :body
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  # public: Simple class to wrap responses from the API
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  #
         | 
| 9 | 
            +
                  def initialize(status = 200, body = nil)
         | 
| 10 | 
            +
                    @status = status
         | 
| 11 | 
            +
                    @body  = body
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def valid?
         | 
| 15 | 
            +
                    @status >= 200 && @status < 300
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            require 'securerandom'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Koko
         | 
| 4 | 
            +
              class Tracker
         | 
| 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
         | 
    
        data/lib/koko/tracker.rb
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            require 'koko/tracker/defaults'
         | 
| 2 | 
            +
            require 'koko/tracker/utils'
         | 
| 3 | 
            +
            require 'koko/tracker/version'
         | 
| 4 | 
            +
            require 'koko/tracker/client'
         | 
| 5 | 
            +
            require 'koko/tracker/request'
         | 
| 6 | 
            +
            require 'koko/tracker/response'
         | 
| 7 | 
            +
            require 'koko/tracker/logging'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Koko
         | 
| 10 | 
            +
              class Tracker
         | 
| 11 | 
            +
                def initialize options = {}
         | 
| 12 | 
            +
                  Request.stub = options[:stub] if options.has_key?(:stub)
         | 
| 13 | 
            +
                  @client = Koko::Tracker::Client.new options
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def method_missing message, *args, &block
         | 
| 17 | 
            +
                  if @client.respond_to? message
         | 
| 18 | 
            +
                    @client.send message, *args, &block
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    super
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def respond_to? method_name, include_private = false
         | 
| 25 | 
            +
                  @client.respond_to?(method_name) || super
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                include Logging
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
    
        data/lib/koko-ai-ruby.rb
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'koko'
         | 
    
        data/lib/koko.rb
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'koko/tracker'
         | 
| @@ -0,0 +1,126 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Koko
         | 
| 4 | 
            +
              class Tracker
         | 
| 5 | 
            +
                describe Client do
         | 
| 6 | 
            +
                  let(:auth)        { 'secret' }
         | 
| 7 | 
            +
                  let(:client)      { Client.new :auth => auth }
         | 
| 8 | 
            +
                  let(:queue)       { client.instance_variable_get :@queue }
         | 
| 9 | 
            +
                  let(:body)        { JSON.generate({}) }
         | 
| 10 | 
            +
                  let(:status_code) { 200 }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  before { stub_request(:post, /.*/).to_return(body: body, status: status_code) }
         | 
| 13 | 
            +
                  before { Timecop.freeze }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  describe '#initialize' do
         | 
| 16 | 
            +
                    it 'errors if no auth is supplied' do
         | 
| 17 | 
            +
                      expect { Client.new }.to raise_error(ArgumentError)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    it 'does not error if a auth is supplied' do
         | 
| 21 | 
            +
                      expect do
         | 
| 22 | 
            +
                        Client.new :auth => auth
         | 
| 23 | 
            +
                      end.to_not raise_error
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    it 'does not error if a auth is supplied as a string' do
         | 
| 27 | 
            +
                      expect do
         | 
| 28 | 
            +
                        Client.new 'auth' => auth
         | 
| 29 | 
            +
                      end.to_not raise_error
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  describe '#track_content' do
         | 
| 34 | 
            +
                    let(:body) { JSON.generate(Factory.behavorial_classification) }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    context 'with a 400' do
         | 
| 37 | 
            +
                      let(:status_code) { 400 }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      it 'raises an ArgumentError' do
         | 
| 40 | 
            +
                        expect { client.track_content(Factory.content) }.to raise_error(ArgumentError)
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    context 'with a 500' do
         | 
| 45 | 
            +
                      let(:status_code) { 500 }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      it 'raises an RuntimeError' do
         | 
| 48 | 
            +
                        expect { client.track_content(Factory.content) }.to raise_error(RuntimeError)
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    it 'uses the current time if no timestamp is given' do
         | 
| 53 | 
            +
                      client.track_content(Factory.content.merge("created_at" => nil))
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      expected_body = JSON.generate(Factory.content.merge("created_at" => Time.now.to_f))
         | 
| 56 | 
            +
                      expect(WebMock).to have_requested(:post, "https://#{Defaults::Request.host}/track/content").with(body: expected_body)
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    it 'makes the correct request converting created_at time to float' do
         | 
| 60 | 
            +
                      client.track_content Factory.content
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                      expected_body = JSON.generate(Factory.content.merge("created_at" => Factory.content["created_at"].to_f))
         | 
| 63 | 
            +
                      expect(WebMock).to have_requested(:post, "https://#{Defaults::Request.host}/track/content").with(body: expected_body)
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    it 'returns the parsed body if a block is passed' do
         | 
| 67 | 
            +
                      response_body = nil
         | 
| 68 | 
            +
                      client.track_content(Factory.content) do |response|
         | 
| 69 | 
            +
                        response_body = response
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                      expect(response_body).to eq(JSON.load(body))
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  describe '#track_flag' do
         | 
| 76 | 
            +
                    context 'with a 400' do
         | 
| 77 | 
            +
                      let(:status_code) { 400 }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                      it 'raises an ArgumentError' do
         | 
| 80 | 
            +
                        expect { client.track_flag(Factory.flag) }.to raise_error(ArgumentError)
         | 
| 81 | 
            +
                      end
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    context 'with a 500' do
         | 
| 85 | 
            +
                      let(:status_code) { 500 }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      it 'raises an RuntimeError' do
         | 
| 88 | 
            +
                        expect { client.track_flag(Factory.flag) }.to raise_error(RuntimeError)
         | 
| 89 | 
            +
                      end
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    it 'makes the correct request converting created_at time to float' do
         | 
| 93 | 
            +
                      client.track_flag Factory.flag
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                      expected_body = JSON.generate(Factory.flag.merge("created_at" => Factory.flag["created_at"].to_f))
         | 
| 96 | 
            +
                      expect(WebMock).to have_requested(:post, "https://#{Defaults::Request.host}/track/flag").with(body: expected_body)
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  describe '#track_moderation' do
         | 
| 101 | 
            +
                    context 'with a 400' do
         | 
| 102 | 
            +
                      let(:status_code) { 400 }
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                      it 'raises an ArgumentError' do
         | 
| 105 | 
            +
                        expect { client.track_moderation(Factory.moderation) }.to raise_error(ArgumentError)
         | 
| 106 | 
            +
                      end
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    context 'with a 500' do
         | 
| 110 | 
            +
                      let(:status_code) { 500 }
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      it 'raises an RuntimeError' do
         | 
| 113 | 
            +
                        expect { client.track_moderation(Factory.moderation) }.to raise_error(RuntimeError)
         | 
| 114 | 
            +
                      end
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    it 'makes the correct request converting created_at time to float' do
         | 
| 118 | 
            +
                      client.track_moderation Factory.moderation
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                      expected_body = JSON.generate(Factory.moderation.merge("created_at" => Factory.moderation["created_at"].to_f))
         | 
| 121 | 
            +
                      expect(WebMock).to have_requested(:post, "https://#{Defaults::Request.host}/track/moderation").with(body: expected_body)
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
            end
         | 
| @@ -0,0 +1,152 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Koko
         | 
| 4 | 
            +
              class Tracker
         | 
| 5 | 
            +
                describe Request do
         | 
| 6 | 
            +
                  before do
         | 
| 7 | 
            +
                    # Try and keep debug statements out of tests
         | 
| 8 | 
            +
                    allow(subject.logger).to receive(:error)
         | 
| 9 | 
            +
                    allow(subject.logger).to receive(:debug)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  describe '#initialize' do
         | 
| 13 | 
            +
                    let!(:net_http) { Net::HTTP.new(anything, anything) }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    before do
         | 
| 16 | 
            +
                      allow(Net::HTTP).to receive(:new) { net_http }
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    it 'sets an initalized Net::HTTP read_timeout' do
         | 
| 20 | 
            +
                      expect(net_http).to receive(:use_ssl=)
         | 
| 21 | 
            +
                      described_class.new
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    it 'sets an initalized Net::HTTP read_timeout' do
         | 
| 25 | 
            +
                      expect(net_http).to receive(:read_timeout=)
         | 
| 26 | 
            +
                      described_class.new
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    it 'sets an initalized Net::HTTP open_timeout' do
         | 
| 30 | 
            +
                      expect(net_http).to receive(:open_timeout=)
         | 
| 31 | 
            +
                      described_class.new
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    it 'sets the http client' do
         | 
| 35 | 
            +
                      expect(subject.http).to_not be_nil
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    context 'no options are set' do
         | 
| 39 | 
            +
                      it 'sets a default path' do
         | 
| 40 | 
            +
                        expect(subject.instance_variable_get(:@path)).to eq(Defaults::Request.path)
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      it 'initializes a new Net::HTTP with default host and port' do
         | 
| 44 | 
            +
                        expect(Net::HTTP).to receive(:new).with(Defaults::Request.host, Defaults::Request.port)
         | 
| 45 | 
            +
                        described_class.new
         | 
| 46 | 
            +
                      end
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    context 'options are given' do
         | 
| 50 | 
            +
                      let(:path) { 'my/cool/path' }
         | 
| 51 | 
            +
                      let(:host) { 'http://www.example.com' }
         | 
| 52 | 
            +
                      let(:port) { 8080 }
         | 
| 53 | 
            +
                      let(:options) do
         | 
| 54 | 
            +
                        {
         | 
| 55 | 
            +
                          path: path,
         | 
| 56 | 
            +
                          host: host,
         | 
| 57 | 
            +
                          port: port
         | 
| 58 | 
            +
                        }
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      subject { described_class.new(options) }
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      it 'sets passed in path' do
         | 
| 64 | 
            +
                        expect(subject.instance_variable_get(:@path)).to eq(path)
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      it 'initializes a new Net::HTTP with passed in host and port' do
         | 
| 68 | 
            +
                        expect(Net::HTTP).to receive(:new).with(host, port)
         | 
| 69 | 
            +
                        described_class.new(options)
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  describe '#post' do
         | 
| 75 | 
            +
                    let(:subject) { Request.new(backoff: 0) }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    let(:response_body) { {}.to_json }
         | 
| 78 | 
            +
                    let(:auth) { 'abcdefg' }
         | 
| 79 | 
            +
                    let(:body) { { some: 'value' } }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    it 'makes a request with the correct headers and body' do
         | 
| 82 | 
            +
                      stub_request(:post, /#{subject.http.address}/).
         | 
| 83 | 
            +
                        with(body: body, headers: { 'Content-Type' => 'application/json', 'authorization' => auth }).
         | 
| 84 | 
            +
                        to_return(body: response_body)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      response = subject.post(auth, body)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                      expect(response.body).to eq({})
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    context 'with a stub' do
         | 
| 92 | 
            +
                      before do
         | 
| 93 | 
            +
                        allow(described_class).to receive(:stub) { true }
         | 
| 94 | 
            +
                      end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                      it 'returns a 200 response' do
         | 
| 97 | 
            +
                        expect(subject.post(auth, body).status).to eq(200)
         | 
| 98 | 
            +
                      end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                      it 'logs a debug statement' do
         | 
| 101 | 
            +
                        expect(subject.logger).to receive(:debug).with(/stubbed request to/)
         | 
| 102 | 
            +
                        subject.post(auth, body)
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    context 'a real request' do
         | 
| 107 | 
            +
                      let(:status_code) { 200 }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                      before do
         | 
| 110 | 
            +
                        stub_request(:post, /.*/).
         | 
| 111 | 
            +
                        to_return(body: response_body, status: status_code)
         | 
| 112 | 
            +
                      end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      context 'request is successful' do
         | 
| 115 | 
            +
                        let(:status_code) { 201 }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                        it 'returns a response code' do
         | 
| 118 | 
            +
                          expect(subject.post(auth, body).status).to eq(status_code)
         | 
| 119 | 
            +
                        end
         | 
| 120 | 
            +
                      end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      context 'request results in 400 ' do
         | 
| 123 | 
            +
                        let(:error)       { 'this is an error' }
         | 
| 124 | 
            +
                        let(:status_code) { 400 }
         | 
| 125 | 
            +
                        let(:response_body) { { error: error }.to_json }
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                        it 'returns the parsed error' do
         | 
| 128 | 
            +
                          expect(subject.post(auth, body).body).to eq({ "error" => error })
         | 
| 129 | 
            +
                        end
         | 
| 130 | 
            +
                      end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                      context 'request results in 500 ' do
         | 
| 133 | 
            +
                        let(:status_code)   { 500 }
         | 
| 134 | 
            +
                        let(:response_body) { "Something is wrong" }
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                        it 'returns the body raw' do
         | 
| 137 | 
            +
                          expect(subject.post(auth, body).body).to eq("Something is wrong")
         | 
| 138 | 
            +
                        end
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    context 'request or parsing of response results in an exception' do
         | 
| 143 | 
            +
                      let(:response_body) { 'Malformed JSON ---' }
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                      it 'has a connection error' do
         | 
| 146 | 
            +
                        expect(subject.post(auth, body).body).to match(/Connection error/)
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                    end
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Koko
         | 
| 4 | 
            +
              class Tracker
         | 
| 5 | 
            +
                describe Response do
         | 
| 6 | 
            +
                  describe '#status' do
         | 
| 7 | 
            +
                    it { expect(subject).to respond_to(:status) }
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  describe '#body' do
         | 
| 11 | 
            +
                    it { expect(subject).to respond_to(:body) }
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  describe '#initialize' do
         | 
| 15 | 
            +
                    let(:status) { 404 }
         | 
| 16 | 
            +
                    let(:body)   { 'Oh No' }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    subject { described_class.new(status, body) }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    it 'sets the instance variable status' do
         | 
| 21 | 
            +
                      expect(subject.instance_variable_get(:@status)).to eq(status)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    it 'sets the instance variable body' do
         | 
| 25 | 
            +
                      expect(subject.instance_variable_get(:@body)).to eq(body)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  describe "#valid?" do
         | 
| 30 | 
            +
                    let(:status) { nil }
         | 
| 31 | 
            +
                    let(:body)   { '' }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    subject { described_class.new(status, body) }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    context 'with a valid response' do
         | 
| 36 | 
            +
                      let(:status) { 200 }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      it 'returns true' do
         | 
| 39 | 
            +
                        expect(subject.valid?).to eq(true)
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    context 'with an invalid response' do
         | 
| 44 | 
            +
                      let(:status) { 400 }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      it 'returns true' do
         | 
| 47 | 
            +
                        expect(subject.valid?).to eq(false)
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            Bundler.require
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'active_support/time'
         | 
| 4 | 
            +
            require 'webmock/rspec'
         | 
| 5 | 
            +
            require 'timecop'
         | 
| 6 | 
            +
            require 'pry'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Setting timezone for ActiveSupport::TimeWithZone to UTC
         | 
| 9 | 
            +
            Time.zone = 'UTC'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module Koko
         | 
| 12 | 
            +
              class Tracker
         | 
| 13 | 
            +
                module Factory
         | 
| 14 | 
            +
                  def self.content
         | 
| 15 | 
            +
                    {
         | 
| 16 | 
            +
                      "id" => "123",
         | 
| 17 | 
            +
                      "created_at" => Time.at(1498070225),
         | 
| 18 | 
            +
                      "user_id" => "123",
         | 
| 19 | 
            +
                      "type" => "post",
         | 
| 20 | 
            +
                      "context_id" => "123",
         | 
| 21 | 
            +
                      "content_type" => "text",
         | 
| 22 | 
            +
                      "content" => { "text" => "Some content" }
         | 
| 23 | 
            +
                    }.clone
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def self.flag
         | 
| 27 | 
            +
                    {
         | 
| 28 | 
            +
                      "id" => "123",
         | 
| 29 | 
            +
                      "flagger_id" => "123",
         | 
| 30 | 
            +
                      "type" => "spam",
         | 
| 31 | 
            +
                      "created_at" => Time.at(1498070225),
         | 
| 32 | 
            +
                      "content" => { "id" => "123" }
         | 
| 33 | 
            +
                    }.clone
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def self.moderation
         | 
| 37 | 
            +
                    {
         | 
| 38 | 
            +
                      "id" => "123",
         | 
| 39 | 
            +
                      "moderator_id" => "123",
         | 
| 40 | 
            +
                      "type" => "user_warned",
         | 
| 41 | 
            +
                      "created_at" => Time.at(1498070225),
         | 
| 42 | 
            +
                      "content" => { "id" => "123" }
         | 
| 43 | 
            +
                    }
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def self.behavorial_classification
         | 
| 47 | 
            +
                    {
         | 
| 48 | 
            +
                      "behavorial_classification" => {
         | 
| 49 | 
            +
                        "id" =>  "123",
         | 
| 50 | 
            +
                        "classification": [
         | 
| 51 | 
            +
                          {
         | 
| 52 | 
            +
                            "label" => "crisis",
         | 
| 53 | 
            +
                            "confidence" => 0.95
         | 
| 54 | 
            +
                          }
         | 
| 55 | 
            +
                        ]
         | 
| 56 | 
            +
                      }
         | 
| 57 | 
            +
                    }
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            # usage:
         | 
| 64 | 
            +
            # it "should return a result of 5" do
         | 
| 65 | 
            +
            #   eventually(options: {timeout: 1}) { long_running_thing.result.should eq(5) }
         | 
| 66 | 
            +
            # end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            module AsyncHelper
         | 
| 69 | 
            +
              def eventually(options = {})
         | 
| 70 | 
            +
                timeout = options[:timeout] || 5 #seconds
         | 
| 71 | 
            +
                interval = options[:interval] || 0.25 #seconds
         | 
| 72 | 
            +
                time_limit = Time.now + timeout
         | 
| 73 | 
            +
                loop do
         | 
| 74 | 
            +
                  begin
         | 
| 75 | 
            +
                    yield
         | 
| 76 | 
            +
                  rescue => error
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  return if error.nil?
         | 
| 79 | 
            +
                  raise error if Time.now >= time_limit
         | 
| 80 | 
            +
                  sleep interval
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            include AsyncHelper
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,183 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: koko-ai-ruby
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.2
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Koko
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2017-06-22 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: commander
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '4.4'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '4.4'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: rake
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '10.3'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '10.3'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: rspec
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '2.0'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '2.0'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: tzinfo
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - '='
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: 1.2.1
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - '='
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: 1.2.1
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: timecop
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - '='
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: 0.8.1
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - '='
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: 0.8.1
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: pry
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: 0.10.4
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - "~>"
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: 0.10.4
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: webmock
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: 3.0.1
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: 3.0.1
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: activesupport
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ">="
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: 3.0.0
         | 
| 118 | 
            +
                - - "<"
         | 
| 119 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 120 | 
            +
                    version: 4.0.0
         | 
| 121 | 
            +
              type: :development
         | 
| 122 | 
            +
              prerelease: false
         | 
| 123 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 124 | 
            +
                requirements:
         | 
| 125 | 
            +
                - - ">="
         | 
| 126 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 127 | 
            +
                    version: 3.0.0
         | 
| 128 | 
            +
                - - "<"
         | 
| 129 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 130 | 
            +
                    version: 4.0.0
         | 
| 131 | 
            +
            description: The Koko AI ruby client library
         | 
| 132 | 
            +
            email: us@itskoko.com
         | 
| 133 | 
            +
            executables: []
         | 
| 134 | 
            +
            extensions: []
         | 
| 135 | 
            +
            extra_rdoc_files: []
         | 
| 136 | 
            +
            files:
         | 
| 137 | 
            +
            - Gemfile
         | 
| 138 | 
            +
            - Gemfile.lock
         | 
| 139 | 
            +
            - History.md
         | 
| 140 | 
            +
            - Makefile
         | 
| 141 | 
            +
            - README.md
         | 
| 142 | 
            +
            - RELEASING.md
         | 
| 143 | 
            +
            - Rakefile
         | 
| 144 | 
            +
            - koko-ai.gemspec
         | 
| 145 | 
            +
            - lib/koko-ai-ruby.rb
         | 
| 146 | 
            +
            - lib/koko.rb
         | 
| 147 | 
            +
            - lib/koko/tracker.rb
         | 
| 148 | 
            +
            - lib/koko/tracker/client.rb
         | 
| 149 | 
            +
            - lib/koko/tracker/defaults.rb
         | 
| 150 | 
            +
            - lib/koko/tracker/logging.rb
         | 
| 151 | 
            +
            - lib/koko/tracker/request.rb
         | 
| 152 | 
            +
            - lib/koko/tracker/response.rb
         | 
| 153 | 
            +
            - lib/koko/tracker/utils.rb
         | 
| 154 | 
            +
            - lib/koko/tracker/version.rb
         | 
| 155 | 
            +
            - spec/koko/tracking/client_spec.rb
         | 
| 156 | 
            +
            - spec/koko/tracking/request_spec.rb
         | 
| 157 | 
            +
            - spec/koko/tracking/response_spec.rb
         | 
| 158 | 
            +
            - spec/spec_helper.rb
         | 
| 159 | 
            +
            homepage: https://github.com/itskoko/koko-ai-ruby
         | 
| 160 | 
            +
            licenses:
         | 
| 161 | 
            +
            - MIT
         | 
| 162 | 
            +
            metadata: {}
         | 
| 163 | 
            +
            post_install_message: 
         | 
| 164 | 
            +
            rdoc_options: []
         | 
| 165 | 
            +
            require_paths:
         | 
| 166 | 
            +
            - lib
         | 
| 167 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 168 | 
            +
              requirements:
         | 
| 169 | 
            +
              - - ">="
         | 
| 170 | 
            +
                - !ruby/object:Gem::Version
         | 
| 171 | 
            +
                  version: '0'
         | 
| 172 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 173 | 
            +
              requirements:
         | 
| 174 | 
            +
              - - ">="
         | 
| 175 | 
            +
                - !ruby/object:Gem::Version
         | 
| 176 | 
            +
                  version: '0'
         | 
| 177 | 
            +
            requirements: []
         | 
| 178 | 
            +
            rubyforge_project: 
         | 
| 179 | 
            +
            rubygems_version: 2.6.8
         | 
| 180 | 
            +
            signing_key: 
         | 
| 181 | 
            +
            specification_version: 4
         | 
| 182 | 
            +
            summary: Koko AI Client
         | 
| 183 | 
            +
            test_files: []
         |