td-client 0.9.0dev2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/td/client.rb +16 -8
  3. data/lib/td/client/api.rb +66 -47
  4. data/lib/td/client/api/bulk_import.rb +1 -2
  5. data/lib/td/client/api/bulk_load.rb +3 -3
  6. data/lib/td/client/api/export.rb +12 -0
  7. data/lib/td/client/api/import.rb +3 -2
  8. data/lib/td/client/api/job.rb +146 -71
  9. data/lib/td/client/api/schedule.rb +1 -1
  10. data/lib/td/client/api_error.rb +5 -0
  11. data/lib/td/client/model.rb +92 -28
  12. data/lib/td/client/version.rb +1 -1
  13. data/spec/spec_helper.rb +5 -5
  14. data/spec/td/client/account_api_spec.rb +5 -5
  15. data/spec/td/client/api_error_spec.rb +77 -0
  16. data/spec/td/client/api_spec.rb +76 -52
  17. data/spec/td/client/api_ssl_connection_spec.rb +1 -1
  18. data/spec/td/client/bulk_import_spec.rb +28 -29
  19. data/spec/td/client/bulk_load_spec.rb +60 -35
  20. data/spec/td/client/db_api_spec.rb +1 -1
  21. data/spec/td/client/export_api_spec.rb +11 -1
  22. data/spec/td/client/import_api_spec.rb +85 -10
  23. data/spec/td/client/job_api_spec.rb +568 -61
  24. data/spec/td/client/model_job_spec.rb +27 -10
  25. data/spec/td/client/model_schedule_spec.rb +2 -2
  26. data/spec/td/client/model_schema_spec.rb +134 -0
  27. data/spec/td/client/partial_delete_api_spec.rb +1 -1
  28. data/spec/td/client/result_api_spec.rb +3 -3
  29. data/spec/td/client/sched_api_spec.rb +12 -4
  30. data/spec/td/client/server_status_api_spec.rb +2 -2
  31. data/spec/td/client/spec_resources.rb +1 -0
  32. data/spec/td/client/table_api_spec.rb +14 -14
  33. data/spec/td/client/user_api_spec.rb +12 -12
  34. data/spec/td/client_sched_spec.rb +31 -6
  35. data/spec/td/client_spec.rb +1 -0
  36. metadata +42 -81
@@ -8,24 +8,47 @@ describe 'Import API' do
8
8
  include_context 'common helper'
9
9
 
10
10
  let :api do
11
- API.new(nil, :endpoint => 'https://api.treasuredata.com')
11
+ API.new(nil, :endpoint => endpoint)
12
12
  end
13
13
 
14
14
  let :api_old do
15
- API.new(nil, :endpoint => 'http://api.treasure-data.com')
15
+ API.new(nil, :endpoint => endpoint_old)
16
16
  end
17
17
 
18
+ let :api_default do
19
+ API.new(nil)
20
+ end
21
+
22
+ let :api_default_http do
23
+ API.new(nil, :ssl => false)
24
+ end
25
+
26
+ let :api_unknown_host do
27
+ API.new(nil, :endpoint => endpoint_unknown)
28
+ end
29
+
30
+ let :api_unknown_host_http do
31
+ API.new(nil, :endpoint => endpoint_unknown, :ssl => false)
32
+ end
33
+
34
+ let(:endpoint) { 'api.treasuredata.com' }
35
+ let(:endpoint_old) { TreasureData::API::OLD_ENDPOINT }
36
+ let(:endpoint_unknown) { "example.com" }
37
+ let(:endpoint_import) { "api-import.treasuredata.com" }
38
+ let(:endpoint_import_old) { "api-import.treasure-data.com" }
39
+ let(:endpoint_import_unknown) { endpoint_unknown }
40
+
18
41
  describe 'import' do
19
42
  it 'runs with unique_id' do
20
43
  t = Tempfile.new('import_api_spec')
21
44
  File.open(t.path, 'w') do |f|
22
45
  f << '12345'
23
46
  end
24
- stub_request(:put, "https://api-import.treasuredata.com/v3/table/import_with_id/db/table/unique_id/format").
47
+ stub_request(:put, "https://#{endpoint_import}/v3/table/import_with_id/db/table/unique_id/format").
25
48
  with(:body => '12345').
26
49
  to_return(:body => '{"elapsed_time":"1.23"}')
27
50
  File.open(t.path) do |f|
28
- api.import('db', 'table', 'format', f, 5, 'unique_id').should == 1.23
51
+ expect(api.import('db', 'table', 'format', f, 5, 'unique_id')).to eq(1.23)
29
52
  end
30
53
  end
31
54
 
@@ -34,24 +57,76 @@ describe 'Import API' do
34
57
  File.open(t.path, 'w') do |f|
35
58
  f << '12345'
36
59
  end
