sumo-search 0.1.1 → 1.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +0 -0
  3. data/.gitignore +0 -2
  4. data/.travis.yml +7 -0
  5. data/README.md +54 -27
  6. data/Rakefile +8 -3
  7. data/bin/sumo +2 -69
  8. data/lib/sumo/cli.rb +43 -0
  9. data/lib/sumo/client.rb +82 -0
  10. data/lib/sumo/collection.rb +97 -0
  11. data/lib/sumo/config.rb +11 -14
  12. data/lib/sumo/error.rb +8 -11
  13. data/lib/sumo/search.rb +69 -0
  14. data/lib/sumo/version.rb +9 -2
  15. data/lib/sumo.rb +52 -18
  16. data/spec/fixtures/sumo-creds +1 -0
  17. data/spec/lib/sumo/client_spec.rb +136 -0
  18. data/spec/lib/sumo/config_spec.rb +55 -74
  19. data/spec/lib/sumo/search_spec.rb +106 -0
  20. data/spec/lib/sumo_spec.rb +0 -30
  21. data/spec/spec_helper.rb +5 -7
  22. data/spec/support/vcr.rb +41 -5
  23. data/spec/vcr/Sumo_Search/_create/sets_the_id_and_client.yml +42 -0
  24. data/spec/vcr/Sumo_Search/_delete_/deletes_the_search.yml +102 -0
  25. data/spec/vcr/Sumo_Search/_messages/returns_an_Enumerator_of_each_message_in_the_search.yml +2579 -0
  26. data/spec/vcr/Sumo_Search/_records/returns_an_Enumerator_of_each_record_in_the_search.yml +2348 -0
  27. data/spec/vcr/Sumo_Search/_status/returns_the_status_of_the_search.yml +71 -0
  28. data/sumo-search.gemspec +5 -6
  29. metadata +62 -57
  30. data/lib/sumo/formatter.rb +0 -21
  31. data/lib/sumo/query_builder.rb +0 -61
  32. data/spec/fixtures/sumo_creds +0 -1
  33. data/spec/lib/sumo/formatter_spec.rb +0 -51
  34. data/spec/lib/sumo/query_builder_spec.rb +0 -128
  35. data/spec/vcr/Sumo/_search/when_the_credentials_can_be_found/and_the_query_is_valid/parses_the_response.yml +0 -49
  36. data/spec/vcr/Sumo/_search/when_the_credentials_can_be_found/but_the_query_is_invalid/raises_an_error.yml +0 -43
  37. data/spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_invalid/raises_an_error.yml +0 -43
  38. data/spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_valid/compiles_and_sends_the_query_to_the_server.yml +0 -49
data/lib/sumo/config.rb CHANGED
@@ -11,13 +11,10 @@ class Sumo::Config
11
11
 
12
12
  attr_reader :config_file
13
13
 
14
- # Set and freeze the @config_file instance variable.
14
+ # Given an optional `String`, sets and freezes the `@config_file` instance
15
+ # variable, as long as it's a valid file path.
15
16
  def initialize(config_file = Sumo::DEFAULT_CONFIG_FILE)
16
- if config_file.is_a?(String)
17
- @config_file = File.expand_path(config_file).freeze
18
- else
19
- raise TypeError, "Expected a String, got: #{config_file}"
20
- end
17
+ @config_file = File.expand_path(config_file).freeze
21
18
  end
22
19
 
23
20
  # Test if an alternate file has been specified.
@@ -25,19 +22,19 @@ class Sumo::Config
25
22
  config_file != Sumo::DEFAULT_CONFIG_FILE
26
23
  end
27
24
 
28
- # Memoize the credentials from the environment.
25
+ # Get the credentials from the environment.
29
26
  def env_creds
30
- @env_creds ||= ENV['SUMO_CREDS']
27
+ ENV['SUMO_CREDS']
31
28
  end
32
29
 
33
- # Memoize the credentials from the configuration file.
30
+ # Get the credentials from the configuration file.
34
31
  def file_creds
