mode 0.0.5 → 0.0.7
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 +4 -4
- data/.gitignore +2 -1
- data/README.md +17 -22
- data/bin/mode +1 -1
- data/lib/mode.rb +34 -6
- data/lib/mode/api/form.rb +53 -0
- data/lib/mode/api/link.rb +31 -0
- data/lib/mode/api/request.rb +181 -0
- data/lib/mode/api/resource.rb +67 -0
- data/lib/mode/auth/access_token.rb +23 -0
- data/lib/mode/cli.rb +3 -3
- data/lib/mode/cli/analyze.rb +1 -1
- data/lib/mode/cli/base.rb +5 -0
- data/lib/mode/cli/connect.rb +18 -0
- data/lib/mode/cli/helpers.rb +0 -9
- data/lib/mode/cli/import.rb +9 -38
- data/lib/mode/cli/login.rb +13 -0
- data/lib/mode/cli/package.rb +2 -5
- data/lib/mode/commands/analyze_field.rb +20 -21
- data/lib/mode/commands/analyze_schema.rb +69 -48
- data/lib/mode/commands/connect.rb +78 -0
- data/lib/mode/commands/helpers.rb +54 -0
- data/lib/mode/commands/import.rb +209 -20
- data/lib/mode/commands/login.rb +111 -0
- data/lib/mode/config.rb +13 -33
- data/lib/mode/configurable.rb +46 -0
- data/lib/mode/connector/config.rb +31 -0
- data/lib/mode/connector/daemon.rb +27 -0
- data/lib/mode/connector/data_source.rb +75 -0
- data/lib/mode/connector/dataset.rb +13 -0
- data/lib/mode/connector/message.rb +31 -0
- data/lib/mode/connector/poller.rb +27 -0
- data/lib/mode/connector/processor.rb +58 -0
- data/lib/mode/connector/registrar.rb +36 -0
- data/lib/mode/connector/scheduler.rb +62 -0
- data/lib/mode/connector/selector.rb +47 -0
- data/lib/mode/connector/type_map.rb +45 -0
- data/lib/mode/connector/uploader.rb +50 -0
- data/lib/mode/logger.rb +202 -0
- data/lib/mode/version.rb +1 -1
- data/mode.gemspec +13 -2
- data/spec/api/form_spec.rb +51 -0
- data/spec/api/link_spec.rb +23 -0
- data/spec/api/request_spec.rb +111 -0
- data/spec/api/resource_spec.rb +70 -0
- data/spec/auth/access_token_spec.rb +22 -0
- data/spec/commands/analyze_field_spec.rb +26 -0
- data/spec/commands/analyze_schema_spec.rb +7 -5
- data/spec/commands/connect_spec.rb +80 -0
- data/spec/commands/helpers_spec.rb +69 -0
- data/spec/commands/import_spec.rb +155 -0
- data/spec/commands/login_spec.rb +178 -0
- data/spec/config_spec.rb +9 -7
- data/spec/connector/config_spec.rb +46 -0
- data/spec/connector/daemon_spec.rb +30 -0
- data/spec/connector/data_source_spec.rb +73 -0
- data/spec/connector/message_spec.rb +22 -0
- data/spec/connector/poller_spec.rb +26 -0
- data/spec/connector/processor_spec.rb +93 -0
- data/spec/connector/registrar_spec.rb +53 -0
- data/spec/connector/scheduler_spec.rb +93 -0
- data/spec/connector/selector_spec.rb +54 -0
- data/spec/connector/type_map_spec.rb +45 -0
- data/spec/connector/uploader_spec.rb +55 -0
- data/spec/fixtures/country-codes/README.md +71 -0
- data/spec/fixtures/country-codes/data/country-codes.csv +250 -0
- data/spec/fixtures/country-codes/datapackage.json +142 -0
- data/spec/fixtures/country-codes/scripts/get_countries_of_earth.py +370 -0
- data/spec/fixtures/country-codes/scripts/reorder_columns.py +8 -0
- data/spec/fixtures/country-codes/scripts/requirements.pip +2 -0
- data/spec/fixtures/espn_draft.csv +473 -1
- data/spec/fixtures/espn_draft/data.csv +473 -0
- data/spec/fixtures/espn_draft/datapackage.json +43 -0
- data/spec/logger_spec.rb +79 -0
- data/spec/spec_helper.rb +6 -1
- metadata +156 -19
- data/lib/mode/cli/setup.rb +0 -12
- data/lib/mode/commands/package.rb +0 -56
- data/lib/mode/commands/setup.rb +0 -36
- data/lib/mode/package_builder.rb +0 -57
- data/spec/commands/setup_spec.rb +0 -62
- data/spec/fixtures/MOCK_DATA.csv +0 -100001
- data/spec/fixtures/cb_clean_small.csv +0 -100000
- data/spec/fixtures/duplicate_keys.csv +0 -3
- data/spec/fixtures/format_examples.csv.txt +0 -6
- data/spec/fixtures/format_examples_after_excel.csv.txt +0 -1
@@ -0,0 +1,45 @@
|
|
1
|
+
module Mode
|
2
|
+
module Connector
|
3
|
+
class TypeMap
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :types
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@types = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
types.each do |name, value|
|
14
|
+
yield name, type?(name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def insert(row)
|
19
|
+
row.each do |name, value|
|
20
|
+
types[name] ||= {}
|
21
|
+
types[name][type(value)] ||= 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def type(value)
|
26
|
+
if value.kind_of?(Integer)
|
27
|
+
:integer
|
28
|
+
elsif value.kind_of?(Float)
|
29
|
+
:number
|
30
|
+
elsif value.kind_of?(Date) || value.kind_of?(Time)
|
31
|
+
:datetime
|
32
|
+
elsif value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
|
33
|
+
:boolean
|
34
|
+
else
|
35
|
+
:string
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def type?(name)
|
40
|
+
keys = types[name].keys
|
41
|
+
keys.compact.length == 1 ? keys.first : :string
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Mode
|
2
|
+
module Connector
|
3
|
+
class Uploader
|
4
|
+
attr_reader :path
|
5
|
+
attr_reader :dataset
|
6
|
+
|
7
|
+
def initialize(path, dataset)
|
8
|
+
@path = path
|
9
|
+
@dataset = dataset
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform!
|
13
|
+
Mode::API::Request.put(
|
14
|
+
path,
|
15
|
+
:execution => {
|
16
|
+
:dataset => {
|
17
|
+
:content => content,
|
18
|
+
:columns => JSON.generate(columns)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def error!(path, message, detail = nil)
|
26
|
+
Mode::API::Request.put(path,
|
27
|
+
:execution => {
|
28
|
+
:error => {
|
29
|
+
:detail => detail,
|
30
|
+
:message => message
|
31
|
+
}
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def columns
|
40
|
+
dataset.column_types.map do |name, type|
|
41
|
+
{:name => name, :type => type}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def content
|
46
|
+
Faraday::UploadIO.new(dataset.path, 'applicaton/octet-stream')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/mode/logger.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
#
|
4
|
+
# LEVEL [TIME] COMPONENT: MESSAGE
|
5
|
+
# ! Detail Line 1
|
6
|
+
# ! Detail Line 2
|
7
|
+
#
|
8
|
+
# All timestamps are in UTC and ISO 8601 format.
|
9
|
+
#
|
10
|
+
# You can grep for messages of a specific level really easily:
|
11
|
+
#
|
12
|
+
# tail -f mode.log | grep '^WARN'
|
13
|
+
# You can grep for messages from a specific component really easily:
|
14
|
+
#
|
15
|
+
# tail -f mode.log | grep 'Connector::Config'
|
16
|
+
# You can even pull out full exception stack traces, plus the accompanying log message:
|
17
|
+
#
|
18
|
+
# tail -f mode.log | grep -B 1 '^\!'
|
19
|
+
#
|
20
|
+
|
21
|
+
#
|
22
|
+
# Modified singleton that allows replacement which is very useful for testing
|
23
|
+
#
|
24
|
+
|
25
|
+
module Mode
|
26
|
+
class Logger
|
27
|
+
# For Convenience
|
28
|
+
DEBUG = ::Logger::DEBUG
|
29
|
+
WARN = ::Logger::WARN
|
30
|
+
INFO = ::Logger::INFO
|
31
|
+
ERROR = ::Logger::ERROR
|
32
|
+
FATAL = ::Logger::FATAL
|
33
|
+
|
34
|
+
attr_reader :logger
|
35
|
+
attr_reader :mutex
|
36
|
+
|
37
|
+
attr_reader :enabled
|
38
|
+
|
39
|
+
def initialize(options = {})
|
40
|
+
@enabled = true
|
41
|
+
@mutex = Mutex.new # Keep it secret, keep it safe
|
42
|
+
|
43
|
+
path = options[:path] || self.class.default_path
|
44
|
+
level = options[:level] || self.class.default_level
|
45
|
+
formatter = options[:formatter] || self.class.default_formatter
|
46
|
+
|
47
|
+
@logger = ::Logger.new(path, rotate_freq)
|
48
|
+
|
49
|
+
set_log_level(level)
|
50
|
+
set_log_formatter(formatter)
|
51
|
+
end
|
52
|
+
|
53
|
+
def disable!
|
54
|
+
mutex.synchronize { @enabled = false }
|
55
|
+
end
|
56
|
+
|
57
|
+
def enable!
|
58
|
+
mutex.synchronize { @enabled = true }
|
59
|
+
end
|
60
|
+
|
61
|
+
def debug(component, message, detail = [])
|
62
|
+
log_with_formatting(Logger::DEBUG, component, message, detail)
|
63
|
+
end
|
64
|
+
|
65
|
+
def debug!
|
66
|
+
set_log_level(Logger::DEBUG)
|
67
|
+
end
|
68
|
+
|
69
|
+
def info(component, message, detail = [])
|
70
|
+
log_with_formatting(Logger::INFO, component, message, detail)
|
71
|
+
end
|
72
|
+
|
73
|
+
def info!
|
74
|
+
set_log_level(Logger::INFO)
|
75
|
+
end
|
76
|
+
|
77
|
+
def warn(component, message, detail = [])
|
78
|
+
log_with_formatting(Logger::WARN, component, message, detail)
|
79
|
+
end
|
80
|
+
|
81
|
+
def warn!
|
82
|
+
set_log_level(Logger::WARN)
|
83
|
+
end
|
84
|
+
|
85
|
+
def error(component, message, detail = [])
|
86
|
+
log_with_formatting(Logger::ERROR, component, message, detail)
|
87
|
+
end
|
88
|
+
|
89
|
+
def error!
|
90
|
+
set_log_level(Logger::ERROR)
|
91
|
+
end
|
92
|
+
|
93
|
+
def fatal(component, message, detail = [])
|
94
|
+
log_with_formatting(Logger::FATAL, component, message, detail)
|
95
|
+
end
|
96
|
+
|
97
|
+
def fatal!
|
98
|
+
set_log_level(Logger::FATAL)
|
99
|
+
end
|
100
|
+
|
101
|
+
class << self
|
102
|
+
attr_reader :mutex
|
103
|
+
attr_reader :instance
|
104
|
+
|
105
|
+
def instance(logger = nil)
|
106
|
+
@mutex ||= Mutex.new
|
107
|
+
|
108
|
+
if logger
|
109
|
+
mutex.synchronize do
|
110
|
+
@instance = logger
|
111
|
+
end
|
112
|
+
else
|
113
|
+
@instance ||= new
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_level
|
118
|
+
Logger::INFO
|
119
|
+
end
|
120
|
+
|
121
|
+
def default_path
|
122
|
+
File.join(mode_path, 'mode.log') # This should come from config
|
123
|
+
end
|
124
|
+
|
125
|
+
def default_rotate_freq
|
126
|
+
'weekly'
|
127
|
+
end
|
128
|
+
|
129
|
+
def default_formatter
|
130
|
+
@default_formatter ||= Proc.new do |severity, datetime, progname, message|
|
131
|
+
"#{severity} [#{format_time(datetime)}]: #{message}\n"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def format_time(time)
|
136
|
+
time_to_datetime(time).new_offset(0).iso8601
|
137
|
+
end
|
138
|
+
|
139
|
+
def time_to_datetime(time)
|
140
|
+
#
|
141
|
+
# http://stackoverflow.com/questions/279769/convert-to-from-datetime-and-time-in-ruby
|
142
|
+
#
|
143
|
+
seconds = time.sec + Rational(time.usec, 10**6)
|
144
|
+
offset = Rational(time.utc_offset, 60 * 60 * 24)
|
145
|
+
DateTime.new(time.year, time.month, time.day, time.hour, time.min, seconds, offset)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def mode_path
|
151
|
+
FileUtils.mkdir_p(File.expand_path("~/.mode"))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def rotate_freq
|
158
|
+
self.class.default_rotate_freq
|
159
|
+
end
|
160
|
+
|
161
|
+
def set_log_level(level)
|
162
|
+
@logger.level = level
|
163
|
+
end
|
164
|
+
|
165
|
+
def set_log_formatter(formatter)
|
166
|
+
@logger.formatter = formatter
|
167
|
+
end
|
168
|
+
|
169
|
+
def log_with_formatting(severity, component, message, detail = [])
|
170
|
+
mutex.synchronize do
|
171
|
+
return unless enabled
|
172
|
+
|
173
|
+
set_log_formatter(component_formatter(component))
|
174
|
+
|
175
|
+
logger.add(severity, message)
|
176
|
+
|
177
|
+
if detail.length > 0
|
178
|
+
set_log_formatter(detail_formatter)
|
179
|
+
|
180
|
+
detail.each do |message|
|
181
|
+
logger.add(severity, message)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
set_log_formatter(self.class.default_formatter)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def component_formatter(component)
|
190
|
+
@component_formatter ||= Proc.new do |severity, datetime, progname, message|
|
191
|
+
datetime = self.class.format_time(datetime)
|
192
|
+
"#{severity} [#{datetime}] #{component}: #{message}\n"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def detail_formatter
|
197
|
+
@detail_formatter ||= Proc.new do |severity, datetime, progname, message|
|
198
|
+
"! #{message}\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
data/lib/mode/version.rb
CHANGED
data/mode.gemspec
CHANGED
@@ -18,14 +18,25 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
#
|
21
|
+
# CLI
|
22
22
|
spec.add_runtime_dependency "thor"
|
23
23
|
spec.add_runtime_dependency "terminal-table"
|
24
24
|
|
25
|
-
#
|
25
|
+
# Data Packaging
|
26
26
|
spec.add_runtime_dependency "data_kit"
|
27
27
|
spec.add_runtime_dependency "data_package"
|
28
28
|
|
29
|
+
# HTTP
|
30
|
+
spec.add_runtime_dependency "faraday"
|
31
|
+
spec.add_runtime_dependency "uri_template"
|
32
|
+
|
33
|
+
# Connectivity
|
34
|
+
spec.add_runtime_dependency "sequel"
|
35
|
+
|
36
|
+
# Concurrency & Processes
|
37
|
+
spec.add_runtime_dependency "daemon-spawn"
|
38
|
+
spec.add_runtime_dependency "rufus-scheduler"
|
39
|
+
|
29
40
|
# Development Dependencies
|
30
41
|
spec.add_development_dependency "bundler", "~> 1.3"
|
31
42
|
spec.add_development_dependency "rake"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mode::API::Link do
|
4
|
+
let(:content) {
|
5
|
+
{
|
6
|
+
'method' => 'post',
|
7
|
+
'action' => '/resources',
|
8
|
+
'input' => {
|
9
|
+
'resource' => {
|
10
|
+
'name' => {
|
11
|
+
'type' => 'text',
|
12
|
+
'value' => ''
|
13
|
+
},
|
14
|
+
'widgets' => {
|
15
|
+
'type' => 'text',
|
16
|
+
'value' => '500'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
before do
|
24
|
+
initialize_logger
|
25
|
+
end
|
26
|
+
|
27
|
+
it "initializes with content" do
|
28
|
+
form = Mode::API::Form.new(content)
|
29
|
+
form.method.should == content['method']
|
30
|
+
form.action.should == content['action']
|
31
|
+
form.input('resource').should == content['input']['resource']
|
32
|
+
end
|
33
|
+
|
34
|
+
it "submits with params" do
|
35
|
+
params = {
|
36
|
+
'resource' => {
|
37
|
+
'name' => 'test'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
Mode::API::Request.should_receive(:post).with('/resources', {
|
42
|
+
'resource' => {
|
43
|
+
'name' => 'test',
|
44
|
+
'widgets' => '500'
|
45
|
+
}
|
46
|
+
}).and_return(:response)
|
47
|
+
|
48
|
+
form = Mode::API::Form.new(content)
|
49
|
+
form.submit!(params).should == :response
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mode::API::Link do
|
4
|
+
let(:content) {
|
5
|
+
{
|
6
|
+
'templated' => true,
|
7
|
+
'href' => '/path/to{/thing}{?q}',
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
before do
|
12
|
+
initialize_logger
|
13
|
+
end
|
14
|
+
|
15
|
+
it "initializes with content" do
|
16
|
+
link = Mode::API::Link.new(content)
|
17
|
+
link.href.should == content['href']
|
18
|
+
link.templated.should == content['templated']
|
19
|
+
|
20
|
+
link.expand(:thing => 't').should == "/path/to/t"
|
21
|
+
link.expand(:thing => 't', :q => 'query').should == "/path/to/t?q=query"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mode::API::Request do
|
4
|
+
let(:username) { 'besquared' }
|
5
|
+
let(:access_token) { 'access_token '}
|
6
|
+
|
7
|
+
before do
|
8
|
+
Mode::API::Request.configure(:test, {
|
9
|
+
:credentials => {
|
10
|
+
:username => username,
|
11
|
+
:access_token => access_token
|
12
|
+
}
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
initialize_logger
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'initializes with default values' do
|
21
|
+
request = Mode::API::Request.new(:get, '/packages')
|
22
|
+
request.method.should == :get
|
23
|
+
request.path.should == '/packages'
|
24
|
+
request.params.should == {}
|
25
|
+
|
26
|
+
http = double(:http)
|
27
|
+
request.should_receive(:http).and_return(http)
|
28
|
+
|
29
|
+
response = double(:response,
|
30
|
+
:body => '{}', :success? => true, :status => 200)
|
31
|
+
http.should_receive(:get).and_return(response)
|
32
|
+
|
33
|
+
request.perform.should be_instance_of(Mode::API::Resource)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'validates configuration' do
|
37
|
+
Mode::API::Request.should_receive(:valid?).and_return(false)
|
38
|
+
expect { Mode::API::Request.validate! }.to raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has environment host' do
|
42
|
+
hosts = {
|
43
|
+
'test' => 'test',
|
44
|
+
'development' => 'localhost',
|
45
|
+
'staging' => 'staging.modeanalytics.com',
|
46
|
+
'production' => 'www.modeanalytics.com'
|
47
|
+
}
|
48
|
+
|
49
|
+
hosts.each do |env, host|
|
50
|
+
Mode::API::Request.configure(env, {})
|
51
|
+
Mode::API::Request.send(:env_host).should == host
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "has bookmarked paths" do
|
56
|
+
Mode::API::Request.access_tokens_path.should == \
|
57
|
+
'/api/besquared/access_tokens'
|
58
|
+
|
59
|
+
Mode::API::Request.packages_path.should == \
|
60
|
+
'/api/besquared/packages'
|
61
|
+
|
62
|
+
Mode::API::Request.data_source_connections_path.should == \
|
63
|
+
'/api/besquared/data_source_connections'
|
64
|
+
|
65
|
+
Mode::API::Request.data_source_connection_path.should == \
|
66
|
+
'/api/besquared/data_source_connections/access_token'
|
67
|
+
|
68
|
+
Mode::API::Request.data_source_connection_messages_path.should == \
|
69
|
+
'/api/besquared/data_source_connections/access_token/messages'
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'has http object' do
|
73
|
+
# make this more robust
|
74
|
+
Mode::API::Request.send(:http).should_not == nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'has shortcut methods' do
|
78
|
+
response = { :body => '{}', :success? => true, :status => 200 }
|
79
|
+
|
80
|
+
http = double(:http, {
|
81
|
+
:head => double(:head, response),
|
82
|
+
:get => double(:get, response),
|
83
|
+
:post => double(:post, response),
|
84
|
+
:put => double(:put, response),
|
85
|
+
:delete => double(:delete, response)
|
86
|
+
})
|
87
|
+
|
88
|
+
Mode::API::Request.should_receive(:http).exactly(5).times.and_return(http)
|
89
|
+
|
90
|
+
Mode::API::Request.head('/packages/1')
|
91
|
+
Mode::API::Request.get('/packages/1')
|
92
|
+
Mode::API::Request.post('/packages')
|
93
|
+
Mode::API::Request.put('/packages/1')
|
94
|
+
Mode::API::Request.delete('/packages/1')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'return log error and return raw response on request errors' do
|
98
|
+
response = double(:get, {
|
99
|
+
:status => 500,
|
100
|
+
:success? => false,
|
101
|
+
:body => '{"error": "failed"}'
|
102
|
+
})
|
103
|
+
|
104
|
+
http = double(:http, { :get => response })
|
105
|
+
Mode::API::Request.should_receive(:http).and_return(http)
|
106
|
+
|
107
|
+
request = Mode::API::Request.new(:get, '/packages/1')
|
108
|
+
Mode::Logger.instance.should_receive(:error)
|
109
|
+
request.perform.should == response
|
110
|
+
end
|
111
|
+
end
|