37
- stub_request(:put, "https://api-import.treasuredata.com/v3/table/import/db/table/format").
60
+ stub_request(:put, "https://#{endpoint_import}/v3/table/import/db/table/format").
61
+ with(:body => '12345').
62
+ to_return(:body => '{"elapsed_time":"1.23"}')
63
+ File.open(t.path) do |f|
64
+ expect(api.import('db', 'table', 'format', f, 5)).to eq(1.23)
65
+ end
66
+ end
67
+
68
+ it 'runs for old endpoint (force "http" instead of "https" for compatibility)' do
69
+ t = Tempfile.new('import_api_spec')
70
+ File.open(t.path, 'w') do |f|
71
+ f << '12345'
72
+ end
73
+ stub_request(:put, "http://#{endpoint_import_old}/v3/table/import/db/table/format").
74
+ with(:body => '12345').
75
+ to_return(:body => '{"elapsed_time":"1.23"}')
76
+ File.open(t.path) do |f|
77
+ expect(api_old.import('db', 'table', 'format', f, 5)).to eq(1.23)
78
+ end
79
+ end
80
+
81
+ it 'runs for no endpoint specified (default behavior)' do
82
+ t = Tempfile.new('import_api_spec')
83
+ File.open(t.path, 'w') do |f|
84
+ f << '12345'
85
+ end
86
+ stub_request(:put, "https://#{endpoint_import}/v3/table/import/db/table/format").
87
+ with(:body => '12345').
88
+ to_return(:body => '{"elapsed_time":"1.23"}')
89
+ File.open(t.path) do |f|
90
+ expect(api_default.import('db', 'table', 'format', f, 5)).to eq 1.23
91
+ end
92
+ end
93
+
94
+ it 'runs for no endpoint specified with ssl: false' do
95
+ t = Tempfile.new('import_api_spec')
96
+ File.open(t.path, 'w') do |f|
97
+ f << '12345'
98
+ end
99
+ stub_request(:put, "http://#{endpoint_import}/v3/table/import/db/table/format").
100
+ with(:body => '12345').
101
+ to_return(:body => '{"elapsed_time":"1.23"}')
102
+ File.open(t.path) do |f|
103
+ expect(api_default_http.import('db', 'table', 'format', f, 5)).to eq 1.23
104
+ end
105
+ end
106
+
107
+ it 'runs for unknown endpoint specified' do
108
+ t = Tempfile.new('import_api_spec')
109
+ File.open(t.path, 'w') do |f|
110
+ f << '12345'
111
+ end
112
+ stub_request(:put, "https://#{endpoint_unknown}/v3/table/import/db/table/format").
38
113
  with(:body => '12345').
39
114
  to_return(:body => '{"elapsed_time":"1.23"}')
40
115
  File.open(t.path) do |f|
41
- api.import('db', 'table', 'format', f, 5).should == 1.23
116
+ expect(api_unknown_host.import('db', 'table', 'format', f, 5)).to eq 1.23
42
117
  end
43
118
  end
44
119
 
45
- it 'runs for old endpoint' do
120
+ it 'runs for unknown endpoint with ssl=false specified' do
46
121
  t = Tempfile.new('import_api_spec')
47
122
  File.open(t.path, 'w') do |f|
48
123
  f << '12345'
49
124
  end
50
- stub_request(:put, "http://api-import.treasure-data.com/v3/table/import/db/table/format").
125
+ stub_request(:put, "http://#{endpoint_unknown}/v3/table/import/db/table/format").
51
126
  with(:body => '12345').
52
127
  to_return(:body => '{"elapsed_time":"1.23"}')
53
128
  File.open(t.path) do |f|
54
- api_old.import('db', 'table', 'format', f, 5).should == 1.23
129
+ expect(api_unknown_host_http.import('db', 'table', 'format', f, 5)).to eq 1.23
55
130
  end
56
131
  end
57
132
 
@@ -60,7 +135,7 @@ describe 'Import API' do
60
135
  File.open(t.path, 'w') do |f|
61
136
  f << '12345'
62
137
  end
63
- stub_request(:put, "https://api-import.treasuredata.com/v3/table/import/db/table/format").
138
+ stub_request(:put, "https://#{endpoint_import}/v3/table/import/db/table/format").
64
139
  with(:body => '12345').
65
140
  to_return(:status => 500)
66
141
  File.open(t.path) do |f|
@@ -20,7 +20,7 @@ describe 'Job API' do
20
20
  it 'should returns 20 jobs by default' do
21
21
  stub_api_request(:get, "/v3/job/list", :query => {'from' => '0'}).to_return(:body => {'jobs' => raw_jobs}.to_json)
22
22
  jobs = api.list_jobs
23
- jobs.size.should == 20
23
+ expect(jobs.size).to eq(20)
24
24
  end
25
25
 
