td-client 0.9.0dev2 → 1.0.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.
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