35
- @file_creds ||= File.read(config_file).chomp if File.exists?(config_file)
32
+ File.read(config_file).chomp if File.exists?(config_file)
36
33
  end
37
34
 
38
35
  # Load the credentials.
39
- def load_config
40
- if file_specified?
36
+ def load_creds
37
+ @config ||= if file_specified?
41
38
  file_creds || env_creds
42
39
  else
43
40
  env_creds || file_creds
@@ -45,8 +42,8 @@ class Sumo::Config
45
42
  end
46
43
 
47
44
  # Load the credentials, raising an error if none are specified.
48
- def load_config!
49
- if (creds = load_config).nil?
45
+ def load_creds!
46
+ if (creds = load_creds).nil?
50
47
  raise NoCredsFound, "No credentials were found, set ENV['SUMO_CREDS']."
51
48
  else
52
49
  creds
data/lib/sumo/error.rb CHANGED
@@ -1,18 +1,15 @@
1
- # This module acts as a namespace for all errors thrown in the gem.
1
+ # This module holds all errors for the gem.
2
2
  module Sumo::Error
3
- # This error is never raised, but instead used to catch all other errors that
4
- # may be raised.
3
+ # This class is never thrown but can be used to catch all errors thrown in the
4
+ # gem.
5
5
  class BaseError < StandardError; end
6
6
 
7
- # Raised when an unexpected type is passed to a method.
8
- class TypeError < BaseError; end
9
-
10
- # This error is raised when no credentials can be found.
7
+ # This is raised when credentials cannot be found.
11
8
  class NoCredsFound < BaseError; end
12
9
 
13
- # This error is raised when there is an error on a request.
14
- class RequestError < BaseError; end
10
+ # Raised when a 4xx-level response is returned by the API.
11
+ class ClientError < BaseError; end
15
12
 
16
- # This error is raised when a parsing error occurs.
17
- class ParseError < BaseError; end
13
+ # Raised when a 5xx-level response is returned by the API.
14
+ class ServerError < BaseError; end
18
15
  end
@@ -0,0 +1,69 @@
1
+ # This class represents a search job.
2
+ class Sumo::Search
3
+ attr_reader :id, :client
4
+
5
+ # Create a new search job with the given query.
6
+ def self.create(params = {}, client = Sumo.client)
7
+ params[:timeZone] ||= params.delete(:time_zone) || params.delete(:tz)
8
+ result = client.post(:path => '/search/jobs', :body => params.to_json)
9
+ new(JSON.parse(result)['id'], client)
10
+ end
11
+
12
+ # Initialize a new `Sumo::Search` with the given `id` and `client`.
13
+ def initialize(id, client)
14
+ @id = id
15
+ @client = client
16
+ end
17
+ private_class_method :new
18
+
19
+ # Get the status of the search job.
20
+ def status
21
+ JSON.parse(client.get(:path => base_path))
22
+ end
23
+
24
+ # Cancel the search job.
25
+ def delete!
26
+ client.delete(:path => base_path)
27
+ nil
28
+ end
29
+
30
+ # Return an `Enumerator` containing each message found by the search.
31
+ def messages
32
+ @messages ||= Sumo::Collection.new(
33
+ :get_values => proc { |hash| self.get_messages(hash) },
34
+ :get_status => proc { self.status },
35
+ :count_key => 'messageCount'
36
+ ).each
37
+ end
38
+
39
+ # Return an `Enumerator` containing each record found by the search.
40
+ def records
41
+ @records ||= Sumo::Collection.new(
42
+ :get_values => proc { |hash| self.get_records(hash) },
43
+ :get_status => proc { self.status },
44
+ :count_key => 'recordCount'
45
+ ).each
46
+ end
47
+
48
+ # Get the messages from the given offset and limit.
49
+ def get_messages(query)
50
+ resp = client.get(:path => "#{base_path}/messages", :query => query)
51
+ extract_response('messages', resp)
52
+ end
53
+
54
+ # Get the records from the given offset and limit.
55
+ def get_records(query)
56
+ resp = client.get(:path => "#{base_path}/records", :query => query)
57
+ extract_response('records', resp)
58
+ end
59
+
60
+ def extract_response(key, resp)
61
+ JSON.parse(resp)[key].map { |hash| hash['map'] }
62
+ end
63
+ private :extract_response
64
+
65
+ def base_path
66
+ @base_path ||= "/search/jobs/#{id}"
67
+ end
68
+ private :base_path
69
+ end
data/lib/sumo/version.rb CHANGED
@@ -1,4 +1,11 @@
1
- # This file contains the version of the gem.
1
+ # This module holds versioning information for the gem.
2
2
  module Sumo