26
26
  (0...MAX_JOB).each {|i|
@@ -31,21 +31,22 @@ describe 'Job API' do
31
31
  jobs = api.list_jobs
32
32
  jobs[i..i].map {|job_id, type, status, query, start_at, end_at, cpu_time,
33
33
  result_size, result_url, priority, retry_limit, org, db,
34
- duration|
35
- job_id.should == job['job_id']
36
- type.should == job['type']
37
- status.should == job['status']
38
- query.should == job['query']
39
- start_at.should == job['start_at']
40
- end_at.should == job['end_at']
41
- cpu_time.should == job['cpu_time']
42
- result_size.should == job['result_size']
43
- result_url.should == job['result_url']
44
- priority.should == job['priority']
45
- retry_limit.should == job['retry_limit']
46
- org.should == job['organization']
47
- db.should == job['database']
48
- duration.should == job['duration']
34
+ duration, num_records|
35
+ expect(job_id).to eq(job['job_id'])
36
+ expect(type).to eq(job['type'])
37
+ expect(status).to eq(job['status'])
38
+ expect(query).to eq(job['query'])
39
+ expect(start_at).to eq(job['start_at'])
40
+ expect(end_at).to eq(job['end_at'])
41
+ expect(cpu_time).to eq(job['cpu_time'])
42
+ expect(result_size).to eq(job['result_size'])
43
+ expect(result_url).to eq(job['result_url'])
44
+ expect(priority).to eq(job['priority'])
45
+ expect(retry_limit).to eq(job['retry_limit'])
46
+ expect(org).to eq(job['organization'])
47
+ expect(db).to eq(job['database'])
48
+ expect(duration).to eq(job['duration'])
49
+ expect(num_records).to eq(job['num_records'])
49
50
  }
50
51
  end
51
52
  }
@@ -53,14 +54,14 @@ describe 'Job API' do
53
54
  it 'should returns 10 jobs with to parameter' do
54
55
  stub_api_request(:get, "/v3/job/list", :query => {'from' => '0', 'to' => '10'}).to_return(:body => {'jobs' => raw_jobs[0...10]}.to_json)
55
56
  jobs = api.list_jobs(0, 10)
56
- jobs.size.should == 10
57
+ expect(jobs.size).to eq(10)
57
58
  end
58
59
 
59
60
  it 'should returns 10 jobs with to status parameter' do
60
61
  error_jobs = raw_jobs.select { |j| j['status'] == 'error' }
61
62
  stub_api_request(:get, "/v3/job/list", :query => {'from' => '0', 'status' => 'error'}).to_return(:body => {'jobs' => error_jobs}.to_json)
62
63
  jobs = api.list_jobs(0, nil, 'error')
63
- jobs.size.should == error_jobs.size
64
+ expect(jobs.size).to eq(error_jobs.size)
64
65
  end
65
66
 
66
67
  #it 'should contain the result_size field' do
@@ -74,22 +75,24 @@ describe 'Job API' do
74
75
  stub_api_request(:get, "/v3/job/show/#{e(i)}").to_return(:body => job.to_json)
75
76
 
76
77
  type, query, status, url, debug, start_at, end_at, cpu_time,
77
- result_size, result_url, hive_result_schema, priority, retry_limit, org, db = api.show_job(i)
78
- type.should == job['type']
79
- query.should == job['query']
80
- status.should == job['status']
81
- url.should == job['url']
82
- debug.should == job['debug']
83
- start_at.should == job['start_at']
84
- end_at.should == job['end_at']
85
- cpu_time.should == job['cpu_time']
86
- result_size.should == job['result_size']
87
- result_url.should == job['result_url']
88
- hive_result_schema.should == job['hive_result_schema']
89
- result_url.should == job['result_url']
90
- priority.should == job['priority']
91
- org.should == job['organization']
92
- db.should == job['database']
78
+ result_size, result_url, hive_result_schema, priority, retry_limit, org, db, duration, num_records = api.show_job(i)
79
+ expect(type).to eq(job['type'])
80
+ expect(query).to eq(job['query'])
81
+ expect(status).to eq(job['status'])
82
+ expect(url).to eq(job['url'])
83
+ expect(debug).to eq(job['debug'])
84
+ expect(start_at).to eq(job['start_at'])
85
+ expect(end_at).to eq(job['end_at'])
86
+ expect(cpu_time).to eq(job['cpu_time'])
87
+ expect(result_size).to eq(job['result_size'])
88
+ expect(result_url).to eq(job['result_url'])
89
+ expect(hive_result_schema).to eq(job['hive_result_schema'])
90
+ expect(result_url).to eq(job['result_url'])
91
+ expect(priority).to eq(job['priority'])
92
+ expect(org).to eq(job['organization'])
93
+ expect(db).to eq(job['database'])
94
+ expect(duration).to eq(job['duration'])
95
+ expect(num_records).to eq(job['num_records'])
93
96
  end
94
97
  }
95
98
 
@@ -129,7 +132,7 @@ describe 'Job API' do
129
132
  stub_api_request(:get, "/v3/job/status/#{e(job_id)}").to_return(:body => result_job.to_json)
130
133
 
131
134
  status = api.job_status(job_id)
132
- status.should == (i.odd? ? 'success' : 'error')
135
+ expect(status).to eq(i.odd? ? 'success' : 'error')
133
136
  end
134
137
  }
135
138
  end
@@ -144,7 +147,7 @@ describe 'Job API' do
144
147
  stub_api_request(:post, "/v3/job/issue/hive/#{e(db_name)}").with(:body => params).to_return(return_body)
145
148
 
146
149
  job_id = api.hive_query(query, db_name)
147
- job_id.should == '1'
150
+ expect(job_id).to eq('1')
148
151
  end
149
152
 
150
153
  it 'issue a query with result_url' do
