scalingo 3.0.0.beta.1 → 3.0.0.beta.2
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/.gitignore +1 -1
- data/CHANGELOG.md +13 -1
- data/README.md +41 -27
- data/lib/scalingo.rb +1 -1
- data/lib/scalingo/api/client.rb +34 -17
- data/lib/scalingo/api/endpoint.rb +1 -1
- data/lib/scalingo/api/response.rb +21 -32
- data/lib/scalingo/bearer_token.rb +11 -5
- data/lib/scalingo/billing.rb +11 -0
- data/lib/scalingo/billing/profile.rb +46 -0
- data/lib/scalingo/client.rb +52 -26
- data/lib/scalingo/configuration.rb +98 -0
- data/lib/scalingo/regional/addons.rb +2 -2
- data/lib/scalingo/regional/containers.rb +1 -1
- data/lib/scalingo/regional/events.rb +2 -2
- data/lib/scalingo/regional/logs.rb +1 -1
- data/lib/scalingo/regional/metrics.rb +1 -1
- data/lib/scalingo/regional/notifiers.rb +1 -1
- data/lib/scalingo/regional/operations.rb +9 -1
- data/lib/scalingo/version.rb +1 -1
- data/samples/billing/profile/_meta.json +23 -0
- data/samples/billing/profile/create-201.json +50 -0
- data/samples/billing/profile/create-400.json +27 -0
- data/samples/billing/profile/create-422.json +44 -0
- data/samples/billing/profile/show-200.json +41 -0
- data/samples/billing/profile/show-404.json +22 -0
- data/samples/billing/profile/update-200.json +47 -0
- data/samples/billing/profile/update-422.json +32 -0
- data/scalingo.gemspec +1 -3
- data/spec/scalingo/api/client_spec.rb +168 -0
- data/spec/scalingo/api/endpoint_spec.rb +30 -0
- data/spec/scalingo/api/response_spec.rb +285 -0
- data/spec/scalingo/auth_spec.rb +15 -0
- data/spec/scalingo/bearer_token_spec.rb +72 -0
- data/spec/scalingo/billing/profile_spec.rb +55 -0
- data/spec/scalingo/client_spec.rb +93 -0
- data/spec/scalingo/configuration_spec.rb +55 -0
- data/spec/scalingo/regional/operations_spec.rb +11 -3
- data/spec/scalingo/regional_spec.rb +14 -0
- metadata +33 -40
- data/Gemfile.lock +0 -110
- data/lib/scalingo/config.rb +0 -38
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profile",
|
3
|
+
"method": "get",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token"
|
7
|
+
}
|
8
|
+
},
|
9
|
+
"response": {
|
10
|
+
"status": 200,
|
11
|
+
"headers": {
|
12
|
+
"Date": "Fri, 12 Jun 2020 15:46:10 GMT",
|
13
|
+
"Etag": "W/\"d1a1d2eed6a6fc5fb399b497a4572e89\"",
|
14
|
+
"Content-Type": "application/json; charset=utf-8",
|
15
|
+
"Transfer-Encoding": "chunked",
|
16
|
+
"Connection": "keep-alive",
|
17
|
+
"Cache-Control": "max-age=0, private, must-revalidate"
|
18
|
+
},
|
19
|
+
"json_body": {
|
20
|
+
"profile": {
|
21
|
+
"id": "882d7733-923b-40dc-88e6-f1324e48c42a",
|
22
|
+
"name": "Billing",
|
23
|
+
"email": null,
|
24
|
+
"address_line1": "Somewhere",
|
25
|
+
"address_line2": null,
|
26
|
+
"address_city": "Somecity",
|
27
|
+
"balance": 0,
|
28
|
+
"address_zip": "12345",
|
29
|
+
"address_state": null,
|
30
|
+
"address_country": "FR",
|
31
|
+
"vat_number": null,
|
32
|
+
"company": null,
|
33
|
+
"payment_method": "sepa",
|
34
|
+
"created_at": "2020-05-29T13:01:46.217Z",
|
35
|
+
"updated_at": "2020-06-12T15:45:43.535Z",
|
36
|
+
"credit": 0,
|
37
|
+
"stripe_payment_method": null
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profile",
|
3
|
+
"method": "get",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token"
|
7
|
+
}
|
8
|
+
},
|
9
|
+
"response": {
|
10
|
+
"status": 404,
|
11
|
+
"headers": {
|
12
|
+
"Date": "Fri, 12 Jun 2020 15:46:10 GMT",
|
13
|
+
"Content-Type": "application/json; charset=utf-8",
|
14
|
+
"Transfer-Encoding": "chunked",
|
15
|
+
"Connection": "keep-alive",
|
16
|
+
"Cache-Control": "no-cache"
|
17
|
+
},
|
18
|
+
"json_body": {
|
19
|
+
"error": "no billing profile"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profiles/882d7733-923b-40dc-88e6-f1324e48c42a",
|
3
|
+
"method": "put",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token",
|
7
|
+
"Content-Type": "application/json"
|
8
|
+
},
|
9
|
+
"json_body": {
|
10
|
+
"profile": {
|
11
|
+
"address_city": "New Somecity"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"response": {
|
16
|
+
"status": 200,
|
17
|
+
"headers": {
|
18
|
+
"Date": "Fri, 12 Jun 2020 15:46:11 GMT",
|
19
|
+
"Etag": "W/\"885ba0ce422a076feb961e3bed89db0c\"",
|
20
|
+
"Content-Type": "application/json; charset=utf-8",
|
21
|
+
"Transfer-Encoding": "chunked",
|
22
|
+
"Connection": "keep-alive",
|
23
|
+
"Cache-Control": "max-age=0, private, must-revalidate"
|
24
|
+
},
|
25
|
+
"json_body": {
|
26
|
+
"profile": {
|
27
|
+
"id": "882d7733-923b-40dc-88e6-f1324e48c42a",
|
28
|
+
"name": "Billing",
|
29
|
+
"email": null,
|
30
|
+
"address_line1": "Somewhere",
|
31
|
+
"address_line2": null,
|
32
|
+
"address_city": "New Somecity",
|
33
|
+
"balance": 0,
|
34
|
+
"address_zip": "12345",
|
35
|
+
"address_state": null,
|
36
|
+
"address_country": "FR",
|
37
|
+
"vat_number": null,
|
38
|
+
"company": null,
|
39
|
+
"payment_method": "sepa",
|
40
|
+
"created_at": "2020-05-29T13:01:46.217Z",
|
41
|
+
"updated_at": "2020-06-12T15:46:11.559Z",
|
42
|
+
"credit": 0,
|
43
|
+
"stripe_payment_method": null
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profiles/882d7733-923b-40dc-88e6-f1324e48c42a",
|
3
|
+
"method": "put",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token",
|
7
|
+
"Content-Type": "application/json"
|
8
|
+
},
|
9
|
+
"json_body": {
|
10
|
+
"profile": {
|
11
|
+
"address_country": "not a country"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"response": {
|
16
|
+
"status": 422,
|
17
|
+
"headers": {
|
18
|
+
"Date": "Fri, 12 Jun 2020 15:46:11 GMT",
|
19
|
+
"Content-Type": "application/json; charset=utf-8",
|
20
|
+
"Transfer-Encoding": "chunked",
|
21
|
+
"Connection": "keep-alive",
|
22
|
+
"Cache-Control": "no-cache"
|
23
|
+
},
|
24
|
+
"json_body": {
|
25
|
+
"errors": {
|
26
|
+
"address_country": [
|
27
|
+
"is not a valid country"
|
28
|
+
]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
data/scalingo.gemspec
CHANGED
@@ -35,14 +35,12 @@ Gem::Specification.new do |s|
|
|
35
35
|
s.test_files = Dir["spec/**/*_spec.rb"]
|
36
36
|
|
37
37
|
s.add_dependency "activesupport", [">= 5", "< 7"]
|
38
|
-
s.add_dependency "activemodel", [">= 5", "< 7"]
|
39
|
-
s.add_dependency "dry-configurable", "~> 0.11"
|
40
38
|
s.add_dependency "faraday", "~> 1.0.1"
|
41
39
|
s.add_dependency "faraday_middleware", "~> 1.0.0"
|
42
40
|
s.add_dependency "multi_json", ">= 1.0.3", "~> 1.0"
|
43
41
|
|
44
42
|
s.add_development_dependency "bundler", "~> 2.0"
|
45
|
-
s.add_development_dependency "rake", "~>
|
43
|
+
s.add_development_dependency "rake", "~> 13.0"
|
46
44
|
s.add_development_dependency "rspec", "~> 3.0"
|
47
45
|
s.add_development_dependency "rubocop", "~> 0.83.0"
|
48
46
|
s.add_development_dependency "standard", "~> 0.4.2"
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Scalingo::API::Client do
|
4
|
+
let(:url) { "http://localhost" }
|
5
|
+
|
6
|
+
subject { described_class.new(scalingo, url) }
|
7
|
+
|
8
|
+
describe "initialize" do
|
9
|
+
it "stores the scalingo client and the url" do
|
10
|
+
instance = described_class.new(:scalingo, :url)
|
11
|
+
|
12
|
+
expect(instance.scalingo).to eq(:scalingo)
|
13
|
+
expect(instance.url).to eq(:url)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "self.register_handler(s)!" do
|
18
|
+
it "is called for each key/value pair" do
|
19
|
+
expect(described_class).to receive(:register_handler!).with(:a, :b).once
|
20
|
+
expect(described_class).to receive(:register_handler!).with(:c, :d).once
|
21
|
+
|
22
|
+
described_class.register_handlers!(a: :b, c: :d)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "defines a lazy-loaded memoized getter, returning an instance of the class supplied" do
|
26
|
+
mock = double
|
27
|
+
|
28
|
+
described_class.register_handler!(:handler, mock)
|
29
|
+
instance = described_class.new(:scalingo, :url)
|
30
|
+
|
31
|
+
# Only 1 instanciation should be done, no matter how many calls are done below
|
32
|
+
expect(mock).to receive(:new).with(instance).and_return("1st").once
|
33
|
+
|
34
|
+
# Not yet loaded...
|
35
|
+
expect(instance.instance_variable_get(:"@handler")).to eq(nil)
|
36
|
+
instance.handler
|
37
|
+
|
38
|
+
# Memoized...
|
39
|
+
expect(instance.instance_variable_get(:"@handler")).not_to eq(nil)
|
40
|
+
|
41
|
+
# More calls won't try to perform more instanciations
|
42
|
+
instance.handler
|
43
|
+
instance.handler
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "headers" do
|
48
|
+
before do
|
49
|
+
expect(scalingo.config).to receive(:user_agent).and_return(user_agent).once
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:user_agent) { "user agent" }
|
53
|
+
let(:extra_hash) { {"X-Other" => "other"} }
|
54
|
+
let(:extra_block) {
|
55
|
+
proc { {"X-Another" => "another"} }
|
56
|
+
}
|
57
|
+
|
58
|
+
it "only returns the user agent if nothing else is configured" do
|
59
|
+
expect(subject.headers).to eq("User-Agent" => user_agent)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "allows additional headers to be globally configured" do
|
63
|
+
expect(scalingo.config).to receive(:additional_headers).and_return(extra_hash)
|
64
|
+
|
65
|
+
expect(subject.headers).to eq("User-Agent" => user_agent, "X-Other" => "other")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "additional headers can be a block" do
|
69
|
+
expect(scalingo.config).to receive(:additional_headers).and_return(extra_block)
|
70
|
+
|
71
|
+
expect(subject.headers).to eq("User-Agent" => user_agent, "X-Another" => "another")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "connection_options" do
|
76
|
+
it "returns the url and headers" do
|
77
|
+
expect(subject).to receive(:url).and_return("url").once
|
78
|
+
expect(subject).to receive(:headers).and_return("headers").once
|
79
|
+
|
80
|
+
expect(subject.connection_options).to eq(url: "url", headers: "headers")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "unauthenticated_connection" do
|
85
|
+
it "returns a memoized object" do
|
86
|
+
expect(Faraday).to receive(:new).with(subject.connection_options).and_return("faraday").once
|
87
|
+
|
88
|
+
expect(subject.unauthenticated_connection).to eq "faraday"
|
89
|
+
|
90
|
+
subject.unauthenticated_connection
|
91
|
+
subject.unauthenticated_connection
|
92
|
+
end
|
93
|
+
|
94
|
+
it "has no authentication header set" do
|
95
|
+
expect(subject.unauthenticated_connection.headers.key?("Authorization")).not_to be true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "authenticated_connection" do
|
100
|
+
context "without bearer token" do
|
101
|
+
let(:scalingo) { scalingo_guest }
|
102
|
+
|
103
|
+
it "raises if configured to" do
|
104
|
+
expect(scalingo.config).to receive(:raise_on_missing_authentication).and_return(true).once
|
105
|
+
|
106
|
+
expect {
|
107
|
+
subject.authenticated_connection
|
108
|
+
}.to raise_error(Scalingo::Error::Unauthenticated)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "returns an unauthenticated connection if configured to not raise" do
|
112
|
+
expect(scalingo.config).to receive(:raise_on_missing_authentication).and_return(false).once
|
113
|
+
|
114
|
+
expect(subject).to receive(:unauthenticated_connection).and_return(:object).once
|
115
|
+
expect(subject.authenticated_connection).to eq(:object)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "with bearer token" do
|
120
|
+
it "has an authentication header set with a bearer scheme" do
|
121
|
+
expect(subject.connection.headers["Authorization"]).to eq "Bearer #{subject.scalingo.token.value}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "connection" do
|
127
|
+
context "logged" do
|
128
|
+
context "no fallback to guest" do
|
129
|
+
it "calls and return the authenticated_connection" do
|
130
|
+
expect(subject).to receive(:authenticated_connection).and_return(:conn)
|
131
|
+
expect(subject.connection).to eq(:conn)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with fallback to guest" do
|
136
|
+
it "calls and return the authenticated_connection" do
|
137
|
+
expect(subject).to receive(:authenticated_connection).and_return(:conn)
|
138
|
+
expect(subject.connection(fallback_to_guest: true)).to eq(:conn)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "not logged" do
|
144
|
+
let(:scalingo) { scalingo_guest }
|
145
|
+
|
146
|
+
context "no fallback to guest" do
|
147
|
+
it "raises when set to raise" do
|
148
|
+
expect(scalingo.config).to receive(:raise_on_missing_authentication).and_return(true).once
|
149
|
+
|
150
|
+
expect { subject.connection }.to raise_error(Scalingo::Error::Unauthenticated)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "calls and return the unauthenticated_connection when set not to raise" do
|
154
|
+
expect(scalingo.config).to receive(:raise_on_missing_authentication).and_return(false).once
|
155
|
+
expect(subject).to receive(:unauthenticated_connection).and_return(:conn)
|
156
|
+
expect(subject.connection(fallback_to_guest: true)).to eq(:conn)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "with fallback to guest" do
|
161
|
+
it "calls and return the unauthenticated_connection" do
|
162
|
+
expect(subject).to receive(:unauthenticated_connection).and_return(:conn)
|
163
|
+
expect(subject.connection(fallback_to_guest: true)).to eq(:conn)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Scalingo::API::Endpoint do
|
4
|
+
let(:client) { double }
|
5
|
+
|
6
|
+
subject { described_class.new(client) }
|
7
|
+
|
8
|
+
describe "initialize" do
|
9
|
+
it "stores the client" do
|
10
|
+
instance = described_class.new(:client)
|
11
|
+
|
12
|
+
expect(instance.client).to eq(:client)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "connection" do
|
17
|
+
it "delegates the connection to the client" do
|
18
|
+
expect(client).to receive(:connection).and_return(:value).once
|
19
|
+
expect(subject.connection).to eq :value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "unpack" do
|
24
|
+
it "forwards unpack to Response" do
|
25
|
+
expect(Scalingo::API::Response).to receive(:unpack).with(client, :a, :b, :c).and_return(:d).once
|
26
|
+
|
27
|
+
expect(subject.send(:unpack, :a, :b, :c)).to eq :d
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Scalingo::API::Response do
|
4
|
+
let(:client) { regional }
|
5
|
+
let(:status) { 200 }
|
6
|
+
let(:headers) { {} }
|
7
|
+
let(:data) { "" }
|
8
|
+
let(:full_body) { "" }
|
9
|
+
let(:meta_object) { nil }
|
10
|
+
|
11
|
+
subject {
|
12
|
+
described_class.new(
|
13
|
+
client: client,
|
14
|
+
status: status,
|
15
|
+
headers: headers,
|
16
|
+
data: data,
|
17
|
+
full_body: full_body,
|
18
|
+
meta: meta_object,
|
19
|
+
)
|
20
|
+
}
|
21
|
+
|
22
|
+
describe "self.unpack" do
|
23
|
+
let(:body) { "" }
|
24
|
+
let(:success) { true }
|
25
|
+
|
26
|
+
let(:response) {
|
27
|
+
OpenStruct.new(
|
28
|
+
body: body,
|
29
|
+
status: status,
|
30
|
+
headers: headers,
|
31
|
+
success?: success,
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
it "passes the client supplied" do
|
36
|
+
object = described_class.unpack(:some_client, response)
|
37
|
+
|
38
|
+
expect(object.client).to eq :some_client
|
39
|
+
end
|
40
|
+
|
41
|
+
it "passes the response status" do
|
42
|
+
object = described_class.unpack(:client, response)
|
43
|
+
|
44
|
+
expect(object.status).to eq status
|
45
|
+
end
|
46
|
+
|
47
|
+
it "passes the response headers" do
|
48
|
+
object = described_class.unpack(:client, response)
|
49
|
+
|
50
|
+
expect(object.headers).to eq headers
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with an empty body" do
|
54
|
+
let(:body) { "" }
|
55
|
+
|
56
|
+
it "without key" do
|
57
|
+
object = described_class.unpack(client, response)
|
58
|
+
|
59
|
+
expect(object.data).to eq ""
|
60
|
+
expect(object.full_body).to eq ""
|
61
|
+
expect(object.meta).to eq nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "ignores key if supplied" do
|
65
|
+
object = described_class.unpack(client, response, key: :key)
|
66
|
+
|
67
|
+
expect(object.data).to eq ""
|
68
|
+
expect(object.full_body).to eq ""
|
69
|
+
expect(object.meta).to eq nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with a nil body" do
|
74
|
+
let(:body) { nil }
|
75
|
+
|
76
|
+
it "without key" do
|
77
|
+
object = described_class.unpack(client, response)
|
78
|
+
|
79
|
+
expect(object.data).to eq nil
|
80
|
+
expect(object.full_body).to eq nil
|
81
|
+
expect(object.meta).to eq nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "ignores key if supplied" do
|
85
|
+
object = described_class.unpack(client, response, key: :key)
|
86
|
+
|
87
|
+
expect(object.data).to eq nil
|
88
|
+
expect(object.full_body).to eq nil
|
89
|
+
expect(object.meta).to eq nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with a string body" do
|
94
|
+
let(:body) { "this is a string body, probably due to an error" }
|
95
|
+
|
96
|
+
it "without key" do
|
97
|
+
object = described_class.unpack(client, response)
|
98
|
+
|
99
|
+
expect(object.data).to eq body
|
100
|
+
expect(object.full_body).to eq body
|
101
|
+
expect(object.meta).to eq nil
|
102
|
+
end
|
103
|
+
|
104
|
+
it "ignores key if supplied" do
|
105
|
+
object = described_class.unpack(client, response, key: :key)
|
106
|
+
|
107
|
+
expect(object.data).to eq body
|
108
|
+
expect(object.full_body).to eq body
|
109
|
+
expect(object.meta).to eq nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with an json (array) body" do
|
114
|
+
let(:body) {
|
115
|
+
[{key: :value}]
|
116
|
+
}
|
117
|
+
|
118
|
+
it "without key" do
|
119
|
+
object = described_class.unpack(client, response)
|
120
|
+
|
121
|
+
expect(object.data).to eq body
|
122
|
+
expect(object.full_body).to eq body
|
123
|
+
expect(object.meta).to eq nil
|
124
|
+
end
|
125
|
+
|
126
|
+
it "ignores key if supplied" do
|
127
|
+
object = described_class.unpack(client, response, key: :root)
|
128
|
+
|
129
|
+
expect(object.data).to eq body
|
130
|
+
expect(object.full_body).to eq body
|
131
|
+
expect(object.meta).to eq nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with a json (hash) body" do
|
136
|
+
let(:body) {
|
137
|
+
{root: {key: :value}}
|
138
|
+
}
|
139
|
+
|
140
|
+
it "without key" do
|
141
|
+
object = described_class.unpack(client, response)
|
142
|
+
|
143
|
+
expect(object.data).to eq body
|
144
|
+
expect(object.full_body).to eq body
|
145
|
+
expect(object.meta).to eq nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it "with valid key" do
|
149
|
+
object = described_class.unpack(client, response, key: :root)
|
150
|
+
|
151
|
+
expect(object.data).to eq({key: :value})
|
152
|
+
expect(object.full_body).to eq body
|
153
|
+
expect(object.meta).to eq nil
|
154
|
+
end
|
155
|
+
|
156
|
+
it "with invalid key" do
|
157
|
+
object = described_class.unpack(client, response, key: :other)
|
158
|
+
|
159
|
+
expect(object.data).to eq nil
|
160
|
+
expect(object.full_body).to eq body
|
161
|
+
expect(object.meta).to eq nil
|
162
|
+
end
|
163
|
+
|
164
|
+
context "with meta" do
|
165
|
+
let(:body) {
|
166
|
+
{root: {key: :value}, meta: {meta1: :value}}
|
167
|
+
}
|
168
|
+
|
169
|
+
it "extracts the meta object" do
|
170
|
+
object = described_class.unpack(client, response)
|
171
|
+
|
172
|
+
expect(object.meta).to eq({meta1: :value})
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "with an error response" do
|
178
|
+
let(:success) { false }
|
179
|
+
let(:body) { {root: {key: :value}} }
|
180
|
+
|
181
|
+
it "does not dig in the response hash, even with a valid key" do
|
182
|
+
object = described_class.unpack(client, response, key: :root)
|
183
|
+
|
184
|
+
expect(object.data).to eq body
|
185
|
+
expect(object.full_body).to eq body
|
186
|
+
expect(object.meta).to eq nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "successful?" do
|
192
|
+
context "is true when 2XX" do
|
193
|
+
let(:status) { 200 }
|
194
|
+
it { expect(subject.successful?).to be true }
|
195
|
+
end
|
196
|
+
|
197
|
+
context "is false when 3XX" do
|
198
|
+
let(:status) { 300 }
|
199
|
+
it { expect(subject.successful?).to be false }
|
200
|
+
end
|
201
|
+
|
202
|
+
context "is false when 4XX" do
|
203
|
+
let(:status) { 400 }
|
204
|
+
it { expect(subject.successful?).to be false }
|
205
|
+
end
|
206
|
+
|
207
|
+
context "is false when 5XX" do
|
208
|
+
let(:status) { 500 }
|
209
|
+
it { expect(subject.successful?).to be false }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe "paginated?" do
|
214
|
+
context "with pagination metadata" do
|
215
|
+
let(:meta_object) {
|
216
|
+
{pagination: {page: 1}}
|
217
|
+
}
|
218
|
+
|
219
|
+
it { expect(subject.paginated?).to be true }
|
220
|
+
end
|
221
|
+
|
222
|
+
context "without pagination metadata" do
|
223
|
+
let(:meta_object) {
|
224
|
+
{messages: []}
|
225
|
+
}
|
226
|
+
|
227
|
+
it { expect(subject.paginated?).to be false }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "operation" do
|
232
|
+
context "with an operation url" do
|
233
|
+
before do
|
234
|
+
load_meta!(api: :regional, folder: :operations)
|
235
|
+
register_stubs!("find-200", api: :regional, folder: :operations)
|
236
|
+
end
|
237
|
+
|
238
|
+
let(:url) {
|
239
|
+
path = "/apps/#{meta[:app_id]}/operations/#{meta[:id]}"
|
240
|
+
File.join(Scalingo::ENDPOINTS[:regional], path)
|
241
|
+
}
|
242
|
+
|
243
|
+
let(:headers) {
|
244
|
+
{location: url}
|
245
|
+
}
|
246
|
+
|
247
|
+
it { expect(subject.operation?).to be true }
|
248
|
+
it { expect(subject.operation_url).to eq url }
|
249
|
+
|
250
|
+
it "can request the operation" do
|
251
|
+
response = subject.operation
|
252
|
+
|
253
|
+
expect(response).to be_successful
|
254
|
+
expect(response.data[:id]).to be_present
|
255
|
+
expect(response.data[:status]).to be_present
|
256
|
+
expect(response.data[:type]).to be_present
|
257
|
+
end
|
258
|
+
|
259
|
+
it "delegates the operation to the given client" do
|
260
|
+
mock = double
|
261
|
+
|
262
|
+
expect(subject.client).to receive(:operations).and_return(mock)
|
263
|
+
expect(mock).to receive(:get).with(url).and_return(:response)
|
264
|
+
|
265
|
+
expect(subject.operation).to eq :response
|
266
|
+
end
|
267
|
+
|
268
|
+
context "when the client doesn't know about operations" do
|
269
|
+
let(:client) { auth }
|
270
|
+
|
271
|
+
it "fails silently" do
|
272
|
+
expect(subject.operation).to eq nil
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context "without an operation url" do
|
278
|
+
let(:meta_object) { {} }
|
279
|
+
|
280
|
+
it { expect(subject.operation?).to be false }
|
281
|
+
it { expect(subject.operation_url).to eq nil }
|
282
|
+
it { expect(subject.operation).to eq nil }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|