3
- VERSION = '0.1.1'
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 2
6
+ RELEASE = nil
7
+
8
+ VERSION = [MAJOR, MINOR, PATCH, RELEASE].compact.join('.')
9
+
10
+ API_VERSION = 1
4
11
  end
data/lib/sumo.rb CHANGED
@@ -1,28 +1,62 @@
1
1
  require 'base64'
2
+ require 'clamp'
2
3
  require 'excon'
3
4
  require 'json'
4
5
 
5
- # This module acts as a namespace, and contains a `.search` method which ties
6
- # together functionality of its child modules.
6
+ # This is the top level module for the gem. It is used as a namespace and holds
7
+ # top-level convenience functions.
7
8
  module Sumo
8
- autoload :Config, 'sumo/config'
9
- autoload :Error, 'sumo/error'
10
- autoload :Formatter, 'sumo/formatter'
11
- autoload :QueryBuilder, 'sumo/query_builder'
12
-
13
- # Unless otherwise specified in the initializer, the configuration file
14
- # defaults to '~/.sumo_creds'.
9
+ # Define global constants.
15
10
  DEFAULT_CONFIG_FILE = File.expand_path('~/.sumo_creds').freeze
16
11
 
17
- # Search the given query. If any options are supplied, those are sent to the
18
- # QueryBuilder. If the key is supplied, that key will be attempted to be
19
- # extracted from the JSON response. If a config_file is supplied, that will
20
- # be read instead of the default.
21
- def search(query, options: {}, key: nil, config_file: DEFAULT_CONFIG_FILE)
22
- creds = Config.new(config_file).load_config!
23
- response = QueryBuilder.new(creds, options.merge('q' => query)).execute
24
- formatted = Formatter.format_json(response)
25
- key.nil? ? formatted : Formatter.extract_key(key, formatted)
12
+ # Require sub-modules.
13
+ require 'sumo/error'
14
+ require 'sumo/config'
15
+ require 'sumo/client'
16
+ require 'sumo/search'
17
+ require 'sumo/collection'
18
+ require 'sumo/cli'
19
+ require 'sumo/version'
20
+
21
+ # Define top-level functions.
22
+
23
+ def creds
24
+ @creds ||= config.load_creds!
25
+ end
26
+ module_function :creds
27
+
28
+ def creds=(new_creds)
29
+ @creds = new_creds
30
+ end
31
+ module_function :creds=
32
+
33
+ # The default config for the gem.
34
+ def config
35
+ @config ||= Sumo::Config.new
36
+ end
37
+ module_function :config
38
+
39
+ # Reset the default config for the gem.
40
+ def config=(new_config)
41
+ @config = new_config
42
+ end
43
+ module_function :config=
44
+
45
+ # The default client for the gem.
46
+ def client
47
+ @client ||= Sumo::Client.new
48
+ end
49
+ module_function :client
50
+
51
+ # Reset the default client for the gem.
52
+ def client=(new_client)
53
+ @client = new_client
54
+ end
55
+ module_function :client=
56
+
57
+ # Create a new search.
58
+ def search(*args)
59
+ Sumo::Search.new(*args)
26
60
  end
27
61
  module_function :search
28
62
  end
