cfoundry 0.4.21 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/Rakefile +47 -24
  2. data/lib/cfoundry/auth_token.rb +48 -0
  3. data/lib/cfoundry/baseclient.rb +96 -277
  4. data/lib/cfoundry/client.rb +2 -0
  5. data/lib/cfoundry/concerns/login_helpers.rb +13 -0
  6. data/lib/cfoundry/errors.rb +21 -13
  7. data/lib/cfoundry/rest_client.rb +290 -0
  8. data/lib/cfoundry/test_support.rb +3 -0
  9. data/lib/cfoundry/trace_helpers.rb +9 -9
  10. data/lib/cfoundry/uaaclient.rb +66 -74
  11. data/lib/cfoundry/upload_helpers.rb +2 -0
  12. data/lib/cfoundry/v1/app.rb +2 -2
  13. data/lib/cfoundry/v1/base.rb +4 -51
  14. data/lib/cfoundry/v1/client.rb +7 -30
  15. data/lib/cfoundry/v1/model.rb +22 -5
  16. data/lib/cfoundry/v1/model_magic.rb +30 -15
  17. data/lib/cfoundry/v2/app.rb +2 -5
  18. data/lib/cfoundry/v2/base.rb +10 -74
  19. data/lib/cfoundry/v2/client.rb +19 -30
  20. data/lib/cfoundry/v2/domain.rb +1 -4
  21. data/lib/cfoundry/v2/model.rb +1 -3
  22. data/lib/cfoundry/v2/model_magic.rb +13 -23
  23. data/lib/cfoundry/version.rb +1 -1
  24. data/lib/cfoundry/zip.rb +1 -1
  25. data/spec/cfoundry/auth_token_spec.rb +77 -0
  26. data/spec/cfoundry/baseclient_spec.rb +54 -30
  27. data/spec/cfoundry/errors_spec.rb +10 -13
  28. data/spec/cfoundry/rest_client_spec.rb +238 -0
  29. data/spec/cfoundry/trace_helpers_spec.rb +10 -5
  30. data/spec/cfoundry/uaaclient_spec.rb +141 -114
  31. data/spec/cfoundry/upload_helpers_spec.rb +129 -0
  32. data/spec/cfoundry/v1/base_spec.rb +2 -2
  33. data/spec/cfoundry/v1/client_spec.rb +17 -0
  34. data/spec/cfoundry/v1/model_magic_spec.rb +43 -0
  35. data/spec/cfoundry/v2/base_spec.rb +256 -33
  36. data/spec/cfoundry/v2/client_spec.rb +68 -0
  37. data/spec/cfoundry/v2/model_magic_spec.rb +49 -0
  38. data/spec/fixtures/apps/with_vmcignore/ignored_dir/file_in_ignored_dir.txt +1 -0
  39. data/spec/fixtures/apps/with_vmcignore/ignored_file.txt +1 -0
  40. data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/file_in_non_ignored_dir.txt +1 -0
  41. data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/ignored_file.txt +1 -0
  42. data/spec/fixtures/apps/with_vmcignore/non_ignored_file.txt +1 -0
  43. data/spec/fixtures/empty_file +0 -0
  44. data/spec/spec_helper.rb +4 -4
  45. data/spec/support/randoms.rb +3 -0
  46. data/spec/support/shared_examples/client_login_examples.rb +46 -0
  47. data/spec/support/{summaries.rb → shared_examples/model_summary_examples.rb} +0 -0
  48. data/spec/support/v1_fake_helper.rb +144 -0
  49. metadata +101 -37
  50. data/lib/cfoundry/spec_helper.rb +0 -1
@@ -1,9 +1,11 @@
1
+ require File.expand_path("../../concerns/login_helpers", __FILE__)
2
+
1
3
  module CFoundry::V2
2
4
  # The primary API entrypoint. Wraps a BaseClient to provide nicer return
3
5
  # values. Initialize with the target and, optionally, an auth token. These
4
6
  # are the only two internal states.
5
7
  class Client
6
- include ClientMethods
8
+ include ClientMethods, CFoundry::LoginHelpers
7
9
 
8
10
  # Internal BaseClient instance. Normally won't be touching this.
9
11
  attr_reader :base
@@ -22,6 +24,10 @@ module CFoundry::V2
22
24
  @base = Base.new(target, token)
23
25
  end
24
26
 
27
+ def version
28
+ 2
29
+ end
30
+
25
31
  # The current target URL of the client.
26
32
  def target
27
33
  @base.target
@@ -76,9 +82,10 @@ module CFoundry::V2
76
82
 
