mode-sdk 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a2fe9f487c0cc04dd360dd4e4439a6d7a9876d95
4
- data.tar.gz: 1d23400061905abb0ecec7a45831e3b39f89a5d6
3
+ metadata.gz: c793c84297832a164a7baa05f0a6d7c46545dbc0
4
+ data.tar.gz: f3b44b33ce27af676e1440ac4fb0ba13e43dabcf
5
5
  SHA512:
6
- metadata.gz: 054cd8e9d9f0c36963b85c7678812d8b60e8baadd8de87e4ccf75b4b5b928e720ea3276ecb1632b3278779852615ef1bf1dbc26035b367310f11604888e02914
7
- data.tar.gz: 2f6481aef7a2385992b57841016d8adab0bf5175f8c786325a2b5977a844ff787121a311d1bc329ac5a5d01f8f7b27e3aa80c56ca4475de09c02011e2061c252
6
+ metadata.gz: 1a55c0fb2ed0f63a454ccb1acb6148f3ae66dd856cb33f4bafc0fd8a709245bc44fb7adcc8d00fbbd60b662392f0349d2190c79122bad9013bedb8df66eccd29
7
+ data.tar.gz: 03d4035e802ea964a4806ed3c15b0419ea7763db6bff24af4d721ffab7c1dd200787c6fa0421600b92d0155a5e12275f6b677f0ae204f96dd418d45020048498
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Mode Ruby SDK
2
2
 
