td-client 1.0.0-java

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/data/ca-bundle.crt +3448 -0
  3. data/lib/td-client.rb +1 -0
  4. data/lib/td/client.rb +606 -0
  5. data/lib/td/client/api.rb +707 -0
  6. data/lib/td/client/api/access_control.rb +74 -0
  7. data/lib/td/client/api/account.rb +45 -0
  8. data/lib/td/client/api/bulk_import.rb +184 -0
  9. data/lib/td/client/api/bulk_load.rb +172 -0
  10. data/lib/td/client/api/database.rb +50 -0
  11. data/lib/td/client/api/export.rb +38 -0
  12. data/lib/td/client/api/import.rb +38 -0
  13. data/lib/td/client/api/job.rb +390 -0
  14. data/lib/td/client/api/partial_delete.rb +27 -0
  15. data/lib/td/client/api/result.rb +46 -0
  16. data/lib/td/client/api/schedule.rb +120 -0
  17. data/lib/td/client/api/server_status.rb +21 -0
  18. data/lib/td/client/api/table.rb +132 -0
  19. data/lib/td/client/api/user.rb +134 -0
  20. data/lib/td/client/api_error.rb +37 -0
  21. data/lib/td/client/compat_gzip_reader.rb +22 -0
  22. data/lib/td/client/model.rb +816 -0
  23. data/lib/td/client/version.rb +5 -0
  24. data/lib/td/core_ext/openssl/ssl/sslcontext/set_params.rb +18 -0
  25. data/spec/spec_helper.rb +63 -0
  26. data/spec/td/client/access_control_api_spec.rb +37 -0
  27. data/spec/td/client/account_api_spec.rb +34 -0
  28. data/spec/td/client/api_error_spec.rb +77 -0
  29. data/spec/td/client/api_spec.rb +269 -0
  30. data/spec/td/client/api_ssl_connection_spec.rb +109 -0
  31. data/spec/td/client/bulk_import_spec.rb +199 -0
  32. data/spec/td/client/bulk_load_spec.rb +401 -0
  33. data/spec/td/client/db_api_spec.rb +123 -0
  34. data/spec/td/client/export_api_spec.rb +51 -0
  35. data/spec/td/client/import_api_spec.rb +148 -0
  36. data/spec/td/client/job_api_spec.rb +833 -0
  37. data/spec/td/client/model_job_spec.rb +136 -0
  38. data/spec/td/client/model_schedule_spec.rb +26 -0
  39. data/spec/td/client/model_schema_spec.rb +134 -0
  40. data/spec/td/client/partial_delete_api_spec.rb +58 -0
  41. data/spec/td/client/result_api_spec.rb +77 -0
  42. data/spec/td/client/sched_api_spec.rb +109 -0
  43. data/spec/td/client/server_status_api_spec.rb +25 -0
  44. data/spec/td/client/spec_resources.rb +99 -0
  45. data/spec/td/client/table_api_spec.rb +226 -0
  46. data/spec/td/client/user_api_spec.rb +118 -0
  47. data/spec/td/client_sched_spec.rb +79 -0
  48. data/spec/td/client_spec.rb +46 -0
  49. metadata +271 -0
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'Job Model' do
5
+ include_context 'spec symbols'
6
+ include_context 'common helper'
7
+ include_context 'job resources'
8
+
9
+ before do
10
+ stub_api_request(:post, "/v3/user/authenticate").
11
+ to_return(:body => {'apikey' => 'apikey'}.to_json)
12
+ end
13
+
14
+ describe '#client' do
15
+ subject do
16
+ Job.new(client, *arguments).client
17
+ end
18
+
19
+ let :client do
20
+ Client.authenticate('user', 'password')
21
+ end
22
+
23
+ let :arguments do
24
+ job_attributes = raw_jobs.first
25
+ [
26
+ 'job_id', 'type', 'query', 'status', 'url', 'debug',
27
+ 'start_at', 'end_at', 'cpu_time', 'result_size', 'result', 'result_url',
28
+ 'hive_result_schema', 'priority', 'retry_limit', 'org_name', 'db_name',
29
+ 'duration', 'num_records'
30
+ ].map {|name| job_attributes[name]}
31
+ end
32
+
33
+ it 'returns Job object having client' do
34
+ expect(subject).to eq client
35
+ end
36
+ end
37
+
38
+ describe '#result_raw' do
39
+ let(:client) { Client.authenticate('user', 'password') }
40
+ let(:job_id) { 12345678 }
41
+ let(:job) { Job.new(client, job_id, nil, nil) }
42
+ let(:format) { 'json' }
43
+ let(:io) { StringIO.new }
44
+
45
+ context 'not finished?' do
46
+ before { allow(job).to receive(:finished?) { false } }
47
+
48
+ it 'do not call #job_result_raw' do
49
+ expect(client).not_to receive(:job_result_raw)
50
+
51
+ expect(job.result_raw(format, io)).to_not be
52
+ end
53
+ end
54
+
55
+ context 'finished?' do
56
+ before { allow(job).to receive(:finished?) { true } }
57
+
58
+ it 'call #job_result_raw' do
59
+ expect(client).to receive(:job_result_raw).with(job_id, format, io)
60
+
61
+ job.result_raw(format, io)
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#wait' do
67
+ let(:client) { Client.authenticate('user', 'password') }
68
+ let(:job_id) { 12345678 }
69
+ let(:job) { Job.new(client, job_id, nil, nil) }
70
+
71
+ def change_job_status(status)
72
+ allow(client).to receive(:job_status).with(job_id).and_return(status)
73
+ end
74
+
75
+ before do
76
+ change_job_status(Job::STATUS_QUEUED)
77
+ end
78
+
79
+ context 'without timeout' do
80
+ it 'waits the job to be finished' do
81
+ begin
82
+ thread = Thread.start { job.wait }
83
+ expect(thread).to be_alive
84
+ change_job_status(Job::STATUS_SUCCESS)
85
+ thread.join(1)
86
+ expect(thread).to be_stop
87
+ ensure
88
+ thread.kill # just in case
89
+ end
90
+ end
91
+
92
+ it 'calls a given block in every wait_interval second' do
93
+ now = 1_400_000_000
94
+ allow(self).to receive(:sleep){|arg| now += arg }
95
+ allow(Process).to receive(:clock_gettime){ now }
96
+ expect { |b|
97
+ begin
98
+ thread = Thread.start {
99
+ job.wait(nil, 2, &b)
100
+ }
101
+ sleep 6
102
+ change_job_status(Job::STATUS_SUCCESS)
103
+ thread.join(1)
104
+ expect(thread).to be_stop
105
+ ensure
106
+ thread.kill # just in case
107
+ end
108
+ }.to yield_control.at_least(2).at_most(3).times
109
+ end
110
+ end
111
+
112
+ context 'with timeout' do
113
+ context 'the job running time is too long' do
114
+ it 'raise Timeout::Error' do
115
+ expect {
116
+ job.wait(0.1)
117
+ }.to raise_error(Timeout::Error)
118
+ end
119
+ end
120
+
121
+ it 'calls a given block in every wait_interval second, and timeout' do
122
+ expect { |b|
123
+ begin
124
+ thread = Thread.start {
125
+ job.wait(0.3, 0.1, &b)
126
+ }
127
+ expect{ thread.value }.to raise_error(Timeout::Error)
128
+ expect(thread).to be_stop
129
+ ensure
130
+ thread.kill # just in case
131
+ end
132
+ }.to yield_control.at_least(2).times
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'Schedule Model' do
5
+ describe '#run' do
6
+ let(:api_key) { '1234567890abcd' }
7
+ let(:api) { double(:api) }
8
+ let(:client) { Client.new(api_key) }
9
+ let(:name) { 'schedule' }
10
+ let(:schedule) {
11
+ Schedule.new(client, name, '0 0 * * * *', 'select 1')
12
+ }
13
+ let(:time) { "2013-01-01 00:00:00" }
14
+ let(:num) { 1 }
15
+
16
+ before do
17
+ allow(API).to receive(:new).with(api_key, {}).and_return(api)
18
+ end
19
+
20
+ it 'success call api' do
21
+ expect(api).to receive(:run_schedule).with(name, time, num).and_return([])
22
+
23
+ schedule.run(time, num)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'TreasureData::Schema::Field' do
5
+ describe '.new' do
6
+ context 'name="v"' do
7
+ it 'raises ParameterValidationError' do
8
+ expect{ Schema::Field.new('v', 'int') }.to raise_error(ParameterValidationError)
9
+ end
10
+ end
11
+ context 'name="time"' do
12
+ it 'raises ParameterValidationError' do
13
+ expect{ Schema::Field.new('time', 'int') }.to raise_error(ParameterValidationError)
14
+ end
15
+ end
16
+ context 'name with UTF-8' do
17
+ it 'works' do
18
+ name = "\u3042\u3044\u3046"
19
+ f = Schema::Field.new(name, 'int')
20
+ expect(f.name).to eq name
21
+ expect(f.type).to eq 'int'
22
+ expect(f.sql_alias).to be_nil
23
+ end
24
+ end
25
+ context 'with sql_alias' do
26
+ it 'raises' do
27
+ f = Schema::Field.new('t:t', 'int', 'alice')
28
+ expect(f.name).to eq 't:t'
29
+ expect(f.type).to eq 'int'
30
+ expect(f.sql_alias).to eq 'alice'
31
+ end
32
+ end
33
+ context 'with invalid sql_alias' do
34
+ it 'raises' do
35
+ expect{ Schema::Field.new('t:t', 'int', 't:t') }.to raise_error(ParameterValidationError)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ describe 'TreasureData::Schema' do
42
+ describe '.parse' do
43
+ let(:columns){ ["foo:int", "BAR\u3070\u30FC:string@bar", "baz:baz!:array<double>@baz"] }
44
+ it do
45
+ sc = Schema.parse(columns)
46
+ expect(sc.fields.size).to eq 3
47
+ expect(sc.fields[0].name).to eq 'foo'
48
+ expect(sc.fields[0].type).to eq 'int'
49
+ expect(sc.fields[0].sql_alias).to be_nil
50
+ expect(sc.fields[1].name).to eq "BAR\u3070\u30FC"
51
+ expect(sc.fields[1].type).to eq 'string'
52
+ expect(sc.fields[1].sql_alias).to eq 'bar'
53
+ expect(sc.fields[2].name).to eq 'baz:baz!'
54
+ expect(sc.fields[2].type).to eq 'array<double>'
55
+ expect(sc.fields[2].sql_alias).to eq 'baz'
56
+ end
57
+ end
58
+
59
+ describe '.new' do
60
+ it do
61
+ f = Schema::Field.new('a', 'int')
62
+ sc = Schema.new([f])
63
+ expect(sc.fields[0]).to eq f
64
+ end
65
+ end
66
+
67
+ describe '#fields' do
68
+ it do
69
+ f = Schema::Field.new('a', 'int')
70
+ sc = Schema.new([f])
71
+ expect(sc.fields[0]).to eq f
72
+ end
73
+ end
74
+
75
+ describe '#add_field' do
76
+ it do
77
+ f = Schema::Field.new('a', 'int')
78
+ sc = Schema.new([f])
79
+ sc.add_field('b', 'double', 'bb')
80
+ expect(sc.fields[1].name).to eq 'b'
81
+ end
82
+ it 'raises ParameterValidationError if name is duplicated' do
83
+ f = Schema::Field.new('a', 'int')
84
+ sc = Schema.new([f])
85
+ expect{ sc.add_field('a', 'double') }.to raise_error(ParameterValidationError)
86
+ end
87
+ it 'raises ParameterValidationError if sql_alias is duplicated' do
88
+ f = Schema::Field.new('a', 'int')
89
+ sc = Schema.new([f])
90
+ expect{ sc.add_field('abc', 'double', 'a') }.to raise_error(ParameterValidationError)
91
+ end
92
+ end
93
+
94
+ describe '#merge' do
95
+ it do
96
+ sc1 = Schema.parse(['foo:int', 'bar:float'])
97
+ sc2 = Schema.parse(['bar:double', 'baz:string'])
98
+ sc3 = sc1.merge(sc2)
99
+ expect(sc3.fields.size).to eq 3
100
+ expect(sc3.fields[0].name).to eq 'foo'
101
+ expect(sc3.fields[0].type).to eq 'int'
102
+ expect(sc3.fields[1].name).to eq 'bar'
103
+ expect(sc3.fields[1].type).to eq 'double'
104
+ expect(sc3.fields[2].name).to eq 'baz'
105
+ expect(sc3.fields[2].type).to eq 'string'
106
+ end
107
+ it do
108
+ sc1 = Schema.parse(['foo:int', 'bar:float'])
109
+ sc2 = Schema.parse(['bar:double@foo'])
110
+ expect{ sc1.merge(sc2) }.to raise_error(ArgumentError)
111
+ end
112
+ end
113
+
114
+ describe '#to_json' do
115
+ it do
116
+ sc = Schema.parse(['foo:int', 'bar:float@baz'])
117
+ expect(sc.to_json).to eq '[["foo","int"],["bar","float","baz"]]'
118
+ end
119
+ end
120
+
121
+ describe '#from_json' do
122
+ it do
123
+ sc = Schema.new
124
+ sc.from_json [["foo","int"],["bar","float","baz"]]
125
+ expect(sc.fields.size).to eq 2
126
+ expect(sc.fields[0].name).to eq 'foo'
127
+ expect(sc.fields[0].type).to eq 'int'
128
+ expect(sc.fields[0].sql_alias).to be_nil
129
+ expect(sc.fields[1].name).to eq 'bar'
130
+ expect(sc.fields[1].type).to eq 'float'
131
+ expect(sc.fields[1].sql_alias).to eq 'baz'
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'PartialDelete API' do
5
+ include_context 'spec symbols'
6
+ include_context 'common helper'
7
+
8
+ let :api do
9
+ API.new(nil)
10
+ end
11
+
12
+ describe 'partialdelete' do
13
+ let :from do
14
+ 0
15
+ end
16
+
17
+ let :to do
18
+ 3600 * 10
19
+ end
20
+
21
+ let :from_to do
22
+ {'from' => from.to_s, 'to' => to.to_s}
23
+ end
24
+
25
+ it 'should partial_delete successfully' do
26
+ # TODO: Use correnty values
27
+ stub_api_request(:post, "/v3/table/partialdelete/#{e(db_name)}/#{e(table_name)}").with(:body => from_to).
28
+ to_return(:body => {'database' => db_name, 'table' => table_name, 'job_id' => '1'}.to_json)
29
+
30
+ expect(api.partial_delete(db_name, table_name, to, from)).to eq('1')
31
+ end
32
+
33
+ it 'should return 404 error with non exist database name' do
34
+ db = 'no_such_db'
35
+ err_msg = "Couldn't find UserDatabase with name = #{db}"
36
+ stub_api_request(:post, "/v3/table/partialdelete/#{e(db)}/#{e(table_name)}").with(:body => from_to).
37
+ to_return(:status => 404, :body => {'message' => err_msg}.to_json)
38
+
39
+ expect {
40
+ api.partial_delete(db, table_name, to, from)
41
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
42
+ end
43
+
44
+ it 'should return 404 error with non exist table name' do
45
+ table = 'no_such_table'
46
+ err_msg = "Unknown table: #{table}"
47
+ stub_api_request(:post, "/v3/table/partialdelete/#{e(db_name)}/#{e(table)}").with(:body => from_to).
48
+ to_return(:status => 404, :body => {'message' => err_msg}.to_json)
49
+
50
+ expect {
51
+ api.partial_delete(db_name, table, to, from)
52
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
53
+ end
54
+
55
+ # TODO: Add from / to parameters spec
56
+ end
57
+ end
58
+
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'Result API' do
5
+ include_context 'spec symbols'
6
+ include_context 'common helper'
7
+
8
+ let :api do
9
+ API.new(nil)
10
+ end
11
+
12
+ describe 'create_result' do
13
+ it 'should create a new result' do
14
+ params = {'url' => result_url}
15
+ stub_api_request(:post, "/v3/result/create/#{e(result_name)}").with(:body => params).to_return(:body => {'result' => result_name}.to_json)
16
+
17
+ expect(api.create_result(result_name, result_url)).to be true
18
+ end
19
+
20
+ it 'should return 422 error with invalid name' do
21
+ name = '1'
22
+ params = {'url' => result_url}
23
+ err_msg = "Validation failed: Name is too short" # " (minimum is 3 characters)"
24
+ stub_api_request(:post, "/v3/result/create/#{e(name)}").with(:body => params).
25
+ to_return(:status => 422, :body => {'message' => err_msg}.to_json)
26
+
27
+ expect {
28
+ api.create_result(name, result_url)
29
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
30
+ end
31
+
32
+ it 'should return 422 error without url' do
33
+ params = {'url' => 'false'} # I want to use nil, but nil doesn't work on WebMock...
34
+ err_msg = "'url' parameter is required"
35
+ stub_api_request(:post, "/v3/result/create/#{e(result_name)}").with(:body => params).
36
+ to_return(:status => 422, :body => {'message' => err_msg}.to_json)
37
+
38
+ expect {
39
+ api.create_result(result_name, false)
40
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
41
+ end
42
+
43
+ it 'should return 409 error with duplicated name' do
44
+ params = {'url' => result_url}
45
+ err_msg = "Result must be unique"
46
+ stub_api_request(:post, "/v3/result/create/#{e(result_name)}").with(:body => params).
47
+ to_return(:status => 409, :body => {'message' => err_msg}.to_json)
48
+
49
+ expect {
50
+ api.create_result(result_name, result_url)
51
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
52
+ end
53
+ end
54
+
55
+ describe 'list_result' do
56
+ it 'should return name and url' do
57
+ stub_api_request(:get, '/v3/result/list').
58
+ to_return(:body => {'results' => [{'name' => 'name', 'url' => 'url'}]}.to_json)
59
+ expect(api.list_result).to eq([['name', 'url', nil]])
60
+ end
61
+ end
62
+
63
+ describe 'delete_result' do
64
+ it 'should delete the result' do
65
+ stub_api_request(:post, "/v3/result/delete/#{e(result_name)}")
66
+ expect(api.delete_result(result_name)).to eq(true)
67
+ end
68
+
69
+ it 'should raise error' do
70
+ stub_api_request(:post, "/v3/result/delete/#{e(result_name)}").
71
+ to_return(:status => 404)
72
+ expect {
73
+ api.delete_result(result_name)
74
+ }.to raise_error(TreasureData::APIError)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'td/client/spec_resources'
3
+
4
+ describe 'Schedule API' do
5
+ include_context 'spec symbols'
6
+ include_context 'common helper'
7
+
8
+ let :api do
9
+ API.new(nil)
10
+ end
11
+
12
+ describe 'create_schedule' do
13
+ let :opts do
14
+ {'cron' => cron, 'query' => query, 'database' => db_name}
15
+ end
16
+
17
+ it 'should create a new schedule' do
18
+ start = Time.now
19
+ stub_api_request(:post, "/v3/schedule/create/#{e(sched_name)}").
20
+ with(:body => opts.merge('type' => 'hive')).
21
+ to_return(:body => {'name' => sched_name, 'start' => start.to_s}.to_json)
22
+
23
+ expect(api.create_schedule(sched_name, opts.merge('type' => 'hive'))).to eq(start.to_s)
24
+ end
25
+
26
+ it 'should create a dummy schedule' do
27
+ stub_api_request(:post, "/v3/schedule/create/#{e(sched_name)}").
28
+ with(:body => opts.merge('type' => 'hive')).
29
+ to_return(:body => {'name' => sched_name, 'start' => nil}.to_json)
30
+
31
+ expect(api.create_schedule(sched_name, opts.merge('type' => 'hive'))).to be_nil
32
+ end
33
+
34
+ it 'should return 422 error with invalid name' do
35
+ name = '1'
36
+ err_msg = "Validation failed: Name is too short" # " (minimum is 3 characters)"
37
+ stub_api_request(:post, "/v3/schedule/create/#{e(name)}").
38
+ with(:body => opts.merge('type' => 'hive')).
39
+ to_return(:status => 422, :body => {'message' => err_msg}.to_json)
40
+
41
+ expect {
42
+ api.create_schedule(name, opts.merge('type' => 'hive'))
43
+ }.to raise_error(TreasureData::APIError, /#{err_msg}/)
44
+ end
45
+ end
46
+
47
+ describe 'delete_schedule' do
48
+ it 'should delete the schedule' do
49
+ stub_api_request(:post, "/v3/schedule/delete/#{e(sched_name)}").
50
+ to_return(:body => {'cron' => 'cron', 'query' => 'query'}.to_json)
51
+ expect(api.delete_schedule(sched_name)).to eq(['cron', 'query'])
52
+ end
53
+ end
54
+
55
+ describe 'update_schedule' do
56
+ let :pig_query do
57
+ "OUT = FOREACH (GROUP plt364 ALL) GENERATE COUNT(plt364);\n" * 200
58
+ end
59
+ let :opts do
60
+ {'cron' => cron, 'query' => pig_query, 'database' => db_name}
61
+ end
62
+
63
+ it 'should not return 414 even if the query text is very long' do
64
+ stub_api_request(:post, "/v3/schedule/update/#{e(sched_name)}").
65
+ with(:body => opts.merge('type' => 'pig')).
66
+ to_return(:body => {'name' => sched_name, 'query' => pig_query}.to_json)
67
+
68
+ expect {
69
+ api.update_schedule(sched_name, opts.merge('type' => 'pig'))
70
+ }.not_to raise_error
71
+ end
72
+
73
+ it 'should update the schedule with the new query' do
74
+ stub_api_request(:post, "/v3/schedule/update/#{e(sched_name)}").
75
+ with(:body => opts.merge('type' => 'pig')).
76
+ to_return(:body => {'name' => sched_name, 'query' => pig_query}.to_json)
77
+
78
+ stub_api_request(:get, "/v3/schedule/list").
79
+ to_return(:body => {'schedules' => [{'name' => sched_name, 'query' => pig_query}]}.to_json)
80
+
81
+ expect(api.list_schedules.first[2]).to eq(pig_query)
82
+ end
83
+ end
84
+
85
+ describe 'history' do
86
+ let :history do
87
+ ['history', 'job_id', 'type', 'database', 'status', 'query', 'start_at', 'end_at', 'result', 'priority'].inject({}) { |r, e|
88
+ r[e] = e
89
+ r
90
+ }
91
+ end
92
+
93
+ it 'should return history records' do
94
+ stub_api_request(:get, "/v3/schedule/history/#{e(sched_name)}").
95
+ with(:query => {'from' => 0, 'to' => 100}).
96
+ to_return(:body => {'history' => [history]}.to_json)
97
+ expect(api.history(sched_name, 0, 100)).to eq([[nil, 'job_id', :type, 'status', 'query', 'start_at', 'end_at', 'result', 'priority', 'database']])
98
+ end
99
+ end
100
+
101
+ describe 'run_schedule' do
102
+ it 'should return history records' do
103
+ stub_api_request(:post, "/v3/schedule/run/#{e(sched_name)}/123456789").
104
+ with(:body => {'num' => '5'}).
105
+ to_return(:body => {'jobs' => [{'job_id' => 'job_id', 'scheduled_at' => 'scheduled_at', 'type' => 'type'}]}.to_json)
106
+ expect(api.run_schedule(sched_name, 123456789, 5)).to eq([['job_id', :type, 'scheduled_at']])
107
+ end
108
+ end
109
+ end