@@ -0,0 +1 @@
1
+ test@test.net:trustno1
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sumo::Client do
4
+ describe '#initialize' do
5
+ let(:creds) { 'email@email.email:password' }
6
+
7
+ context 'with no arguments' do
8
+ subject { Sumo::Client.new }
9
+ before { Sumo.stub(:creds).and_return(creds) }
10
+ it 'sets the default credentials' do
11
+ subject.creds.should == creds
12
+ end
13
+ end
14
+
15
+ context 'with an argument' do
16
+ subject { Sumo::Client.new(creds) }
17
+
18
+ it 'sets the credentials to that argument' do
19
+ subject.creds.should == creds
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#request' do
25
+ let(:connection) { double(:connection) }
26
+ let(:response) { double(:response) }
27
+ let(:creds) { 'creds@email.com:test' }
28
+ let(:encoded) { Base64.encode64(creds).strip }
29
+ subject { Sumo::Client.new(creds) }
30
+ before { subject.stub(:connection).and_return(connection) }
31
+
32
+ it 'sets the correct headers' do
33
+ subject.stub(:handle_errors!)
34
+ response.stub(:body)
35
+ response.stub(:headers).and_return({})
36
+ connection.should_receive(:request)
37
+ .with(
38
+ :method => :get,
39
+ :path => '/api/v1/',
40
+ :headers => {
41
+ 'Content-Type' => 'application/json',
42
+ 'Accept' => 'application/json',
43
+ 'Authorization' => "Basic #{encoded}" })
44
+ .and_return(response)
45
+ subject.request(:method => :get, :path => '/')
46
+ end
47
+
48
+ context 'when a 2xx-level status code is returned by the API' do
49
+ let(:body) { 'TEST RESULT' }
50
+ let(:cookie) { 'oreo' }
51
+ let(:headers) { {'Set-Cookie' => cookie } }
52
+ let(:response) {
53
+ double(:response, :status => 200, :body => body, :headers => headers)
54
+ }
55
+ before { connection.stub(:request).and_return(response) }
56
+
57
+ it 'returns the response body' do
58
+ subject.request(:method => :get, :path => '/').should == body
59
+ end
60
+
61
+ it 'sets the cookie' do
62
+ subject.request(:method => :get, :path => '/')
63
+ connection.should_receive(:request)
64
+ .with(
65
+ :method => :get,
66
+ :path => '/api/v1/',
67
+ :headers => {
68
+ 'Content-Type' => 'application/json',
69
+ 'Accept' => 'application/json',
70
+ 'Cookie' => cookie,
71
+ 'Authorization' => "Basic #{encoded}" })
72
+ .and_return(response)
73
+ subject.request(:method => :get, :path => '/')
74
+ end
75
+ end
76
+
77
+ context 'when a 4xx-level status code is returned by the API' do
78
+ let(:response) { double(:response, :status => 400, :body => body) }
79
+ let(:body) { { 'message' => message }.to_json }
80
+ let(:message) { 'Client Error' }
81
+ before { connection.stub(:request).and_return(response) }
82
+
83
+ context 'when a message can be parsed out of the response' do
84
+ it 'raises a ClientError with that message' do
85
+ expect { subject.request(:method => :post, :path => '/') }
86
+ .to raise_error(Sumo::Error::ClientError, message)
87
+ end
88
+ end
89
+
90
+ context 'when a message cannot be parsed out of the response' do
91
+ let(:message) { nil }
92
+
93
+ it 'raises a ClientError with the default error message' do
94
+ expect { subject.request(:method => :delete, :path => '/') }
95
+ .to raise_error(Sumo::Error::ClientError,
96
+ Sumo::Client::DEFAULT_ERROR_MESSAGE)
97
+ end
98
+ end
99
+ end
100
+
101
+ context 'when a 5xx-level status code is returned by the API' do
102
+ let(:response) { double(:response, :status => 500, :body => body) }
103
+ let(:body) { { 'message' => message }.to_json }
104
+ let(:message) { 'Server Error' }
105
+ before { connection.stub(:request).and_return(response) }
106
+
107
+ context 'when a message can be parsed out of the response' do
108
+ it 'raises a ServerError with that message' do
109
+ expect { subject.request(:method => :post, :path => '/') }
110
+ .to raise_error(Sumo::Error::ServerError, message)
111
+ end
112
+ end
113
+
114
+ context 'when a message cannot be parsed out of the response' do
115
+ let(:message) { nil }
116
+
117
+ it 'raises a ServerError with the default error message' do
118
+ expect { subject.request(:method => :delete, :path => '/') }
119
+ .to raise_error(Sumo::Error::ServerError,
120
+ Sumo::Client::DEFAULT_ERROR_MESSAGE)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ [:get, :post, :delete].each do |http_method|
127
+ describe "##{http_method}" do
128
+ subject { Sumo::Client.new('') }
129
+
130
+ it "sends a request where the HTTP method is #{http_method}" do
131
+ subject.should_receive(:request).with(:method => http_method)
132
+ subject.public_send(http_method, {})
133
+ end
134
+ end
135
+ end
136
+ end
@@ -1,129 +1,110 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Sumo::Config do
4
- describe '.new' do
5
- subject { Sumo::Config }
4
+ let(:test_config_file) {
5
+ File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'sumo-creds')
6
+ }
6
7
 
