mode-sdk 0.0.1 → 0.1.0

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 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