77
83
  # The currently authenticated user.
78
84
  def current_user
79
- if guid = @base.token_data[:user_id]
85
+ token_data = @base.token.token_data
86
+ if guid = token_data[:user_id]
80
87
  user = user(guid)
81
- user.emails = [{ :value => @base.token_data[:email] }]
88
+ user.emails = [{ :value => token_data[:email] }]
82
89
  user
83
90
  end
84
91
  end
@@ -88,36 +95,18 @@ module CFoundry::V2
88
95
  @base.info
89
96
  end
90
97
 
91
- # Login prompts
92
- def login_prompts
93
- if @base.uaa
94
- @base.uaa.prompts
95
- else
96
- { :username => ["text", "Email"],
97
- :password => ["password", "Password"]
98
- }
99
- end
100
- end
101
-
102
- # Authenticate with the target. Sets the client token.
103
- #
104
- # Credentials is a hash, typically containing :username and :password
105
- # keys.
106
- #
107
- # The values in the hash should mirror the prompts given by
108
- # `login_prompts`.
109
- def login(credentials)
98
+ def login(username, password)
110
99
  @current_organization = nil
111
100
  @current_space = nil
101
+ super
102
+ end
112
103
 
113
- @base.token =
114
- if @base.uaa
115
- @base.uaa.authorize(credentials)
116
- else
117
- @base.create_token(
118
- { :password => credentials[:password] },
119
- credentials[:username])[:token]
120
- end
104
+ def register(email, password)
105
+ uaa_user = @base.uaa.add_user(email, password)
106
+ cc_user = user
107
+ cc_user.guid = uaa_user['id']
108
+ cc_user.create!
109
+ cc_user
121
110
  end
122
111
 
123
112
  # Clear client token. No requests are made for this.
@@ -3,12 +3,9 @@ require "cfoundry/v2/model"
3
3
  module CFoundry::V2
4
4
  class Domain < Model
5
5
  attribute :name, :string
6
- attribute :wildcard, :boolean, :default => true
6
+ attribute :wildcard, :boolean
7
7
  to_one :owning_organization, :as => :organization, :default => nil
8
8
 
9
9
  queryable_by :name, :owning_organization_guid, :space_guid
10
-
11
- # hide wildcard support for now
12
- private :wildcard=
13
10
  end
14
11
  end
@@ -72,7 +72,7 @@ module CFoundry::V2
72
72
  @manifest ||= {}
73
73
  @manifest[:entity] ||= {}
74
74
 
75
- self.class.defaults.merge(@manifest[:entity]).each do |k, v|
75
+ @manifest[:entity].each do |k, v|
76
76
  if v.is_a?(Hash) && v.key?(:metadata)
77
77
  # skip; there's a _guid attribute already