7
- context 'when no argument is given' do
8
- it 'sets @config_file to its default value' do
9
- subject.new.config_file.should == Sumo::DEFAULT_CONFIG_FILE
10
- end
11
- end
12
-
13
- context 'when an argument is given' do
14
- context 'when it is not a String' do
15
- it 'raises an error' do
16
- expect { subject.new(nil) }.to raise_error(Sumo::Error::TypeError)
17
- end
18
- end
8
+ describe '#initialize' do
9
+ let(:config_file) { '/etc/sumo-creds' }
10
+ subject { Sumo::Config.new(config_file) }
19
11
 
20
- context 'when it is a String' do
21
- let(:file) { '/dev/null' }
22
-
23
- it 'sets @config_file to the argument' do
24
- subject.new(file).config_file.should == file
25
- end
26
- end
12
+ it 'sets the @config_file instance variable' do
13
+ subject.config_file.should == config_file
27
14
  end
28
15
  end
29
16
 
30
17
  describe '#file_specified?' do
31
- context 'when @config_file is the default' do
32
- it 'returns false' do
33
- subject.file_specified?.should be_false
34
- end
35
- end
36
-
37
- context 'when @config_file is not the default' do
38
- subject { Sumo::Config.new('my-config-file') }
18
+ context 'when Sumo::DEFAULT_CONFIG_FILE is not equal to @config_file' do
19
+ let(:config_file) { '/etc/sumo-creds' }
20
+ subject { Sumo::Config.new(config_file) }
39
21
 
40
22
  it 'returns true' do
41
23
  subject.file_specified?.should be_true
42
24
  end
43
25
  end
44
- end
45
-
46
- describe '#env_creds' do
47
- before { ENV['SUMO_CREDS'] = creds }
48
26
 
49
- context 'when the environment variable is not set' do
50
- let(:creds) { nil }
27
+ context 'when Sumo::DEFAULT_CONFIG_FILE is equal to @config_file' do
28
+ subject { Sumo::Config.new }
51
29
 
52
- it 'returns nil' do
53
- subject.env_creds.should be_nil
30
+ it 'returns false' do
31
+ subject.file_specified?.should be_false
54
32
  end
55
33
  end
34
+ end
56
35
 
57
- context 'when the environment variable is set' do
58
- let(:creds) { 'alpha' }
36
+ describe '#env_creds' do
37
+ let(:email) { 'test@test.net' }
38
+ let(:password) { 'trustno1' }
39
+ let(:creds) { [email, password].join(':') }
59
40
 