3
+ [![Build Status](https://travis-ci.org/mode/mode-ruby-sdk.svg)](https://travis-ci.org/mode/mode-ruby-sdk)
3
4
  [![Code Climate](https://codeclimate.com/repos/53ea7f57e30ba007c500a24a/badges/44f08215be76ea780d56/gpa.svg)](https://codeclimate.com/repos/53ea7f57e30ba007c500a24a/feed)
5
+ [![Gem Version](https://badge.fury.io/rb/mode-sdk.svg)](http://badge.fury.io/rb/mode-sdk)
4
6
 
5
7
  This SDK provides a wrapper for the [Mode Analytics](https://modeanalytics.com)
6
8
  API.
@@ -94,6 +96,11 @@ your import, including:
94
96
 
95
97
  You can poll this response until your import has completed.
96
98
 
99
+ ## Documentation
100
+
101
+ [http://rubydoc.info/github/mode/mode-ruby-sdk/master/frames]
102
+ (http://rubydoc.info/github/mode/mode-ruby-sdk/master/frames)
103
+
97
104
  ## Examples
98
105
 
99
106
  A collection of examples can be found in the
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mode/sdk'
3
+ require 'optparse'
4
+ require 'readline'
5
+
6
+ options = {}
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] filename"
10
+
11
+ opts.on('-h', '--help', 'Display this help') do
12
+ $stdout.puts opts.to_s
13
+ exit
14
+ end
15
+
16
+ opts.on('-r', '--replace', 'Replace table if it already exists') do |value|
17
+ options[:replace] = value
18
+ end
19
+
20
+ opts.on('-u', '--upload-token=value', 'Upload token') do |value|
21
+ options[:upload_token] = value
22
+ end
23
+
24
+ opts.on('-t', '--table-name=name', 'Table name') do |value|
25
+ options[:table_name] = value
26
+ end
27
+
28
+ opts.on('-f', '--file=value', 'CSV file') do |value|
29
+ options[:file] = value
30
+ end
31
+ end.parse!
32
+
33
+ options[:token] = Mode::Sdk::Cli.env_get('MODE_TOKEN', 'Mode API token')
34
+ options[:secret] = Mode::Sdk::Cli.env_get('MODE_SECRET', 'Mode API secret')
35
+
36
+ options[:file] ||= Readline.readline('Path to CSV file: ', true).strip
37
+
38
+ Mode::Sdk::Cli.new(options).import!
@@ -45,6 +45,12 @@ module Mode
45
45
  account.fetch('username')
46
46
  end
47
47
 
48
+ # @see Mode::Sdk::Client.authenticated?
49
+ #
50
+ def authenticated?
51
+ Mode::Sdk::Client.authenticated?
52
+ end
53
+
48
54
  # Un-memoize everything
49
55
  #
50
56
  def reset
@@ -58,10 +64,12 @@ module Mode
58
64
  end
59
65
  end
60
66
 
67
+ require 'mode/sdk/cli'
61
68
  require 'mode/sdk/client'
62
69
  require 'mode/sdk/column'
63
70
  require 'mode/sdk/column_set'
64
71
  require 'mode/sdk/configuration'
72
+ require 'mode/sdk/csv_file'
65
73
  require 'mode/sdk/hash_util'
66
74
  require 'mode/sdk/table'
67
75
  require 'mode/sdk/table_import'
@@ -0,0 +1,241 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'pp'
4
+
5
+ module Mode
6
+ module Sdk
7
+ # Provides command line interface functionality for importing data
8
+ #
9
+ # @attr_reader [Hash] options hash of options
10
+ #
11
+ class Cli
12
+ attr_reader :options
13
+
14
+ # Construct a new Cli instance
15
+ #
16
+ # @param options [optional, Hash] hash of options
17
+ #
18
+ # @option options [true, false] :replace whether to allow the table to be
19
+ # replaced, if it exists
20
+ # @option options [String] :table_name the name of the table (defaults to
21
+ # normalized file name)
22
+ # @option options [String] :upload_token the token of a previously
23
+ # created Mode upload
24
+ # @option options [String] :token Mode API token
25
+ # @option options [String] :secret Mode API secret
26
+ #
27
+ # @return [Mode::Sdk::Cli] the instance
28
+ #
29
+ def initialize(options = {})
30
+ @options = options
31
+ end
32
+
33
+ # Authenticate and import data
34
+ #
35
+ def import!
36
+ authenticate!
37
+ validate_options!
38
+
39
+ import.poll do |repr|
40
+ handle_import_state(repr)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def path
47
+ options.fetch(:file)
48
+ end
49
+
50
+ # Handle the response from import state polling
51
+ #
52
+ # @param repr [Hash] the API representation of the table import
53
+ #
54
+ def handle_import_state(repr)
55
+ state = repr.fetch('state')
56
+
57
+ case state
58
+ when 'new', 'enqueued', 'running'
59
+ log "Import #{state}..."
60
+ when 'succeeded'
61
+ table_url = repr['_embedded']['table']['_links']['web']['href']
62
+ log "Import #{state}"
63
+ log "Created table #{table_url}"
64
+ else
65
+ log "Import #{state}"
66
+ log repr, true
67
+ end
68
+ end
69
+
70
+ # @return [Mode::Sdk::TableImport] table import instance
71
+ #
72
+ def import
73
+ return @import if defined?(@import)
74
+
75
+ import_path = upsert_table.body['_links']['self'].fetch('href')
76
+
77
+ @import = Mode::Sdk::TableImport.new(import_path)
78
+ end
79
+
80
+ # Create or replace table in Mode warehouse
81
+ #
82
+ # @return [Mode::Sdk::Client::Response] the response from the create or
83
+ # replace request
84
+ #
85
+ def upsert_table
86
+ validate_upsert!
87
+
88
+ if table.exists?
89
+ log "Replacing #{table.full_name}..."
90
+ table.replace
91
+ else
92
+ log "Creating #{table.full_name}..."
93
+ table.create
94
+ end
95
+ end
96
+
97
+ # Ensure table options are compatible with table state
98
+ #
99
+ # @return [true]
100
+ #
101
+ def validate_upsert!
102
+ return true unless table.exists? && !replace_table?
103
+
104
+ error = "Table #{table.full_name} already exists."
105
+ error << 'Use --replace flag to allow replace.'
106
+
107
+ stop! error
108
+ end
109
+
110
+ # Check whether the given table may be replaced according to options
111
+ #
112
+ # @return [true, false]
113
+ #
114
+ def replace_table?
115
+ options.fetch(:replace, false)
116
+ end
117
+
118
+ # Test Mode authentication with the provided credentials
119
+ #
120
+ # @return [true]
121
+ #
122
+ def authenticate!
123
+ @authenticated ||= true.tap do
124
+ configure!
125
+
126
+ unless Mode::Sdk.authenticated?
127
+ stop! 'Authentication failed, please try again'
128
+ end
129
+ end
130
+ end
131
+
132
+ # Ensure provided options are valid
133
+ #
134
+ # @return [true]
135
+ #
136
+ def validate_options!
137
+ stop! 'Please provide path to CSV file' unless path && path.size > 0
138
+
139
+ true
140
+ end
141
+
142
+ # Initialize table instance and assign upload token and column schema
143
+ #
144
+ # @return [Mode::Sdk::Table] the table
145
+ #
146
+ def table
147
+ @table ||= Mode::Sdk::Table.new(table_name).tap do |table|
148
+ table.upload_token = upload_token
149
+ table.columns = columns
150
+ end
151
+ end
152
+
153
+ # Build Mode API-compatible column definitions from CSV header
154
+ #
155
+ # @return [Array<Hash>] array of columns
156
+ #
157
+ # @todo Infer data types from sample values and allow overrides
158
+ #
159
+ def columns
160
+ csv.header.map do |name|
161
+ { name: Mode::Sdk::Column.normalize_name(name),
162
+ type: 'string' }
163
+ end
164
+ end
165
+
166
+ # Name of the table to import
167
+ #
168
+ # @return [String] normalized table name
169
+ #
170
+ def table_name
171
+ @table_name ||= options[:table_name] || begin
172
+ Mode::Sdk::Table.normalize_name(csv.name)
173
+ end
174
+ end
175
+
176
+ # @return [Mode::Sdk::CsvFile] the csv file to import
177
+ #
178
+ def csv
179
+ @csv ||= Mode::Sdk::CsvFile.new(path)
180
+ end
181
+
182
+ # @return [String the token of the upload associated with this import
183
+ #
184
+ def upload_token
185
+ @upload_token ||= options[:upload_token] || begin
186
+ log "Uploading #{path} (#{csv.line_count} lines)..."
187
+
188
+ upload = Mode::Sdk::Upload.new(csv.content)
189
+ upload.create
190
+ upload.token
191
+ end
192
+ end
193
+
194
+ # Configure the Mode SDK with authentication credentials
195
+ #
196
+ def configure!
197
+ Mode::Sdk.configure do |config|
198
+ config.token = options.fetch(:token, nil)
199
+ config.secret = options.fetch(:secret, nil)
200
+ end
201
+ end
202
+
203
+ # Log a message to stdout
204
+ #
205
+ # @param value [Object] the value to print
206
+ # @param pretty [optional, true, false] whether to pretty-print the value
207
+ #
208
+ def log(value, pretty = false)
209
+ $stdout.puts pretty ? PP.pp(value, '') : "=> #{value}"
210
+ end
211
+
212
+ # Print an error message and exit
213
+ #
214
+ # @param error [String] the error message
215
+ #
216
+ # @raise [SystemExit]
217
+ #
218
+ def stop!(error)
219
+ $stderr.puts "=> #{error}"
220
+ exit
221
+ end
222
+
223
+ class << self
224
+ # Get value from environment variable or stdin prompt
225
+ #
226
+ # @param key [String] the name of the environment variable
227
+ # @param prompt [optional, String] prompt to use if environment
228
+ # variable is missing
229
+ #
230
+ # @return [String] the value
231
+ #
232
+ def env_get(key, prompt = nil)
233
+ return ENV[key] if ENV[key]
234
+
235
+ $stdout.print "#{prompt}: " if prompt
236
+ $stdin.gets.chomp
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -73,6 +73,15 @@ module Mode
73
73
  get('/api/account', expect: [200]).body
74
74
  end
75
75
 
76
+ # Test authentication credentials
77
+ #
78
+ # @return [true, false] whether the provided authentication credentials
79
+ # are valid
80
+ #
81
+ def authenticated?
82
+ get('/api/account', expect: [200, 401]).code == 200
83
+ end
84
+
76
85
  private
77
86
 
78
87
  def request(request, body, options = {})
@@ -44,7 +44,6 @@ module Mode
44
44
 
45
45
  private
46
46
 
47
- DEFAULT_HOST = 'https://modeanalytics.com'
48
47
  DEFAULT_CONTENT_TYPE = 'application/json'
49
48
  USER_AGENT = "mode-sdk/#{Mode::Sdk::VERSION}"
50
49
 
@@ -118,7 +117,7 @@ module Mode
118
117
  # @return [String] the API host
119
118
  #
120
119
  def host
121
- ENV['MODE_HOST'] || Mode::Sdk.config.host || DEFAULT_HOST
120
+ Mode::Sdk.config.host
122
121
  end
123
122
 
124
123
  private
@@ -64,7 +64,7 @@ module Mode
64
64
  fail Mode::Sdk::Client::AuthorizationError
65
65
  else
66
66
  fail Mode::Sdk::Client::ResponseError,
67
- "Unexpected response code: #{code}"
67
+ "Unexpected response: #{code}\n#{body}"
68
68
  end
69
69
  end
70
70
 
@@ -10,12 +10,40 @@ module Mode
10
10
  # [http://developer.modeanalytics.com/#page:authentication]
11
11
  # (http://developer.modeanalytics.com/#page:authentication)
12
12
  #
13
- # @attr [String] token Mode API token
14
- # @attr [String] secret Mode API secret
15
- # @attr [String] host Mode API host
13
+ # @attr_writer [String] token Mode API token
14
+ # @attr_writer [String] secret Mode API secret
15
+ # @attr_writer [String] host Mode API host
16
16
  #
17
17
  class Configuration
18
- attr_accessor :token, :secret, :host
18
+ # The default host to use for Mode API requests
19
+ #
20
+ DEFAULT_HOST = 'https://modeanalytics.com'
21
+
22
+ attr_writer :token, :secret, :host
23
+
24
+ # Mode API token
25
+ #
26
+ # @return [String] the token
27
+ #
28
+ def token
29
+ @token || ENV['MODE_TOKEN']
30
+ end
31
+
32
+ # Mode API secret
33
+ #
34
+ # @return [String] the secret
35
+ #
36
+ def secret
37
+ @secret || ENV['MODE_SECRET']
38
+ end
39
+
40
+ # Mode API host
41
+ #
42
+ # @return [String] the host
43
+ #
44
+ def host
45
+ @host || ENV['MODE_HOST'] || DEFAULT_HOST
46
+ end
19
47
  end
20
48
  end
21
49
  end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'csv'
4
+
5
+ module Mode
6
+ module Sdk
7
+ # Represents a local CSV file
8
+ #
9
+ # @attr_reader [String] original_path the path of the file
10
+ #
11
+ class CsvFile
12
+ attr_reader :original_path
13
+
14
+ # Construct a new CsvFile instance
15
+ #
16
+ # @param original_path [String] the original path of the file
17
+ #
18
+ # @return [Mode::Sdk::CsvFile] the instance
19
+ #
20
+ def initialize(original_path)
21
+ @original_path = original_path
22
+ end
23
+
24
+ # The expanded path of the file
25
+ #
26
+ # @return [String] the expanded path
27
+ #
28
+ def path
29
+ @path ||= File.expand_path(original_path)
30
+ end
31
+
32
+ # The base name of the file with CSV extension removed, if present
33
+ #
34
+ # @return [String] the filename
35
+ #
36
+ def name
37
+ File.basename(path, '.csv')
38
+ end
39
+
40
+ # The size of the file
41
+ #
42
+ # @return [Integer] the filesize
43
+ #
44
+ def size
45
+ @size ||= File.size(path)
46
+ end
47
+
48
+ # An array of all lines in the file
49
+ #
50
+ # @return [Array<String>] array of lines
51
+ #
52
+ def lines
53
+ @lines ||= File.open(path, 'r', &:read).split(/[\r\n]+/)
54
+ end
55
+
56
+ # The total number of lines in the file
57
+ #
58
+ # @return [Integer] number of lines
59
+ #
60
+ def line_count
61
+ @line_count ||= lines.size
62
+ end
63
+
64
+ # An array of parsed CSV header column names
65
+ #
66
+ # @return [Array<String>] array of column names
67
+ #
68
+ def header
69
+ @header ||= CSV.parse_line(lines[0])
70
+ end
71
+
72
+ # All lines of the file except the header with standardized LF line
73
+ # breaks
74
+ #
75
+ # @return [String] the file content
76
+ #
77
+ def content
78
+ @content ||= lines[1..-1].join("\n")
79
+ end
80
+ end
81
+ end
82
+ end
@@ -4,6 +4,6 @@ module Mode
4
4
  # Official [Mode Analytics](https://modeanalytics.com) Ruby SDK
5
5
  #
6
6
  module Sdk
7
- VERSION = '0.0.1'
7
+ VERSION = '0.1.0'
8
8
  end
9
9
  end
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(/^(test|spec|features)/)
21
21
  spec.require_paths = ['lib']
22
22
 
23
+ spec.required_ruby_version = '~> 2.0'
24
+
23
25
  spec.add_development_dependency 'bundler', '~> 1.3'
24
26
  spec.add_development_dependency 'rake'
25
27
  spec.add_development_dependency 'rspec'
@@ -0,0 +1,233 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::Cli do
6
+ describe '#import!' do
7
+ let :cli do
8
+ Mode::Sdk::Cli.new
9
+ end
10
+
11
+ it 'produces success output' do
12
+ import = double(:import)
13
+
14
+ success_repr = {
15
+ 'state' => 'succeeded',
16
+ '_embedded' => {
17
+ 'table' => { '_links' => { 'web' => { 'href' => 'http://test' } } }
18
+ }
19
+ }
20
+
21
+ expect(import).to receive(:poll)
22
+ .and_yield('state' => 'enqueued')
23
+ .and_yield(success_repr)
24
+
25
+ expect(cli).to receive(:import).and_return(import)
26
+ expect(cli).to receive(:authenticate!)
27
+ expect(cli).to receive(:validate_options!)
28
+
29
+ lines = [
30
+ '=> Import enqueued...',
31
+ '=> Import succeeded',
32
+ '=> Created table http://test',
33
+ ''
34
+ ]
35
+
36
+ expect { cli.import! }.to output(lines.join("\n")).to_stdout
37
+ end
38
+
39
+ it 'produces failure output' do
40
+ import = double(:import)
41
+
42
+ expect(import).to receive(:poll)
43
+ .and_yield('state' => 'enqueued')
44
+ .and_yield('state' => 'failed')
45
+
46
+ expect(cli).to receive(:import).and_return(import)
47
+ expect(cli).to receive(:authenticate!)
48
+ expect(cli).to receive(:validate_options!)
49
+
50
+ lines = [
51
+ '=> Import enqueued...',
52
+ '=> Import failed',
53
+ { 'state' => 'failed' },
54
+ ''
55
+ ]
56
+
57
+ expect { cli.import! }.to output(lines.join("\n")).to_stdout
58
+ end
59
+ end
60
+
61
+ describe '#validate_options!' do
62
+ it 'returns true if path is present' do
63
+ cli = Mode::Sdk::Cli.new(file: 'filename.csv')
64
+
65
+ expect(cli.send(:validate_options!)).to eq(true)
66
+ end
67
+
68
+ it 'stops if path is blank' do
69
+ cli = Mode::Sdk::Cli.new(file: '')
70
+
71
+ expect {
72
+ silence_stderr do
73
+ cli.send(:validate_options!)
74
+ end
75
+ }.to raise_exception(SystemExit)
76
+ end
77
+ end
78
+
79
+ describe '#table' do
80
+ let :cli do
81
+ Mode::Sdk::Cli.new(file: 'filename.csv')
82
+ end
83
+
84
+ it 'builds table and assigns attributes' do
85
+ expect(cli).to receive(:upload_token).and_return('token')
86
+ expect(cli).to receive(:columns).and_return([{ foo: 'bar' }])
87
+
88
+ table = cli.send(:table)
89
+
90
+ expect(table).to be_an_instance_of(Mode::Sdk::Table)
91
+ expect(table.upload_token).to eq('token')
92
+ expect(table.columns).to eq([{ foo: 'bar' }])
93
+ end
94
+ end
95
+
96
+ describe '#upsert_table' do
97
+ describe 'if table exists' do
98
+ let :table do
99
+ double(:table, exists?: true, full_name: 'full.name')
100
+ end
101
+
102
+ it 'exits without replace flag' do
103
+ cli = Mode::Sdk::Cli.new(replace: false)
104
+ expect(cli).to receive(:table).and_return(table).at_least(:once)
105
+
106
+ expect {
107
+ silence_stderr do
108
+ cli.send(:upsert_table)
109
+ end
110
+ }.to raise_error(SystemExit)
111
+ end
112
+
113
+ it 'replaces with replace flag' do
114
+ cli = Mode::Sdk::Cli.new(replace: true)
115
+ expect(cli).to receive(:table).and_return(table).at_least(:once)
116
+ expect(table).to receive(:replace)
117
+
118
+ expect {
119
+ cli.send(:upsert_table)
120
+ }.to output("=> Replacing full.name...\n").to_stdout
121
+ end
122
+ end
123
+
124
+ describe 'if table does not exist' do
125
+ let :table do
126
+ double(:table, exists?: false, full_name: 'full.name')
127
+ end
128
+
129
+ it 'creates table' do
130
+ cli = Mode::Sdk::Cli.new
131
+ expect(cli).to receive(:table).and_return(table).at_least(:once)
132
+ expect(table).to receive(:create)
133
+
134
+ expect {
135
+ cli.send(:upsert_table)
136
+ }.to output("=> Creating full.name...\n").to_stdout
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#authenticate!' do
142
+ it 'memoizes successful result' do
143
+ cli = Mode::Sdk::Cli.new
144
+
145
+ expect(Mode::Sdk).to receive(:authenticated?).and_return(true).once
146
+
147
+ 3.times do
148
+ expect(cli.send(:authenticate!)).to eq(true)
149
+ end
150
+ end
151
+
152
+ it 'raises exception if unsuccessful' do
153
+ cli = Mode::Sdk::Cli.new
154
+
155
+ expect(Mode::Sdk).to receive(:authenticated?).and_return(false)
156
+
157
+ expect {
158
+ silence_stderr do
159
+ cli.send(:authenticate!)
160
+ end
161
+ }.to raise_error(SystemExit)
162
+ end
163
+ end
164
+
165
+ describe '#columns' do
166
+ it 'forms columns from csv header' do
167
+ cli = Mode::Sdk::Cli.new
168
+
169
+ csv = double(:csv, header: %w(foo bar))
170
+
171
+ expect(cli).to receive(:csv).and_return(csv)
172
+
173
+ columns = [
174
+ { name: 'foo', type: 'string' },
175
+ { name: 'bar', type: 'string' }]
176
+
177
+ expect(cli.send(:columns)).to eq(columns)
178
+ end
179
+ end
180
+
181
+ describe '.env_get' do
182
+ it 'returns value from environment without prompting' do
183
+ expect(ENV).to receive(:[]).with('FOO').and_return('bar').at_least(:once)
184
+
185
+ expect(Mode::Sdk::Cli.env_get('FOO')).to eq('bar')
186
+ end
187
+
188
+ it 'prompts for missing environment variable' do
189
+ expect($stdin).to receive(:gets).and_return("input\n")
190
+
191
+ expect(Mode::Sdk::Cli.env_get('FOO')).to eq('input')
192
+ end
193
+ end
194
+
195
+ describe '#upload_token' do
196
+ it 'returns value from options if provided' do
197
+ cli = Mode::Sdk::Cli.new(upload_token: 'token')
198
+
199
+ expect_any_instance_of(Mode::Sdk::Upload).to receive(:token).never
200
+
201
+ expect(cli.send(:upload_token)).to eq('token')
202
+ end
203
+
204
+ it 'creates upload if necessary' do
205
+ cli = Mode::Sdk::Cli.new(file: 'path')
206
+
207
+ expect_any_instance_of(Mode::Sdk::CsvFile).to receive(:content)
208
+ expect_any_instance_of(Mode::Sdk::CsvFile).to(
209
+ receive(:line_count).and_return(10))
210
+
211
+ expect_any_instance_of(Mode::Sdk::Upload).to receive(:create)
212
+ expect_any_instance_of(Mode::Sdk::Upload).to(
213
+ receive(:token).and_return('token'))
214
+
215
+ lines = ['=> Uploading path (10 lines)...', '']
216
+
217
+ expect { cli.send(:upload_token) }.to output(lines.join("\n")).to_stdout
218
+ end
219
+ end
220
+
221
+ describe '#import' do
222
+ it 'returns table import' do
223
+ cli = Mode::Sdk::Cli.new
224
+
225
+ body = { '_links' => { 'self' => { 'href' => 'path' } } }
226
+ response = double(:response, body: body)
227
+ expect(cli).to receive(:upsert_table).and_return(response)
228
+
229
+ import = cli.send(:import)
230
+ expect(import).to be_an_instance_of(Mode::Sdk::TableImport)
231
+ end
232
+ end
233
+ end
@@ -45,7 +45,7 @@ describe Mode::Sdk::Client::Response do
45
45
  end
46
46
 
47
47
  it 'raises exception if response is unexpected' do
48
- http_response = double(:http_response, code: '200')
48
+ http_response = double(:http_response, code: '200', body: '{}')
49
49
 
50
50
  response = Mode::Sdk::Client::Response.new(http_response, [201])
51
51
 
@@ -68,4 +68,24 @@ describe Mode::Sdk::Client do
68
68
  expect(response).to eq('username' => 'someone')
69
69
  end
70
70
  end
71
+
72
+ describe '.authenticated' do
73
+ it 'returns true if authenticated' do
74
+ mock_response = double(:response, code: 200)
75
+
76
+ expect_any_instance_of(Mode::Sdk::Client::Request).to(
77
+ receive(:response).and_return(mock_response))
78
+
79
+ expect(Mode::Sdk::Client.authenticated?).to eq(true)
80
+ end
81
+
82
+ it 'returns false if not authenticated' do
83
+ mock_response = double(:response, code: 401)
84
+
85
+ expect_any_instance_of(Mode::Sdk::Client::Request).to(
86
+ receive(:response).and_return(mock_response))
87
+
88
+ expect(Mode::Sdk::Client.authenticated?).to eq(false)
89
+ end
90
+ end
71
91
  end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::CsvFile do
6
+ let :file do
7
+ Mode::Sdk::CsvFile.new(test_file_path('population_growth.csv'))
8
+ end
9
+
10
+ describe '#name' do
11
+ it 'returns name without csv extension' do
12
+ expect(file.name).to eq('population_growth')
13
+ end
14
+ end
15
+
16
+ describe '#size' do
17
+ it 'returns size' do
18
+ expect(file.size).to be_an_instance_of(Fixnum)
19
+ end
20
+ end
21
+
22
+ describe '#lines' do
23
+ it 'parses csv' do
24
+ expect(file.lines).to be_an_instance_of(Array)
25
+ expect(file.lines.size).to eq(10)
26
+ end
27
+ end
28
+
29
+ describe '#line_count' do
30
+ it 'returns line count' do
31
+ expect(file.line_count).to eq(10)
32
+ end
33
+ end
34
+
35
+ describe '#header' do
36
+ it 'parses header' do
37
+ expect(file.header).to eq(%w(date value))
38
+ end
39
+ end
40
+
41
+ describe '#content' do
42
+ it 'returns content' do
43
+ expect(file.content).to be_an_instance_of(String)
44
+ expect(file.content.scan(/\n/).count).to eq(8)
45
+ end
46
+ end
47
+ end
@@ -47,4 +47,12 @@ describe Mode::Sdk do
47
47
  expect(Mode::Sdk.username).to eq('someone')
48
48
  end
49
49
  end
50
+
51
+ describe '.authenticated?' do
52
+ it 'returns value from client' do
53
+ expect(Mode::Sdk::Client).to receive(:authenticated?).and_return(true)
54
+
55
+ expect(Mode::Sdk.authenticated?).to eq(true)
56
+ end
57
+ end
50
58
  end
@@ -8,7 +8,12 @@ Bundler.setup
8
8
 
9
9
  require 'mode/sdk'
10
10
 
11
+ Dir['./spec/support/**/*.rb'].each { |file| require file }
12
+
11
13
  RSpec.configure do |config|
14
+ config.include FileHelper
15
+ config.include StreamsHelper
16
+
12
17
  config.before(:all) do
13
18
  Mode::Sdk.configure do |mode_config|
14
19
  mode_config.host = 'http://testhost'
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Spec helpers related to test files
4
+ #
5
+ module FileHelper
6
+ def test_file_path(name)
7
+ File.join(File.dirname(__FILE__), '..', 'support', 'files', name)
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ date,value
2
+ 2012-12-31,0.74379786478725
3
+ 2011-12-31,0.72847474918508
4
+ 2010-12-31,0.82931990108905
5
+ 2009-12-31,0.87665129880291
6
+ 2008-12-31,0.94586528728259
7
+ 2007-12-31,0.95105524277243
8
+ 2006-12-31,0.96425391713608
9
+ 2005-12-31,0.92171316716121
10
+ 2004-12-31,0.92548396894348
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Spec helpers related to standard streams
4
+ #
5
+ module StreamsHelper
6
+ def silence_stderr
7
+ stderr = $stderr
8
+ $stderr = File.new('/dev/null', 'w')
9
+ yield
10
+ ensure
11
+ $stderr = stderr
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mode-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Heather Rivers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-12 00:00:00.000000000 Z
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -69,23 +69,28 @@ dependencies:
69
69
  description: Mode Ruby SDK
70
70
  email:
71
71
  - heather@modeanalytics.com
72
- executables: []
72
+ executables:
73
+ - mode_import
73
74
  extensions: []
74
75
  extra_rdoc_files: []
75
76
  files:
76
77
  - .gitignore
78
+ - .travis.yml
77
79
  - .yardopts
78
80
  - Gemfile
79
81
  - LICENSE.txt
80
82
  - README.md
81
83
  - Rakefile
84
+ - bin/mode_import
82
85
  - lib/mode/sdk.rb
86
+ - lib/mode/sdk/cli.rb
83
87
  - lib/mode/sdk/client.rb
84
88
  - lib/mode/sdk/client/request.rb
85
89
  - lib/mode/sdk/client/response.rb
86
90
  - lib/mode/sdk/column.rb
87
91
  - lib/mode/sdk/column_set.rb
88
92
  - lib/mode/sdk/configuration.rb
93
+ - lib/mode/sdk/csv_file.rb
89
94
  - lib/mode/sdk/hash_util.rb
90
95
  - lib/mode/sdk/table.rb
91
96
  - lib/mode/sdk/table_import.rb
@@ -93,11 +98,13 @@ files:
93
98
  - lib/mode/sdk/version.rb
94
99
  - lib/mode/sdk/warehouse_util.rb
95
100
  - mode-sdk.gemspec
101
+ - spec/lib/mode/sdk/cli_spec.rb
96
102
  - spec/lib/mode/sdk/client/request_spec.rb
97
103
  - spec/lib/mode/sdk/client/response_spec.rb
98
104
  - spec/lib/mode/sdk/client_spec.rb
99
105
  - spec/lib/mode/sdk/column_set_spec.rb
100
106
  - spec/lib/mode/sdk/column_spec.rb
107
+ - spec/lib/mode/sdk/csv_file_spec.rb
101
108
  - spec/lib/mode/sdk/hash_util_spec.rb
102
109
  - spec/lib/mode/sdk/table_import_spec.rb
103
110
  - spec/lib/mode/sdk/table_spec.rb
@@ -105,6 +112,9 @@ files:
105
112
  - spec/lib/mode/sdk/warehouse_util_spec.rb
106
113
  - spec/lib/mode/sdk_spec.rb
107
114
  - spec/spec_helper.rb
115
+ - spec/support/file_helper.rb
116
+ - spec/support/files/population_growth.csv
117
+ - spec/support/streams_helper.rb
108
118
  homepage: https://github.com/mode/mode-ruby-sdk
109
119
  licenses:
110
120
  - MIT
@@ -115,9 +125,9 @@ require_paths:
115
125
  - lib
116
126
  required_ruby_version: !ruby/object:Gem::Requirement
117
127
  requirements:
118
- - - '>='
128
+ - - ~>
119
129
  - !ruby/object:Gem::Version
120
- version: '0'
130
+ version: '2.0'
121
131
  required_rubygems_version: !ruby/object:Gem::Requirement
122
132
  requirements:
123
133
  - - '>='
@@ -125,16 +135,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
135
  version: '0'
126
136
  requirements: []
127
137
  rubyforge_project:
128
- rubygems_version: 2.0.0
138
+ rubygems_version: 2.0.14
129
139
  signing_key:
130
140
  specification_version: 4
131
141
  summary: Ruby SDK for interacting with the Mode Analytics API
132
142
  test_files:
143
+ - spec/lib/mode/sdk/cli_spec.rb
133
144
  - spec/lib/mode/sdk/client/request_spec.rb
134
145
  - spec/lib/mode/sdk/client/response_spec.rb
135
146
  - spec/lib/mode/sdk/client_spec.rb
136
147
  - spec/lib/mode/sdk/column_set_spec.rb
137
148
  - spec/lib/mode/sdk/column_spec.rb
149
+ - spec/lib/mode/sdk/csv_file_spec.rb
138
150
  - spec/lib/mode/sdk/hash_util_spec.rb
139
151
  - spec/lib/mode/sdk/table_import_spec.rb
140
152
  - spec/lib/mode/sdk/table_spec.rb
@@ -142,4 +154,6 @@ test_files:
142
154
  - spec/lib/mode/sdk/warehouse_util_spec.rb
143
155
  - spec/lib/mode/sdk_spec.rb
144
156
  - spec/spec_helper.rb
145
- has_rdoc:
157
+ - spec/support/file_helper.rb
158
+ - spec/support/files/population_growth.csv
159
+ - spec/support/streams_helper.rb