@@ -152,7 +155,7 @@ describe 'Job API' do
152
155
  stub_api_request(:post, "/v3/job/issue/hive/#{e(db_name)}").with(:body => params).to_return(return_body)
153
156
 
154
157
  job_id = api.hive_query(query, db_name, 'td://@/test/table')
155
- job_id.should == '1'
158
+ expect(job_id).to eq('1')
156
159
  end
157
160
 
158
161
  it 'issue a query with priority' do
@@ -160,13 +163,13 @@ describe 'Job API' do
160
163
  stub_api_request(:post, "/v3/job/issue/hive/#{e(db_name)}").with(:body => params).to_return(return_body)
161
164
 
162
165
  job_id = api.hive_query(query, db_name, nil, 1)
163
- job_id.should == '1'
166
+ expect(job_id).to eq('1')
164
167
  end
165
168
  end
166
169
 
167
170
  describe 'job_result' do
168
171
  let :packed do
169
- s = StringIO.new
172
+ s = StringIO.new(String.new)
170
173
  pk = MessagePack::Packer.new(s)
171
174
  pk.write('hello')
172
175
  pk.write('world')
@@ -178,13 +181,167 @@ describe 'Job API' do
178
181
  stub_api_request(:get, '/v3/job/result/12345').
179
182
  with(:query => {'format' => 'msgpack'}).
180
183
  to_return(:body => packed)
181
- api.job_result(12345).should == ['hello', 'world']
184
+ expect(api.job_result(12345)).to eq(['hello', 'world'])
182
185
  end
186
+
187
+ it '200->200 cannot resume' do
188
+ sz = packed.bytesize / 3
189
+ stub_api_request(:get, '/v3/job/result/12345').
190
+ with(:query => {'format' => 'msgpack'}).
191
+ to_return(
192
+ :headers => {
193
+ 'Content-Length' => packed.bytesize,
194
+ 'Etag' => '"abcdefghijklmn"',
195
+ },
196
+ :body => packed[0, sz]
197
+ )
198
+ stub_api_request(:get, '/v3/job/result/12345').
199
+ with(
200
+ :headers => {
201
+ 'If-Range' => '"abcdefghijklmn"',
202
+ 'Range' => "bytes=#{sz}-",
203
+ },
204
+ :query => {'format' => 'msgpack'}
205
+ ).
206
+ to_return(
207
+ :status => 200,
208
+ :headers => {
209
+ 'Content-Length' => packed.bytesize - sz,
210
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
211
+ 'Etag' => '"abcdefghijklmn"',
212
+ },
213
+ :body => packed
214
+ )
215
+ expect(api).to receive(:sleep).once
216
+ expect($stderr).to receive(:print)
217
+ expect($stderr).to receive(:puts)
218
+ expect{api.job_result(12345)}.to raise_error(TreasureData::APIError)
219
+ end
220
+
221
+ it '200->403 cannot resume' do
222
+ sz = packed.bytesize / 3
223
+ stub_api_request(:get, '/v3/job/result/12345').
224
+ with(:query => {'format' => 'msgpack'}).
225
+ to_return(
226
+ :headers => {
227
+ 'Content-Length' => packed.bytesize,
228
+ 'Etag' => '"abcdefghijklmn"',
229
+ },
230
+ :body => packed[0, sz]
231
+ )
232
+ stub_api_request(:get, '/v3/job/result/12345').
233
+ with(
234
+ :headers => {
235
+ 'If-Range' => '"abcdefghijklmn"',
236
+ 'Range' => "bytes=#{sz}-",
237
+ },
238
+ :query => {'format' => 'msgpack'}
239
+ ).
240
+ to_return(
241
+ :status => 403,
242
+ :headers => {
243
+ 'Content-Length' => packed.bytesize - sz,
244
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
245
+ 'Etag' => '"abcdefghijklmn"',
246
+ },
247
+ :body => packed
248
+ )
249
+ expect(api).to receive(:sleep).once
250
+ expect($stderr).to receive(:print)
251
+ expect($stderr).to receive(:puts)
252
+ expect{api.job_result(12345)}.to raise_error(TreasureData::APIError)
253
+ end
254
+
255
+ it 'can resume' do
256
+ sz = packed.bytesize / 3
257
+ stub_api_request(:get, '/v3/job/result/12345').
258
+ with(:query => {'format' => 'msgpack'}).
259
+ to_return(
260
+ :headers => {
261
+ 'Content-Length' => packed.bytesize,
262
+ 'Etag' => '"abcdefghijklmn"',
263
+ },
264
+ :body => packed[0, sz]
265
+ )
266
+ stub_api_request(:get, '/v3/job/result/12345').
267
+ with(
268
+ :headers => {
269
+ 'If-Range' => '"abcdefghijklmn"',
270
+ 'Range' => "bytes=#{sz}-",
271
+ },
272
+ :query => {'format' => 'msgpack'}
273
+ ).
274
+ to_return(
275
+ :status => 206,
276
+ :headers => {
277
+ 'Content-Length' => packed.bytesize - sz,
278
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
279
+ 'Etag' => '"abcdefghijklmn"',
280
+ },
281
+ :body => packed[sz, packed.bytesize - sz]
282
+ )
283
+ expect(api).to receive(:sleep).once
284
+ expect($stderr).to receive(:print)
285
+ expect($stderr).to receive(:puts)
286
+ expect(api.job_result(12345)).to eq ['hello', 'world']
287
+ end
288
+
289
+ it '200->500->206 can resume' do
290
+ sz = packed.bytesize / 3
291
+ stub_api_request(:get, '/v3/job/result/12345').
292
+ with(:query => {'format' => 'msgpack'}).
293
+ to_return(
294
+ :headers => {
295
+ 'Content-Length' => packed.bytesize,
296
+ 'Etag' => '"abcdefghijklmn"',
297
+ },
298
+ :body => packed[0, sz]
299
+ )
300
+ stub_api_request(:get, '/v3/job/result/12345').
301
+ with(
302
+ :headers => {
303
+ 'If-Range' => '"abcdefghijklmn"',
304
+ 'Range' => "bytes=#{sz}-",
305
+ },
306
+ :query => {'format' => 'msgpack'}
307
+ ).
308
+ to_return(
309
+ :status => 500,
310
+ :headers => {
311
+ 'Content-Length' => packed.bytesize - sz,
312
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
313
+ 'Etag' => '"abcdefghijklmn"',
314
+ },
315
+ :body => packed
316
+ )
317
+ stub_api_request(:get, '/v3/job/result/12345').
318
+ with(
319
+ :headers => {
320
+ 'If-Range' => '"abcdefghijklmn"',
321
+ 'Range' => "bytes=#{sz}-",
322
+ },
323
+ :query => {'format' => 'msgpack'}
324
+ ).
325
+ to_return(
326
+ :status => 206,
327
+ :headers => {
328
+ 'Content-Length' => packed.bytesize - sz,
329
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
330
+ 'Etag' => '"abcdefghijklmn"',
331
+ },
332
+ :body => packed[sz, packed.bytesize - sz]
333
+ )
334
+ expect(api).to receive(:sleep).once
335
+ expect($stderr).to receive(:print)
336
+ expect($stderr).to receive(:puts)
337
+ expect(api.job_result(12345)).to eq ['hello', 'world']
338
+ end
339
+
183
340
  end