60
- it 'returns that variable' do
61
- subject.env_creds.should == creds
62
- end
41
+ before { ENV['SUMO_CREDS'] = creds }
42
+ after { ENV['SUMO_CREDS'] = nil }
43
+
44
+ it 'retrieves the $SUMO_CREDS environment variable' do
45
+ subject.env_creds.should == creds
63
46
  end
64
47
  end
65
48
 
66
49
  describe '#file_creds' do
67
- subject { Sumo::Config.new(file) }
50
+ subject { Sumo::Config.new(config_file) }
68
51
 
69
- context 'when the @config_file does not exist' do
70
- let(:file) { 'not a file' }
52
+ context 'when @config_file is not a file' do
53
+ let(:config_file) { '/not/a/file' }
71
54
 
72
55
  it 'returns nil' do
73
56
  subject.file_creds.should be_nil
74
57
  end
75
58
  end
76
59
 
77
- context 'when @config_file does exist' do
78
- let(:file) { 'spec/fixtures/sumo_creds' }
60
+ context 'when @config_file is a file' do
61
+ let(:config_file) { test_config_file }
79
62
 
80
- it 'returns its contents' do
81
- subject.file_creds.should == 'fake_user@fake_site:fake_pass'
63
+ it 'returns the contents of that file' do
64
+ subject.file_creds.should == File.read(config_file).strip
82
65
  end
83
66
  end
84
67
  end
85
68
 
86
- describe '#load_config' do
87
- before do
88
- subject.stub(:file_specified?).and_return(specified)
89
- subject.stub(:file_creds).and_return('alpha')
90
- subject.stub(:env_creds).and_return('beta')
91
- end
69
+ describe '#load_creds' do
70
+ let(:email) { 'test@test.net' }
71
+ let(:password) { 'trustsum1' }
72
+ let(:creds) { [email, password].join(':') }
92
73
 
93
- context 'when an alternate file is specifed' do
94
- let(:specified) { true }
74
+ before { ENV['SUMO_CREDS'] = creds }
75
+ after { ENV['SUMO_CREDS'] = nil }
95
76
 
96
- it 'attempts to read the file first' do
97
- subject.load_config.should == 'alpha'
77
+ context 'when a config file is not specified' do
78
+ it 'prefers the environment variable' do
79
+ subject.load_creds.should == ENV['SUMO_CREDS']
98
80
  end
99
81
  end
100
82
 
101
- context 'when an alternate file is not specified' do
102
- let(:specified) { false }
83
+ context 'when a config file is specified' do
84
+ subject { Sumo::Config.new(test_config_file) }
103
85
 
104
- it 'attempts to read the environment variable first' do
105
- subject.load_config.should == 'beta'
86
+ it 'prefers the config file' do
87
+ subject.load_creds.should == File.read(test_config_file).strip
106
88
  end
107
89
  end
108
90
  end
109
91
 
110
- describe 'load_config!' do
111
- before { subject.stub(:load_config).and_return(config) }
112
-
113
- context 'when #load_config returns nil' do
114
- let(:config) { nil }
92
+ describe '#load_creds!' do
93
+ context 'when the configuration cannot be found' do
94
+ before { subject.stub(:load_creds).and_return(nil) }
115
95
 
116
96
  it 'raises an error' do
117
- expect { subject.load_config! }
118
- .to raise_error(Sumo::Error::NoCredsFound)
97
+ expect { subject.load_creds! }
98
+ .to raise_error(Sumo::Error::NoCredsFound)
119
99
  end
120
100
  end
121
101
 
122
- context 'when #load_config returns a value' do
123
- let(:config) { 'configuration' }
102
+ context 'when the configuration can be found' do
103
+ let(:creds) { 'sumo@sumo.net:my-pass' }
104
+ before { subject.stub(:load_creds).and_return(creds) }
124
105
 
125
- it 'returns that value' do
126
- subject.load_config!.should == config
106
+ it 'returns the configuration' do
107
+ subject.load_creds!.should == creds
127
108
  end
128
109
  end
129
110
  end