haveapi-client 0.26.4 → 0.27.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 +4 -4
- data/Gemfile +4 -0
- data/lib/haveapi/cli/action_state.rb +12 -11
- data/lib/haveapi/cli/cli.rb +2 -2
- data/lib/haveapi/client/action.rb +2 -2
- data/lib/haveapi/client/communicator.rb +2 -6
- data/lib/haveapi/client/exceptions.rb +5 -1
- data/lib/haveapi/client/parameters/resource.rb +2 -0
- data/lib/haveapi/client/parameters/typed.rb +130 -35
- data/lib/haveapi/client/params.rb +5 -2
- data/lib/haveapi/client/version.rb +1 -1
- data/spec/integration/client_spec.rb +92 -0
- data/spec/integration/typed_input_spec.rb +113 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/test_server.rb +95 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6231f3629c9e015d7e916dc7cf97466b30849db2a1fe86a521f5fd1b99fbb72
|
|
4
|
+
data.tar.gz: 46cb500d05af0f1bdd25f4a2749d7d459ff8460d1477dea59b64e816162dc9e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5afb270a053317c22c6a5748739423690743a02a1b3f8e58d1e045d33b4446bd0d96f5427ffc7c37f6cfbb3266b2cfd8801692d9f3c557843fe586e4a60a9535
|
|
7
|
+
data.tar.gz: e42801e205282f72038c3777d819799be33569e63a9864ef13786e7346785cca469dd6128f9c3241f257d098bd089c9f9b3d8d47473f778019b8c30361833207
|
data/Gemfile
CHANGED
|
@@ -26,9 +26,9 @@ module HaveAPI::CLI
|
|
|
26
26
|
id ||= @id
|
|
27
27
|
|
|
28
28
|
if cancel
|
|
29
|
-
|
|
29
|
+
warn 'Waiting for the action to cancel (hit Ctrl+C to skip)...'
|
|
30
30
|
else
|
|
31
|
-
|
|
31
|
+
warn 'Waiting for the action to complete (hit Ctrl+C to skip)...'
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
last_status = false
|
|
@@ -47,11 +47,11 @@ module HaveAPI::CLI
|
|
|
47
47
|
end
|
|
48
48
|
rescue Interrupt
|
|
49
49
|
@pb && @pb.stop
|
|
50
|
-
|
|
50
|
+
warn
|
|
51
51
|
|
|
52
52
|
cancel_action(timeout: timeout) if can_cancel && !cancel && last_status
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
warn
|
|
55
55
|
print_help(id)
|
|
56
56
|
exit(false)
|
|
57
57
|
end
|
|
@@ -81,7 +81,7 @@ module HaveAPI::CLI
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
if res.is_a?(HaveAPI::Client::Response) && res.ok?
|
|
84
|
-
|
|
84
|
+
warn 'Cancelled'
|
|
85
85
|
exit
|
|
86
86
|
|
|
87
87
|
elsif res
|
|
@@ -102,11 +102,11 @@ module HaveAPI::CLI
|
|
|
102
102
|
def print_help(id = nil)
|
|
103
103
|
id ||= @id
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
warn 'Run'
|
|
106
|
+
warn " #{$0} action_state show #{id}"
|
|
107
|
+
warn 'or'
|
|
108
|
+
warn " #{$0} action_state wait #{id}"
|
|
109
|
+
warn "to check the action's progress."
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
protected
|
|
@@ -121,7 +121,8 @@ module HaveAPI::CLI
|
|
|
121
121
|
'%t: [%B]'
|
|
122
122
|
end,
|
|
123
123
|
starting_at: state.progress.current,
|
|
124
|
-
autofinish: false
|
|
124
|
+
autofinish: false,
|
|
125
|
+
output: $stderr
|
|
125
126
|
)
|
|
126
127
|
|
|
127
128
|
@pb.title = if state.status
|
data/lib/haveapi/cli/cli.rb
CHANGED
|
@@ -112,7 +112,7 @@ module HaveAPI::CLI
|
|
|
112
112
|
@input_params[:meta] = { includes: includes } if includes
|
|
113
113
|
|
|
114
114
|
begin
|
|
115
|
-
ret = action.execute(@input_params
|
|
115
|
+
ret = action.execute(@input_params)
|
|
116
116
|
rescue HaveAPI::Client::ValidationError => e
|
|
117
117
|
format_errors(action, 'input parameters not valid', e.errors)
|
|
118
118
|
exit(false)
|
|
@@ -508,7 +508,7 @@ module HaveAPI::CLI
|
|
|
508
508
|
|
|
509
509
|
def format_output(action, response, out = $>)
|
|
510
510
|
if @opts[:raw]
|
|
511
|
-
puts JSON.
|
|
511
|
+
puts JSON.pretty_generate(response)
|
|
512
512
|
return
|
|
513
513
|
end
|
|
514
514
|
|
|
@@ -16,7 +16,7 @@ module HaveAPI::Client
|
|
|
16
16
|
"#<#{self.class.name} @name=#{@name}>"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def execute(data
|
|
19
|
+
def execute(data)
|
|
20
20
|
params_arg = {}
|
|
21
21
|
|
|
22
22
|
if input
|
|
@@ -29,7 +29,7 @@ module HaveAPI::Client
|
|
|
29
29
|
params_arg = params.to_api
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
ret = @api.call(self, params_arg
|
|
32
|
+
ret = @api.call(self, params_arg)
|
|
33
33
|
reset
|
|
34
34
|
ret
|
|
35
35
|
end
|
|
@@ -117,7 +117,7 @@ module HaveAPI::Client
|
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
def call(action, params = {}
|
|
120
|
+
def call(action, params = {})
|
|
121
121
|
args = []
|
|
122
122
|
input_namespace = action.input && action.namespace(:input)
|
|
123
123
|
meta = nil
|
|
@@ -164,11 +164,7 @@ module HaveAPI::Client
|
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
if response[:status]
|
|
167
|
-
|
|
168
|
-
ok(JSON.pretty_generate(response[:response]))
|
|
169
|
-
else
|
|
170
|
-
ok(response[:response])
|
|
171
|
-
end
|
|
167
|
+
ok(response[:response])
|
|
172
168
|
|
|
173
169
|
else
|
|
174
170
|
error(response[:message], response[:errors])
|
|
@@ -5,7 +5,11 @@ module HaveAPI::Client
|
|
|
5
5
|
attr_reader :response
|
|
6
6
|
|
|
7
7
|
def initialize(response)
|
|
8
|
-
|
|
8
|
+
if response.respond_to?(:action)
|
|
9
|
+
super("#{response.action.name} failed: #{response.message}")
|
|
10
|
+
else
|
|
11
|
+
super(response.to_s)
|
|
12
|
+
end
|
|
9
13
|
|
|
10
14
|
@response = response
|
|
11
15
|
end
|
|
@@ -2,20 +2,6 @@ require 'date'
|
|
|
2
2
|
|
|
3
3
|
module HaveAPI::Client
|
|
4
4
|
class Parameters::Typed
|
|
5
|
-
module Boolean
|
|
6
|
-
def self.to_b(str)
|
|
7
|
-
return true if str === true
|
|
8
|
-
return false if str === false
|
|
9
|
-
|
|
10
|
-
if str.respond_to?(:=~)
|
|
11
|
-
return true if str =~ /^(true|t|yes|y|1)$/i
|
|
12
|
-
return false if str =~ /^(false|f|no|n|0)$/i
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
false
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
5
|
attr_reader :errors, :value
|
|
20
6
|
|
|
21
7
|
def initialize(params, desc, value)
|
|
@@ -26,6 +12,8 @@ module HaveAPI::Client
|
|
|
26
12
|
end
|
|
27
13
|
|
|
28
14
|
def valid?
|
|
15
|
+
return false unless @errors.empty?
|
|
16
|
+
|
|
29
17
|
ret = Validator.validate(@desc[:validators], @value, @params)
|
|
30
18
|
|
|
31
19
|
@errors.concat(ret) unless ret === true
|
|
@@ -44,39 +32,146 @@ module HaveAPI::Client
|
|
|
44
32
|
protected
|
|
45
33
|
|
|
46
34
|
def coerce(raw)
|
|
35
|
+
return nil if raw.nil?
|
|
36
|
+
|
|
47
37
|
type = @desc[:type]
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
case type
|
|
40
|
+
when 'Integer'
|
|
41
|
+
coerce_integer(raw)
|
|
42
|
+
when 'Float'
|
|
43
|
+
coerce_float(raw)
|
|
44
|
+
when 'Boolean'
|
|
45
|
+
coerce_boolean(raw)
|
|
46
|
+
when 'Datetime'
|
|
47
|
+
coerce_datetime(raw)
|
|
48
|
+
when 'String', 'Text'
|
|
49
|
+
coerce_string(raw)
|
|
50
|
+
else
|
|
51
|
+
raw
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def coerce_integer(raw)
|
|
58
|
+
case raw
|
|
59
|
+
when ::Integer
|
|
60
|
+
raw
|
|
61
|
+
|
|
62
|
+
when ::Float
|
|
63
|
+
return raw.to_i if raw.finite? && raw == raw.to_i
|
|
64
|
+
|
|
65
|
+
invalid_integer
|
|
66
|
+
|
|
67
|
+
when ::String
|
|
68
|
+
s = raw.strip
|
|
69
|
+
return invalid_integer if s.empty?
|
|
70
|
+
return invalid_integer unless s.match?(/\A[+-]?\d+\z/)
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
raw.to_f
|
|
72
|
+
Integer(s, 10)
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
else
|
|
75
|
+
invalid_integer
|
|
76
|
+
end
|
|
77
|
+
rescue ArgumentError
|
|
78
|
+
invalid_integer
|
|
79
|
+
end
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
def coerce_float(raw)
|
|
82
|
+
if raw.is_a?(::Numeric)
|
|
83
|
+
value = raw.to_f
|
|
84
|
+
return value if value.finite?
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
@errors << 'not a valid float'
|
|
87
|
+
nil
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rescue ArgumentError
|
|
69
|
-
@errors << 'not in ISO 8601 format'
|
|
70
|
-
nil
|
|
71
|
-
end
|
|
72
|
-
end
|
|
89
|
+
elsif raw.is_a?(::String)
|
|
90
|
+
s = raw.strip
|
|
91
|
+
return invalid_float if s.empty?
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
value = Float(s)
|
|
94
|
+
return value if value.finite?
|
|
95
|
+
|
|
96
|
+
invalid_float
|
|
76
97
|
|
|
77
98
|
else
|
|
99
|
+
invalid_float
|
|
100
|
+
end
|
|
101
|
+
rescue ArgumentError
|
|
102
|
+
invalid_float
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def coerce_boolean(raw)
|
|
106
|
+
return raw if [true, false].include?(raw)
|
|
107
|
+
|
|
108
|
+
if raw.is_a?(::Integer)
|
|
109
|
+
return true if raw == 1
|
|
110
|
+
return false if raw == 0
|
|
111
|
+
|
|
112
|
+
return invalid_boolean
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if raw.is_a?(::String)
|
|
116
|
+
s = raw.strip.downcase
|
|
117
|
+
return invalid_boolean if s.empty?
|
|
118
|
+
|
|
119
|
+
return true if %w[true t yes y 1].include?(s)
|
|
120
|
+
return false if %w[false f no n 0].include?(s)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
invalid_boolean
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def coerce_datetime(raw)
|
|
127
|
+
case raw
|
|
128
|
+
when ::Time
|
|
78
129
|
raw
|
|
130
|
+
|
|
131
|
+
when ::Date, ::DateTime
|
|
132
|
+
raw.to_time
|
|
133
|
+
|
|
134
|
+
when ::String
|
|
135
|
+
return invalid_datetime if raw.strip.empty?
|
|
136
|
+
|
|
137
|
+
DateTime.iso8601(raw).to_time
|
|
138
|
+
|
|
139
|
+
else
|
|
140
|
+
invalid_datetime
|
|
79
141
|
end
|
|
142
|
+
rescue ArgumentError
|
|
143
|
+
invalid_datetime
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def coerce_string(raw)
|
|
147
|
+
return invalid_string if raw.is_a?(::Array) || raw.is_a?(::Hash)
|
|
148
|
+
|
|
149
|
+
raw.to_s
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def invalid_integer
|
|
153
|
+
@errors << 'not a valid integer'
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def invalid_float
|
|
158
|
+
@errors << 'not a valid float'
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def invalid_boolean
|
|
163
|
+
@errors << 'not a valid boolean'
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def invalid_datetime
|
|
168
|
+
@errors << 'not in ISO 8601 format'
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def invalid_string
|
|
173
|
+
@errors << 'not a valid string'
|
|
174
|
+
nil
|
|
80
175
|
end
|
|
81
176
|
end
|
|
82
177
|
end
|
|
@@ -29,9 +29,12 @@ module HaveAPI::Client
|
|
|
29
29
|
@action.input_params.each do |name, p|
|
|
30
30
|
next if p[:validators].nil?
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
presence_validator =
|
|
33
|
+
p[:validators][:presence] || p[:validators][:present] || p[:validators][:required]
|
|
34
34
|
|
|
35
|
+
if presence_validator && @params[name].nil?
|
|
36
|
+
error(name, 'required parameter missing')
|
|
37
|
+
next
|
|
35
38
|
elsif @params[name].nil?
|
|
36
39
|
next
|
|
37
40
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe HaveAPI::Client::Client do
|
|
6
|
+
let(:base_url) { TEST_SERVER.base_url }
|
|
7
|
+
|
|
8
|
+
it 'exposes versions and compatibility' do
|
|
9
|
+
client = described_class.new(base_url)
|
|
10
|
+
|
|
11
|
+
versions = client.versions
|
|
12
|
+
expect(versions[:default]).to eq('1.0')
|
|
13
|
+
expect(versions[:versions]).to include('1.0')
|
|
14
|
+
expect(client.compatible?).to(
|
|
15
|
+
satisfy { |value| [:compatible, :imperfect, false].include?(value) }
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'filters docs until authenticated' do
|
|
20
|
+
unauthenticated = described_class.new(base_url)
|
|
21
|
+
unauthenticated.setup
|
|
22
|
+
|
|
23
|
+
expect { unauthenticated.project }.to raise_error(NoMethodError)
|
|
24
|
+
|
|
25
|
+
authenticated = described_class.new(base_url)
|
|
26
|
+
authenticated.authenticate(:basic, user: 'user', password: 'pass')
|
|
27
|
+
|
|
28
|
+
expect { authenticated.project }.not_to raise_error
|
|
29
|
+
expect(authenticated.project).to be_a(HaveAPI::Client::Resource)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'supports CRUD flow for projects' do
|
|
33
|
+
client = described_class.new(base_url)
|
|
34
|
+
client.authenticate(:basic, user: 'user', password: 'pass')
|
|
35
|
+
|
|
36
|
+
projects = client.project.list
|
|
37
|
+
expect(projects).to be_a(Array)
|
|
38
|
+
expect(projects.size).to be >= 2
|
|
39
|
+
|
|
40
|
+
project = client.project.find(projects.first.id)
|
|
41
|
+
expect(project.id).to eq(projects.first.id)
|
|
42
|
+
|
|
43
|
+
created = client.project.create(name: 'Gamma')
|
|
44
|
+
expect(created.id).to be_a(Integer)
|
|
45
|
+
expect(created.name).to eq('Gamma')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'handles nested task resources' do
|
|
49
|
+
client = described_class.new(base_url)
|
|
50
|
+
client.authenticate(:basic, user: 'user', password: 'pass')
|
|
51
|
+
|
|
52
|
+
project = client.project.list.first
|
|
53
|
+
tasks = client.project.task.list(project.id)
|
|
54
|
+
expect(tasks).to be_a(Array)
|
|
55
|
+
|
|
56
|
+
task = client.project(project.id).task.create(label: 'Do it')
|
|
57
|
+
expect(task.id).to be_a(Integer)
|
|
58
|
+
expect(task.label).to eq('Do it')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'raises validation error on missing required params' do
|
|
62
|
+
client = described_class.new(base_url)
|
|
63
|
+
client.authenticate(:basic, user: 'user', password: 'pass')
|
|
64
|
+
|
|
65
|
+
expect { client.project.create({}) }
|
|
66
|
+
.to raise_error(HaveAPI::Client::ValidationError)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'supports blocking actions and action state polling' do
|
|
70
|
+
client = described_class.new(base_url)
|
|
71
|
+
client.authenticate(:basic, user: 'user', password: 'pass')
|
|
72
|
+
|
|
73
|
+
project = client.project.create(name: 'Blocky')
|
|
74
|
+
task = client.project(project.id).task.create(label: 'Run me')
|
|
75
|
+
|
|
76
|
+
response = client.project.task.run(project.id, task.id, meta: { block: false })
|
|
77
|
+
expect(response.meta[:action_state_id]).to be_a(Integer)
|
|
78
|
+
|
|
79
|
+
result = response.wait_for_completion(interval: 0.05, update_in: 0.05, timeout: 2)
|
|
80
|
+
expect(result).to be(true)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'surfaces HaveAPI errors as ActionFailed' do
|
|
84
|
+
client = described_class.new(base_url)
|
|
85
|
+
|
|
86
|
+
expect { client.test.fail }
|
|
87
|
+
.to raise_error(HaveAPI::Client::ActionFailed) do |err|
|
|
88
|
+
expect(err.response.message).to eq('forced failure')
|
|
89
|
+
expect(err.response.errors).to include(:base)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe HaveAPI::Client::Client do
|
|
6
|
+
let(:client) { described_class.new(TEST_SERVER.base_url) }
|
|
7
|
+
let(:valid_params) do
|
|
8
|
+
{
|
|
9
|
+
i: 1,
|
|
10
|
+
f: 1.0,
|
|
11
|
+
b: true,
|
|
12
|
+
dt: '2020-01-01T00:00:00Z',
|
|
13
|
+
s: 'x',
|
|
14
|
+
t: 'y'
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'coerces valid typed inputs' do
|
|
19
|
+
res = client.test.echo(
|
|
20
|
+
i: ' 42 ',
|
|
21
|
+
f: 5,
|
|
22
|
+
b: 'yes',
|
|
23
|
+
dt: '2020-01-01T00:00:00Z',
|
|
24
|
+
s: 123,
|
|
25
|
+
t: false
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(res).to be_a(HaveAPI::Client::Response)
|
|
29
|
+
expect(res[:i]).to eq(42)
|
|
30
|
+
expect(res[:f]).to eq(5.0)
|
|
31
|
+
expect(res[:b]).to be(true)
|
|
32
|
+
expect(res[:dt]).to match(/\A2020-01-01T00:00:00(?:Z|\+00:00)\z/)
|
|
33
|
+
expect(res[:s]).to eq('123')
|
|
34
|
+
expect(res[:t]).to eq('false')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'accepts exponent float strings' do
|
|
38
|
+
res = client.test.echo(
|
|
39
|
+
i: 1,
|
|
40
|
+
f: '1e3',
|
|
41
|
+
b: true,
|
|
42
|
+
dt: '2020-01-01',
|
|
43
|
+
s: 'ok',
|
|
44
|
+
t: 'ok'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(res[:f]).to eq(1000.0)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'rejects invalid integer strings' do
|
|
51
|
+
expect { client.test.echo(valid_params.merge(i: 'abc')) }
|
|
52
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
53
|
+
expect(err.errors).to include(:i)
|
|
54
|
+
expect(err.errors[:i]).to include(a_string_matching(/not a valid integer/))
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'rejects non-integral floats for integers' do
|
|
59
|
+
expect { client.test.echo(valid_params.merge(i: 12.3)) }
|
|
60
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
61
|
+
expect(err.errors).to include(:i)
|
|
62
|
+
expect(err.errors[:i]).to include(a_string_matching(/not a valid integer/))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'rejects invalid floats' do
|
|
67
|
+
expect { client.test.echo(valid_params.merge(f: 'abc')) }
|
|
68
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
69
|
+
expect(err.errors).to include(:f)
|
|
70
|
+
expect(err.errors[:f]).to include(a_string_matching(/not a valid float/))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'rejects invalid boolean strings' do
|
|
75
|
+
expect { client.test.echo(valid_params.merge(b: 'maybe')) }
|
|
76
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
77
|
+
expect(err.errors).to include(:b)
|
|
78
|
+
expect(err.errors[:b]).to include(a_string_matching(/not a valid boolean/))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'rejects invalid boolean integers' do
|
|
83
|
+
expect { client.test.echo(valid_params.merge(b: 2)) }
|
|
84
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
85
|
+
expect(err.errors).to include(:b)
|
|
86
|
+
expect(err.errors[:b]).to include(a_string_matching(/not a valid boolean/))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'rejects invalid datetimes' do
|
|
91
|
+
expect { client.test.echo(valid_params.merge(dt: 'yesterday')) }
|
|
92
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
93
|
+
expect(err.errors).to include(:dt)
|
|
94
|
+
expect(err.errors[:dt]).to include(a_string_matching(/ISO 8601/))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'rejects arrays for string params' do
|
|
99
|
+
expect { client.test.echo(valid_params.merge(s: [1, 2])) }
|
|
100
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
101
|
+
expect(err.errors).to include(:s)
|
|
102
|
+
expect(err.errors[:s]).to include(a_string_matching(/not a valid string/))
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'rejects hashes for text params' do
|
|
107
|
+
expect { client.test.echo(valid_params.merge(t: { a: 1 })) }
|
|
108
|
+
.to raise_error(HaveAPI::Client::ValidationError) do |err|
|
|
109
|
+
expect(err.errors).to include(:t)
|
|
110
|
+
expect(err.errors[:t]).to include(a_string_matching(/not a valid string/))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'haveapi/client'
|
|
5
|
+
|
|
6
|
+
require_relative 'support/test_server'
|
|
7
|
+
|
|
8
|
+
TEST_SERVER = ClientTestServer.new
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
config.order = :random
|
|
12
|
+
|
|
13
|
+
config.before(:suite) do
|
|
14
|
+
TEST_SERVER.start
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.after(:suite) do
|
|
18
|
+
TEST_SERVER.stop!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.before do
|
|
22
|
+
TEST_SERVER.reset!
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'timeout'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
class ClientTestServer
|
|
9
|
+
READY_PREFIX = 'HAVEAPI_TEST_SERVER_READY'
|
|
10
|
+
|
|
11
|
+
attr_reader :base_url
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@root = File.expand_path('../../../..', __dir__)
|
|
15
|
+
@server_script = File.join(@root, 'servers', 'ruby', 'test_support', 'client_test_server.rb')
|
|
16
|
+
@gemfile = File.join(@root, 'servers', 'ruby', 'Gemfile')
|
|
17
|
+
@cwd = File.join(@root, 'clients', 'ruby')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start
|
|
21
|
+
return if @wait_thr
|
|
22
|
+
|
|
23
|
+
env = { 'BUNDLE_GEMFILE' => @gemfile }
|
|
24
|
+
cmd = ['bundle', 'exec', 'ruby', @server_script, '--port', '0']
|
|
25
|
+
@stdin, @stdout, @wait_thr = Open3.popen2e(env, *cmd, chdir: @cwd)
|
|
26
|
+
|
|
27
|
+
read_ready!
|
|
28
|
+
wait_for_health!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reset!
|
|
32
|
+
ensure_started!
|
|
33
|
+
|
|
34
|
+
uri = URI.join(@base_url, '/__reset')
|
|
35
|
+
req = Net::HTTP::Post.new(uri)
|
|
36
|
+
req['Content-Type'] = 'application/json'
|
|
37
|
+
res = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
|
|
38
|
+
|
|
39
|
+
return if res.is_a?(Net::HTTPSuccess)
|
|
40
|
+
|
|
41
|
+
raise "reset failed: #{res.code} #{res.body}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop!
|
|
45
|
+
return unless @wait_thr
|
|
46
|
+
|
|
47
|
+
Process.kill('TERM', @wait_thr.pid)
|
|
48
|
+
@wait_thr.value
|
|
49
|
+
rescue Errno::ESRCH
|
|
50
|
+
nil
|
|
51
|
+
ensure
|
|
52
|
+
@stdin&.close
|
|
53
|
+
@stdout&.close
|
|
54
|
+
@wait_thr = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def ensure_started!
|
|
60
|
+
start unless @wait_thr
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def read_ready!
|
|
64
|
+
Timeout.timeout(10) do
|
|
65
|
+
while (line = @stdout.gets)
|
|
66
|
+
next unless line.include?(READY_PREFIX)
|
|
67
|
+
|
|
68
|
+
@base_url = line.split.last&.strip
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
raise 'server did not start' unless @base_url
|
|
74
|
+
rescue Timeout::Error
|
|
75
|
+
raise 'server did not start in time'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def wait_for_health!
|
|
79
|
+
Timeout.timeout(5) do
|
|
80
|
+
loop do
|
|
81
|
+
begin
|
|
82
|
+
uri = URI.join(@base_url, '/__health')
|
|
83
|
+
res = Net::HTTP.get_response(uri)
|
|
84
|
+
return if res.is_a?(Net::HTTPSuccess)
|
|
85
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, IOError
|
|
86
|
+
# retry
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
sleep 0.05
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
rescue Timeout::Error
|
|
93
|
+
raise 'server did not become healthy in time'
|
|
94
|
+
end
|
|
95
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -151,6 +151,10 @@ files:
|
|
|
151
151
|
- lib/haveapi/client/version.rb
|
|
152
152
|
- lib/restclient_ext/resource.rb
|
|
153
153
|
- shell.nix
|
|
154
|
+
- spec/integration/client_spec.rb
|
|
155
|
+
- spec/integration/typed_input_spec.rb
|
|
156
|
+
- spec/spec_helper.rb
|
|
157
|
+
- spec/support/test_server.rb
|
|
154
158
|
homepage: ''
|
|
155
159
|
licenses:
|
|
156
160
|
- MIT
|