sumo-search 0.1.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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