184
341
 
185
342
  describe 'job_result_format' do
186
343
  let :packed do
187
- s = StringIO.new
344
+ s = StringIO.new(String.new)
188
345
  Zlib::GzipWriter.wrap(s) do |f|
189
346
  f << ['hello', 'world'].to_json
190
347
  end
@@ -203,8 +360,8 @@ describe 'Job API' do
203
360
  total_size = 0
204
361
  api.job_result_format(12345, 'json', io) {|size| total_size += size }
205
362
 
206
- io.string.should == json
207
- total_size.should == json.size
363
+ expect(io.string).to eq(json)
364
+ expect(total_size).to eq(json.size)
208
365
  end
209
366
  end
210
367
 
@@ -216,7 +373,7 @@ describe 'Job API' do
216
373
  :headers => {'Content-Encoding' => 'gzip'},
217
374
  :body => packed
218
375
  )
219
- api.job_result_format(12345, 'json').should == ['hello', 'world'].to_json
376
+ expect(api.job_result_format(12345, 'json')).to eq(['hello', 'world'].to_json)
220
377
  end
221
378
 
222
379
  it 'writes formatted job result' do
@@ -228,24 +385,66 @@ describe 'Job API' do
228
385
  )
229
386
  s = StringIO.new
230
387
  api.job_result_format(12345, 'json', s)
231
- s.string.should == ['hello', 'world'].to_json
388
+ expect(s.string).to eq(['hello', 'world'].to_json)
389
+ end
390
+
391
+ context 'can resume' do
392
+ before do
393
+ sz = packed.bytesize / 3
394
+ stub_api_request(:get, '/v3/job/result/12345').
395
+ with(:query => {'format' => 'json'}).
396
+ to_return(
397
+ :headers => {
398
+ 'Content-Encoding' => 'gzip',
399
+ 'Content-Length' => packed.bytesize,
400
+ 'Etag' => '"abcdefghijklmn"',
401
+ },
402
+ :body => packed[0, sz]
403
+ )
404
+ stub_api_request(:get, '/v3/job/result/12345').
405
+ with(
406
+ :headers => {
407
+ 'If-Range' => '"abcdefghijklmn"',
408
+ 'Range' => "bytes=#{sz}-",
409
+ },
410
+ :query => {'format' => 'json'}
411
+ ).
412
+ to_return(
413
+ :status => 206,
414
+ :headers => {
415
+ 'Content-Encoding' => 'gzip',
416
+ 'Content-Length' => packed.bytesize-sz,
417
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
418
+ 'Etag' => '"abcdefghijklmn"',
419
+ },
420
+ :body => packed[sz, packed.bytesize-sz]
421
+ )
422
+ expect(api).to receive(:sleep).once
423
+ expect($stderr).to receive(:print)
424
+ expect($stderr).to receive(:puts)
425
+ end
426
+ it 'can work with io' do
427
+ s = StringIO.new
428
+ api.job_result_format(12345, 'json', s)
429
+ expect(s.string).to eq ['hello', 'world'].to_json
430
+ end
431
+ it 'can work without block' do
432
+ expect(api.job_result_format(12345, 'json')).to eq ['hello', 'world'].to_json
433
+ end
232
434
  end