78
78
  elsif v.is_a?(Array) && !v.empty? && v.all? { |x|
@@ -87,8 +87,6 @@ module CFoundry::V2
87
87
  x
88
88
  end
89
89
  end
90
- elsif k.to_s.end_with?("_json") && v.is_a?(String)
91
- payload[k] = MultiJson.load(v)
92
90
  elsif k.to_s.end_with?("_url")
93
91
  else
94
92
  payload[k] = v
@@ -63,7 +63,7 @@ module CFoundry::V2
63
63
  end
64
64
 
65
65
  define_method(:"create_#{singular}") do |payload|
66
- post(payload, "v2", plural, :content => :json, :accept => :json)
66
+ post("v2", plural, :content => :json, :accept => :json, :payload => payload)
67
67
  end
68
68
 
69
69
  define_method(:"delete_#{singular}") do |guid|
@@ -72,7 +72,7 @@ module CFoundry::V2
72
72
  end
73
73
 
74
74
  define_method(:"update_#{singular}") do |guid, payload|
75
- put(payload, "v2", plural, guid, :content => :json, :accept => :json)
75
+ put("v2", plural, guid, :content => :json, :accept => :json, :payload => payload)
76
76
  end
77
77
 
78
78
  define_method(plural) do |*args|
@@ -118,8 +118,7 @@ module CFoundry::V2
118
118
  define_method(:"#{singular}_from") do |path, *args|
119
119
  send(
120
120
  :"make_#{singular}",
121
- @base.request_path(
122
- Net::HTTP::Get,
121
+ @base.get(
123
122
  path,
124
123
  :accept => :json,
125
124
  :params => ModelMagic.params_from(args)))
@@ -127,8 +126,7 @@ module CFoundry::V2
127
126
 
128
127
  define_method(:"#{plural}_from") do |path, *args|
129
128
  objs = @base.all_pages(
130
- @base.request_path(
131
- Net::HTTP::Get,
129
+ @base.get(
132
130
  path,
133
131
  :accept => :json,
134
132
  :params => ModelMagic.params_from(args)))
@@ -151,6 +149,7 @@ module CFoundry::V2
151
149
 
152
150
  def attribute(name, type, opts = {})
153
151
  attributes[name] = opts
152
+ json_name = opts[:at] || name
154
153
 
155
154
  default = opts[:default]
156
155
 
@@ -162,8 +161,8 @@ module CFoundry::V2
162
161
  return @cache[name] if @cache.key?(name)
163
162
 
164
163
  @cache[name] =
165
- if manifest[:entity].key?(name)
166
- manifest[:entity][name]
164
+ if manifest[:entity].key?(json_name)
165
+ manifest[:entity][json_name]
167
166
  else
168
167
  default
169
168
  end
@@ -179,11 +178,11 @@ module CFoundry::V2
179
178
  @manifest ||= {}
180
179
  @manifest[:entity] ||= {}
181
180
 
182
- old = @manifest[:entity][name]
181
+ old = @manifest[:entity][json_name]
183
182
  @changes[name] = [old, val] if old != val
184
- @manifest[:entity][name] = val
183
+ @manifest[:entity][json_name] = val
185
184
 
186
- @diff[name] = val
185
+ @diff[json_name] = val
187
186
  end
188
187
  end
189
188
 
@@ -314,10 +313,7 @@ module CFoundry::V2
314
313
  cache << x unless cache.include?(x)
315
314
  end
316
315
 
317
- @client.base.request_path(
318
- Net::HTTP::Put,
319
- ["v2", plural_object_name, @guid, plural, x.guid],
320
- :accept => :json)
316
+ @client.base.put("v2", plural_object_name, @guid, plural, x.guid, :accept => :json)
321
317
  end
322
318
 
323
319
  define_method(:"remove_#{singular}") do |x|
@@ -329,10 +325,7 @@ module CFoundry::V2
329
325
  cache.delete(x)
330
326
  end
331
327
 
332
- @client.base.request_path(
333
- Net::HTTP::Delete,
334
- ["v2", plural_object_name, @guid, plural, x.guid],
335
- :accept => :json)
328
+ @client.base.delete("v2", plural_object_name, @guid, plural, x.guid, :accept => :json)
336
329
  end
337
330
 
338
331
  define_method(:"#{plural}=") do |xs|
@@ -367,10 +360,7 @@ module CFoundry::V2
367
360
 
368
361
  def has_summary(actions = {})
369
362
  define_method(:summary) do
370
- @client.base.request_path(
371
- Net::HTTP::Get,
372
- ["v2", plural_object_name, @guid, "summary"],
373
- :accept => :json)
363
+ @client.base.get("v2", plural_object_name, @guid, "summary", :accept => :json)
374
364
  end
375
365
 
376
366
  define_method(:summarize!) do |*args|
@@ -1,4 +1,4 @@
1
1
  module CFoundry # :nodoc:
2
2
  # CFoundry library version number.
3
- VERSION = "0.4.21".freeze
3
+ VERSION = "0.5.0".freeze
4
4
  end
data/lib/cfoundry/zip.rb CHANGED
@@ -5,7 +5,7 @@ module CFoundry
5
5
  # to use system zip command if necessary.
6
6
  module Zip
7
7
  # Directory entries to exclude from packing.
8
- PACK_EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '*.log']
8
+ PACK_EXCLUSION_GLOBS = %w(.. . *~ #*# *.log)
9
9
 
10
10
  module_function
11
11
 
@@ -0,0 +1,77 @@
1
+ require "base64"
2
+ require "spec_helper"
3
+
4
+ describe CFoundry::AuthToken do
5
+ describe ".from_uaa_token_info" do
6
+ let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id": "a6", "email": "a@b.com"}random-bytes') }
7
+ let(:info_hash) do
8
+ {
9
+ :token_type => "bearer",
10
+ :access_token => access_token,
11
+ :refresh_token => "some-refresh-token"
12
+ }
13
+ end
14
+
15
+
16
+ let(:token_info) { CF::UAA::TokenInfo.new(info_hash) }
17
+
18
+ subject { CFoundry::AuthToken.from_uaa_token_info(token_info) }
19
+
20
+ describe "#auth_header" do
21
+ its(:auth_header) { should eq "bearer #{access_token}" }
22
+ end
23
+
24
+ describe "#to_hash" do
25
+ let(:result_hash) do
26
+ {
27
+ :token => "bearer #{access_token}",
28
+ :refresh_token => "some-refresh-token"
29
+ }
30
+ end
31
+
32
+ its(:to_hash) { should eq result_hash }
33
+ end
34
+
35
+ describe "#token_data" do
36
+ context "when the access token is encoded as expected" do
37
+ its(:token_data) { should eq({ :user_id => "a6", :email => "a@b.com"}) }
38
+ end
39
+
40
+ context "when the access token is not encoded as expected" do
41
+ let(:access_token) { Base64.encode64('random-bytes') }
42
+ its(:token_data) { should eq({}) }
43
+ end
44
+
45
+ context "when the access token contains invalid json" do
46
+ let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id", "a6", "email": "a@b.com"}random-bytes') }
47
+ its(:token_data) { should eq({}) }
48
+ end
49
+ end
50
+ end
51
+
52
+ describe ".from_hash(hash)" do
53
+ let(:token_data) { '{"baz":"buzz"}' }
54
+ let(:token) { Base64.encode64("{\"foo\":1}#{token_data}") }
55
+
56
+ let(:hash) do
57
+ {
58
+ :token => "bearer #{token}",
59
+ :refresh_token => "some-refresh-token"
60
+ }
61
+ end
62
+
63
+ subject { CFoundry::AuthToken.from_hash(hash) }
64
+
65
+ describe "#auth_header" do
66
+ its(:auth_header) { should eq("bearer #{token}") }
67
+ end
68
+
69
+ describe "#to_hash" do
70
+ its(:to_hash) { should eq(hash) }
71
+ end
72
+
73
+ describe "#token_data" do
74
+ its(:token_data) { should eq({ :baz => "buzz" }) }
75
+ end
76
+ end
77
+ end
@@ -1,51 +1,75 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe CFoundry::BaseClient do
4
- let(:base) { CFoundry::BaseClient.new("https://api.cloudfoundry.com") }
4
+ subject { CFoundry::BaseClient.new }
5
5
 
6
- describe '#request_uri' do
7
- subject { base.request_uri URI.parse(base.target + "/foo"), Net::HTTP::Get }
8
-
9
- context 'when a timeout exception occurs' do
10
- before { stub_request(:get, 'https://api.cloudfoundry.com/foo').to_raise(::Timeout::Error) }
6
+ describe "#request" do
7
+ before do
8
+ stub(subject).handle_response(anything, anything, anything)
9
+ end
11
10
 
12
- it 'raises the correct error' do
13
- expect { subject }.to raise_error CFoundry::Timeout, "GET https://api.cloudfoundry.com/foo timed out"
11
+ context "when given multiple segments" do
12
+ it "encodes the segments and joins them with '/'" do
13
+ mock(subject).request_raw("GET", "foo/bar%2Fbaz", {})
14
+ subject.request("GET", "foo", "bar/baz")
14
15
  end
15
16
  end
16
17
 
17
- context 'when an HTTPNotFound error occurs' do
18
- before {
19
-
20
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 404,
21
- :body => "NOT FOUND"
22
- }
18
+ context "when the first segment starts with a '/'" do
19
+ context "and there's only one segment" do
20
+ it "requests with the segment unaltered" do
21
+ mock(subject).request_raw("GET", "/v2/apps", {})
22
+ subject.request("GET", "/v2/apps")
23
+ end
24
+ end
23
25
 
24
- it 'raises the correct error' do
25
- expect {subject}.to raise_error CFoundry::NotFound, "404: NOT FOUND"
26
+ context "and there's more than one segment" do
27
+ it "encodes the segments and joins them with '/'" do
28
+ mock(subject).request_raw("GET", "%2Ffoo/bar%2Fbaz", {})
29
+ subject.request("GET", "/foo", "bar/baz")
30
+ end
26
31
  end
27
32
  end
33
+ end
28
34
 
35
+ describe "UAAClient" do
36
+ before do
37
+ stub(subject).info { { :authorization_endpoint => "http://uaa.example.com" } }
38
+ end
29
39
 
30
- context 'when an HTTPForbidden error occurs' do
31
- before {
32
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 403,
33
- :body => "NONE SHALL PASS"
34
- }
40
+ describe "#uaa" do
41
+ it "creates a UAAClient on the first call" do
42
+ expect(subject.uaa).to be_a CFoundry::UAAClient
43
+ end
44
+
45
+ it "returns the same object on later calls" do
46
+ uaa = subject.uaa
47
+ expect(subject.uaa).to eq uaa
48
+ end
35
49
 
36
- it 'raises the correct error' do
37
- expect {subject}.to raise_error CFoundry::Denied, "403: NONE SHALL PASS"
50
+ it "has the same AuthToken as BaseClient" do
51
+ token = CFoundry::AuthToken.new(nil)
52
+ stub(subject).token { token }
53
+ expect(subject.uaa.token).to eq token
38
54
  end
39
55
  end
40
56
 
41
- context "when any other type of error occurs" do
42
- before {
43
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 411,
44
- :body => "NOT LONG ENOUGH"
45
- }
57
+ describe "#token=" do
58
+ it "propagates the change to #uaa" do
59
+ subject.uaa
60
+ expect(subject.uaa.token).to eq subject.token
61
+ subject.token = CFoundry::AuthToken.new(nil)
62
+ expect(subject.uaa.token).to eq subject.token
63
+ end
64
+ end
46
65
 
47
- it 'raises the correct error' do
48
- expect {subject}.to raise_error CFoundry::BadResponse, "411: NOT LONG ENOUGH"
66
+ describe "#trace=" do
67
+ it "propagates the change to #uaa" do
68
+ subject.uaa
69
+ subject.trace = true
70
+ expect(subject.uaa.trace).to eq true
71
+ subject.trace = false
72
+ expect(subject.uaa.trace).to eq false
49
73
  end
50
74
  end
51
75
  end
@@ -4,23 +4,20 @@ describe 'Errors' do
4
4
  describe CFoundry::Timeout do
5
5
  let(:parent) { Timeout::Error.new }
6
6
 
7
- subject { CFoundry::Timeout.new(Net::HTTP::Post, '/blah', parent) }
7
+ subject { CFoundry::Timeout.new("POST", '/blah', parent) }
8
8
 
9
9
  its(:to_s) { should eq "POST /blah timed out" }
10
- its(:method) { should eq Net::HTTP::Post }
10
+ its(:method) { should eq "POST" }
11
11
  its(:uri) { should eq '/blah' }
12
12
  its(:parent) { should eq parent }
13
13
  end
14
14
 
15
15
  describe CFoundry::APIError do
16
- let(:request) { Net::HTTP::Get.new("http://api.cloudfoundry.com/foo") }
17
- let(:response) { Net::HTTPNotFound.new("foo", 404, "bar")}
16
+ let(:request) { { :method => "GET", :url => "http://api.cloudfoundry.com/foo", :headers => {} } }
18
17
  let(:response_body) { "NOT FOUND" }
19
- subject { CFoundry::APIError.new(request, response) }
18
+ let(:response) { { :status => 404, :headers => {}, :body => response_body } }
20
19
 
21
- before do
22
- stub(response).body {response_body}
23
- end
20
+ subject { CFoundry::APIError.new(nil, nil, request, response) }
24
21
 
25
22
  its(:to_s) { should eq "404: NOT FOUND" }
26
23
 
@@ -35,7 +32,7 @@ describe 'Errors' do
35
32
  let(:response_body) { "{\"description\":\"Something went wrong\"}"}
36
33
 
37
34
  it "sets description to description field in parsed JSON" do
38
- CFoundry::APIError.new(request, response).description.should == "Something went wrong"
35
+ CFoundry::APIError.new(nil, nil, request, response).description.should == "Something went wrong"
39
36
  end
40
37
  end
41
38
 
@@ -45,12 +42,12 @@ describe 'Errors' do
45
42
  let(:response_body) { "Some plain text"}
46
43
 
47
44
  it "sets description to body text" do
48
- CFoundry::APIError.new(request, response).description.should == "Some plain text"
45
+ CFoundry::APIError.new(nil, nil, request, response).description.should == "Some plain text"
49
46
  end
50
47
  end
51
48
 
52
49
  it "allows override of description" do
53
- CFoundry::APIError.new(request, response, "My description").description.should == "My description"
50
+ CFoundry::APIError.new("My description", nil, request, response).description.should == "My description"
54
51
  end
55
52
 
56
53
  end
@@ -64,11 +61,11 @@ describe 'Errors' do
64
61
  end
65
62
 
66
63
  it "sets error code to response error code by default" do
67
- CFoundry::APIError.new(request, response).error_code.should == 404
64
+ CFoundry::APIError.new(nil, nil, request, response).error_code.should == 404
68
65
  end
69
66
 
70
67
  it "allows override of error code" do
71
- CFoundry::APIError.new(request, response, nil, 303).error_code.should == 303
68
+ CFoundry::APIError.new(nil, 303, request, response).error_code.should == 303
72
69
  end
73
70
 
74
71
  end