233
435
  end
234
436
  end
235
437
 
236
438
  describe 'job_result_each' do
237
439
  let :packed do
238
- s = StringIO.new
239
- pk = MessagePack::Packer.new(s)
240
- pk.write('hello')
241
- pk.write('world')
242
- pk.flush
243
- s.rewind
244
- out = StringIO.new
245
- Zlib::GzipWriter.wrap(out) do |f|
246
- f.write s.read
440
+ s = StringIO.new(String.new)
441
+ Zlib::GzipWriter.wrap(s) do |f|
442
+ pk = MessagePack::Packer.new(f)
443
+ pk.write('hello')
444
+ pk.write('world')
445
+ pk.flush
247
446
  end
248
- out.string
447
+ s.string
249
448
  end
250
449
 
251
450
  it 'yields job result for each row' do
@@ -259,7 +458,46 @@ describe 'Job API' do
259
458
  api.job_result_each(12345) do |row|
260
459
  result << row
261
460
  end
262
- result.should == ['hello', 'world']
461
+ expect(result).to eq(['hello', 'world'])
462
+ end
463
+
464
+ it 'can resume' do
465
+ sz= packed.bytesize / 3
466
+ stub_api_request(:get, '/v3/job/result/12345').
467
+ with(:query => {'format' => 'msgpack'}).
468
+ to_return(
469
+ :headers => {
470
+ 'Content-Encoding' => 'gzip',
471
+ 'Content-Length' => packed.bytesize,
472
+ 'Etag' => '"abcdefghijklmn"',
473
+ },
474
+ :body => packed[0, sz]
475
+ )
476
+ stub_api_request(:get, '/v3/job/result/12345').
477
+ with(
478
+ :headers => {
479
+ 'If-Range' => '"abcdefghijklmn"',
480
+ 'Range' => "bytes=#{sz}-",
481
+ },
482
+ :query => {'format' => 'msgpack'}
483
+ ).
484
+ to_return(
485
+ :status => 206,
486
+ :headers => {
487
+ 'Content-Length' => packed.bytesize-sz,
488
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
489
+ 'Etag' => '"abcdefghijklmn"',
490
+ },
491
+ :body => packed[sz, packed.bytesize-sz]
492
+ )
493
+ expect(api).to receive(:sleep).once
494
+ expect($stderr).to receive(:print)
495
+ expect($stderr).to receive(:puts)
496
+ result = []
497
+ api.job_result_each(12345) do |row|
498
+ result << row
499
+ end
500
+ expect(result).to eq ['hello', 'world']
263
501
  end
264
502
  end
265
503
 
@@ -274,7 +512,7 @@ describe 'Job API' do
274
512
  # pk.flush
275
513
  # end
276
514
  # s.string
277
- "\u001F\x8B\b\u0000#\xA1\x93T\u0000\u0003[\x9A\x91\x9A\x93\x93\xBF\xB4<\xBF('\u0005\u0000e 0\xB3\f\u0000\u0000\u0000"
515
+ "\x1F\x8B\b\x00#\xA1\x93T\x00\x03[\x9A\x91\x9A\x93\x93\xBF\xB4<\xBF('\x05\x00e 0\xB3\f\x00\x00\x00".force_encoding(Encoding::ASCII_8BIT)
278
516
  end
279
517
 
280
518
  it 'yields job result for each row with progress' do
@@ -288,7 +526,46 @@ describe 'Job API' do
288
526
  api.job_result_each_with_compr_size(12345) do |row, size|
289
527
  result << [row, size]
290
528
  end
291
- result.should == [['hello', 32], ['world', 32]]
529
+ expect(result).to eq([['hello', 32], ['world', 32]])
530
+ end
531
+
532
+ it 'can resume' do
533
+ sz = packed.bytesize / 3
534
+ stub_api_request(:get, '/v3/job/result/12345').
535
+ with(:query => {'format' => 'msgpack'}).
536
+ to_return(
537
+ :headers => {
538
+ 'Content-Encoding' => 'gzip',
539
+ 'Content-Length' => packed.bytesize,
540
+ 'Etag' => '"abcdefghijklmn"',
541
+ },
542
+ :body => packed[0, sz]
543
+ )
544
+ stub_api_request(:get, '/v3/job/result/12345').
545
+ with(
546
+ :headers => {
547
+ 'If-Range' => '"abcdefghijklmn"',
548
+ 'Range' => "bytes=#{sz}-",
549
+ },
550
+ :query => {'format' => 'msgpack'}
551
+ ).
552
+ to_return(
553
+ :status => 206,
554
+ :headers => {
555
+ 'Content-Length' => packed.bytesize - sz,
556
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
557
+ 'Etag' => '"abcdefghijklmn"',
558
+ },
559
+ :body => packed[sz, packed.bytesize - sz]
560
+ )
561
+ expect(api).to receive(:sleep).once
562
+ expect($stderr).to receive(:print)
563
+ expect($stderr).to receive(:puts)
564
+ result = []
565
+ api.job_result_each_with_compr_size(12345) do |row, size|
566
+ result << [row, size]
567
+ end
568
+ expect(result).to eq [['hello', 32], ['world', 32]]
292
569
  end
293
570
  end
294
571
 
@@ -302,7 +579,7 @@ describe 'Job API' do
302
579
  to_return(:body => 'raw binary')
303
580
  api.job_result_raw(12345, 'json', io)
304
581
 
305
- io.string.should == 'raw binary'
582
+ expect(io.string).to eq('raw binary')
306
583
  end
307
584
  end
308
585
 
@@ -311,16 +588,246 @@ describe 'Job API' do
311
588
  stub_api_request(:get, '/v3/job/result/12345').
312
589
  with(:query => {'format' => 'json'}).
313
590
  to_return(:body => 'raw binary')
314
- api.job_result_raw(12345, 'json').should == 'raw binary'
591
+ expect(api.job_result_raw(12345, 'json')).to eq('raw binary')
315
592
  end
316
593
  end
594
+
595
+ let :packed do
596
+ # Hard code fixture data to make the size stable
597
+ # s = StringIO.new
598
+ # Zlib::GzipWriter.wrap(s) do |f|
599
+ # pk = MessagePack::Packer.new(f)
600
+ # pk.write('hello')
601
+ # pk.write('world')
602
+ # pk.flush
603
+ # end
604
+ # s.string
605
+ "\x1F\x8B\b\x00#\xA1\x93T\x00\x03[\x9A\x91\x9A\x93\x93\xBF\xB4<\xBF('\x05\x00e 0\xB3\f\x00\x00\x00".force_encoding(Encoding::ASCII_8BIT)
606
+ end
607
+
608
+ it 'can resume' do
609
+ sz = packed.bytesize / 3
610
+ stub_api_request(:get, '/v3/job/result/12345').
611
+ with(:query => {'format' => 'msgpack.gz'}).
612
+ to_return(
613
+ :headers => {
614
+ 'Content-Length' => packed.bytesize,
615
+ 'Etag' => '"abcdefghijklmn"',
616
+ },
617
+ :body => packed[0, sz]
618
+ )
619
+ stub_api_request(:get, '/v3/job/result/12345').
620
+ with(
621
+ :headers => {
622
+ 'If-Range' => '"abcdefghijklmn"',
623
+ 'Range' => "bytes=#{sz}-",
624
+ },
625
+ :query => {'format' => 'msgpack.gz'}
626
+ ).
627
+ to_return(
628
+ :status => 206,
629
+ :headers => {
630
+ 'Content-Length' => packed.bytesize - sz,
631
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
632
+ 'Etag' => '"abcdefghijklmn"',
633
+ },
634
+ :body => packed[sz, packed.bytesize - sz]
635
+ )
636
+ expect(api).to receive(:sleep).once
637
+ expect($stderr).to receive(:print)
638
+ expect($stderr).to receive(:puts)
639
+ sio = StringIO.new(String.new)
640
+ api.job_result_raw(12345, 'msgpack.gz', sio)
641
+ expect(sio.string).to eq(packed)
642
+ end
317
643
  end
318
644
 
319
645
  describe 'kill' do
320
646
  it 'kills a job' do
321
647
  stub_api_request(:post, '/v3/job/kill/12345').
322
648
  to_return(:body => {'former_status' => 'status'}.to_json)
323
- api.kill(12345).should == 'status'
649
+ expect(api.kill(12345)).to eq('status')
650
+ end
651
+ end
652
+
653
+ describe 'job_result_download' do
654
+ let (:data){ [[1, 'hello', nil], [2, 'world', true], [3, '!', false]] }
655
+ let :formatted do
656
+ case format
657
+ when 'json'
658
+ data.map{|a| JSON(a) }.join("\n")
659
+ when 'msgpack'
660
+ pk = MessagePack::Packer.new
661
+ data.each{|x| pk.write(x) }
662
+ pk.to_str
663
+ else
664
+ raise
665
+ end
666
+ end
667
+ let :gziped do
668
+ s = StringIO.new(String.new)
669
+ Zlib::GzipWriter.wrap(s) do |f|
670
+ f.write formatted
671
+ end
672
+ s.string
673
+ end
674
+ let :deflated do
675
+ Zlib::Deflate.deflate(formatted)
676
+ end
677
+ subject do
678
+ str = ''
679
+ api.__send__(:job_result_download, 12345, format){|x| str << x }
680
+ str
681
+ end
682
+ context '200' do
683
+ before do
684
+ sz = packed.bytesize / 3
685
+ stub_api_request(:get, '/v3/job/result/12345').
686
+ with(:query => {'format' => format}).
687
+ to_return(
688
+ :headers => {
689
+ 'Content-Encoding' => content_encoding,
690
+ 'Content-Length' => packed.bytesize,
691
+ 'Etag' => '"abcdefghijklmn"',
692
+ },
693
+ :body => packed
694
+ )
695
+ expect(api).not_to receive(:sleep)
696
+ expect($stderr).not_to receive(:print)
697
+ expect($stderr).not_to receive(:puts)
698
+ end
699
+ context 'Content-Encoding: gzip' do
700
+ let (:content_encoding){ 'gzip' }
701
+ let (:packed){ gziped }
702
+ context 'msgpack' do
703
+ let (:format){ 'msgpack' }
704
+ it { is_expected.to eq formatted }
705
+ end
706
+ context 'json' do
707
+ let (:format){ 'json' }
708
+ it { is_expected.to eq formatted }
709
+ end
710
+ end
711
+ context 'Content-Encoding: deflate' do
712
+ let (:content_encoding){ 'deflate' }
713
+ let (:packed){ deflated }
714
+ context 'msgpack' do
715
+ let (:format){ 'msgpack' }
716
+ it { is_expected.to eq formatted }
717
+ end
718
+ context 'json' do
719
+ let (:format){ 'json' }
720
+ it { is_expected.to eq formatted }
721
+ end
722
+ end
723
+ end
724
+
725
+ context '200 -> 206' do
726
+ before do
727
+ sz = packed.bytesize / 3
728
+ stub_api_request(:get, '/v3/job/result/12345').
729
+ with(:query => {'format' => format}).
730
+ to_return(
731
+ :headers => {
732
+ 'Content-Encoding' => content_encoding,
733
+ 'Content-Length' => packed.bytesize,
734
+ 'Etag' => '"abcdefghijklmn"',
735
+ },
736
+ :body => packed[0, sz]
737
+ )
738
+ stub_api_request(:get, '/v3/job/result/12345').
739
+ with(
740
+ :headers => {
741
+ 'If-Range' => '"abcdefghijklmn"',
742
+ 'Range' => "bytes=#{sz}-",
743
+ },
744
+ :query => {'format' => format}
745
+ ).
746
+ to_return(
747
+ :status => 206,
748
+ :headers => {
749
+ 'Content-Encoding' => content_encoding,
750
+ 'Content-Length' => packed.bytesize - sz,
751
+ 'Content-Range' => "bytes #{sz}-#{packed.bytesize-1}/#{packed.bytesize}",
752
+ 'Etag' => '"abcdefghijklmn"',
753
+ },
754
+ :body => packed[sz, packed.bytesize - sz]
755
+ )
756
+ expect(api).to receive(:sleep).once
757
+ allow($stderr).to receive(:print)
758
+ allow($stderr).to receive(:puts)
759
+ end
760
+ context 'Content-Encoding: gzip' do
761
+ let (:content_encoding){ 'gzip' }
762
+ let (:packed){ gziped }
763
+ context 'msgpack' do
764
+ let (:format){ 'msgpack' }
765
+ it { is_expected.to eq formatted }
766
+ end
767
+ context 'json' do
768
+ let (:format){ 'json' }
769
+ it { is_expected.to eq formatted }
770
+ end
771
+ end
772
+ context 'Content-Encoding: deflate' do
773
+ let (:content_encoding){ 'deflate' }
774
+ let (:packed){ deflated }
775
+ context 'msgpack' do
776
+ let (:format){ 'msgpack' }
777
+ it { is_expected.to eq formatted }
778
+ end
779
+ context 'json' do
780
+ let (:format){ 'json' }
781
+ it { is_expected.to eq formatted }
782
+ end
783
+ end
784
+ end
785
+
786
+ context 'without autodecode' do
787
+ before do
788
+ sz = packed.bytesize / 3
789
+ stub_api_request(:get, '/v3/job/result/12345').
790
+ with(:query => {'format' => format}).
791
+ to_return(
792
+ :headers => {
793
+ 'Content-Length' => packed.bytesize,
794
+ 'Etag' => '"abcdefghijklmn"',
795
+ },
796
+ :body => packed
797
+ )
798
+ expect(api).not_to receive(:sleep)
799
+ expect($stderr).not_to receive(:print)
800
+ expect($stderr).not_to receive(:puts)
801
+ end
802
+ subject do
803
+ str = ''
804
+ api.__send__(:job_result_download, 12345, format, false){|x| str << x }
805
+ str
806
+ end
807
+ context 'Content-Encoding: gzip' do
808
+ let (:content_encoding){ 'gzip' }
809
+ let (:packed){ gziped }
810
+ context 'msgpack' do
811
+ let (:format){ 'msgpack' }
812
+ it { is_expected.to eq packed }
813
+ end
814
+ context 'json' do
815
+ let (:format){ 'json' }
816
+ it { is_expected.to eq packed }
817
+ end
818
+ end
819
+ context 'Content-Encoding: deflate' do
820
+ let (:content_encoding){ 'deflate' }
821
+ let (:packed){ deflated }
822
+ context 'msgpack' do
823
+ let (:format){ 'msgpack' }
824
+ it { is_expected.to eq packed }
825
+ end
826
+ context 'json' do
827
+ let (:format){ 'json' }
828
+ it { is_expected.to eq packed }
829
+ end
830
+ end
324
831
  end
325
832
  end
326
833
  end