forward 0.0.1 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ class Hash
2
+ def symbolize_keys
3
+ inject({}) do |options, (key, value)|
4
+ options[(key.to_sym rescue key) || key] = value
5
+ options
6
+ end
7
+ end
8
+
9
+ def symbolize_keys!
10
+ self.replace(self.symbolize_keys)
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Forward
2
+ # An error occurred with the API
3
+ class ApiError < StandardError; end
4
+ # An error occurred with the Client
5
+ class ClientError < StandardError; end
6
+ # An error occurred with the Config
7
+ class ConfigError < StandardError; end
8
+ # An error occurred with the Tunnel
9
+ class TunnelError < StandardError; end
10
+ end
@@ -0,0 +1,58 @@
1
+ module Forward
2
+ class Tunnel
3
+ CHECK_INTERVAL = 7
4
+
5
+ # The Tunnel resource ID.
6
+ attr_reader :id
7
+ # The domain for the Tunnel.
8
+ attr_reader :subdomain
9
+ # The remote port.
10
+ attr_reader :port
11
+ # The tunneler host.
12
+ attr_reader :tunneler
13
+ # The amount of time in seconds the Tunnel has be inactive for
14
+ attr_accessor :inactive_for
15
+
16
+ # Initializes a Tunnel instance for the Client and requests a tunnel from
17
+ # API.
18
+ #
19
+ # client - The Client instance.
20
+ def initialize(options = {})
21
+ @response = Forward::Api::Tunnel.create(options)
22
+ @id = @response[:_id]
23
+ @subdomain = @response[:subdomain]
24
+ @port = @response[:port]
25
+ @tunneler = @response[:tunneler_public]
26
+ @timeout = @response[:timeout]
27
+ @inactive_for = 0
28
+ end
29
+
30
+ def poll_status
31
+ Thread.new {
32
+ loop do
33
+ if @timeout && !@timeout.zero? && @inactive_for > @timeout
34
+ Forward.log(:debug, "Session closing due to inactivity `#{@inactive_for}' seconds")
35
+ Client.cleanup_and_exit!("Tunnel has been inactive for #{@inactive_for} seconds, exiting...")
36
+ elsif Forward::Api::Tunnel.show(@id).nil?
37
+ Client.current.tunnel = nil
38
+ Forward.log(:debug, "Tunnel destroyed, closing session")
39
+ Client.cleanup_and_exit!
40
+ else
41
+ sleep CHECK_INTERVAL
42
+ end
43
+
44
+ @inactive_for += CHECK_INTERVAL
45
+ end
46
+ }
47
+ end
48
+
49
+ def cleanup
50
+ Forward::Api::Tunnel.destroy(@id) if @id
51
+ end
52
+
53
+ def active?
54
+ @active
55
+ end
56
+
57
+ end
58
+ end
@@ -1,3 +1,3 @@
1
1
  module Forward
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.11'
3
3
  end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Api::PublicKey do
4
+
5
+ before :each do
6
+ FakeWeb.allow_net_connect = false
7
+ end
8
+
9
+ it 'retrieves the public_key and returns it' do
10
+ fake_body = { :private_key => 'ssh-key 1234567890' }
11
+
12
+ stub_api_request(:post, '/api/public_keys', :body => fake_body.to_json)
13
+
14
+ response = Api::PublicKey.create
15
+ response.must_equal fake_body[:private_key]
16
+ end
17
+
18
+ it 'exits with message if response has errors' do
19
+ fake_body = { :errors => { :base => 'Unable to retrieve a private key' } }
20
+
21
+ stub_api_request(:post, '/api/public_keys', :body => fake_body.to_json)
22
+
23
+ lambda {
24
+ dev_null { Api::PublicKey.create }
25
+ }.must_raise SystemExit
26
+ end
27
+
28
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Api::Resource do
4
+ Api = Forward::Api
5
+
6
+ before :each do
7
+ Api.token = 'abc123'
8
+ end
9
+
10
+ it 'builds http requests based on given method' do
11
+ resource = Api::Resource.new
12
+ resource.uri = '/path'
13
+
14
+ [ :get, :post, :put, :delete ].each do |method|
15
+ resource.build_request(method)
16
+ klass = eval("Net::HTTP::#{method.capitalize}")
17
+ request = resource.instance_variable_get('@request')
18
+
19
+ request.kind_of?(klass).must_equal true
20
+ end
21
+ end
22
+
23
+ it 'builds http requests with json bodies' do
24
+ resource = Api::Resource.new
25
+ resource.uri = '/path'
26
+
27
+ [ :post, :put, :delete ].each do |method|
28
+ params = { :foo => 'bar '}
29
+ resource.build_request(method, params)
30
+ klass = eval("Net::HTTP::#{method.capitalize}")
31
+ request = resource.instance_variable_get('@request')
32
+
33
+ request.kind_of?(klass).must_equal true
34
+ request.body.must_equal params.to_json
35
+ end
36
+ end
37
+
38
+ it 'adds auth and json headers to the request' do
39
+ resource = Api::Resource.new
40
+ resource.uri = '/path'
41
+ resource.build_request(:post)
42
+ resource.add_headers!
43
+ request = resource.instance_variable_get('@request')
44
+
45
+ request['Authorization'].must_equal "Token token=#{Api.token}"
46
+ request['Content-Type'].must_equal 'application/json'
47
+ request['Accept'].must_equal 'application/json'
48
+ end
49
+
50
+
51
+ it 'raises an error if the response code is not 200' do
52
+ resource = Api::Resource.new
53
+ response = mock
54
+ response.stubs(:code).returns(403)
55
+ response.stubs(:body)
56
+
57
+ lambda { resource.parse_response(response) }.must_raise Api::BadResponse
58
+ end
59
+
60
+ it 'raises an error if the response is not json' do
61
+ resource = Api::Resource.new
62
+ response = mock
63
+ response.stubs(:code).returns(200)
64
+ response.stubs(:body)
65
+ response.stubs(:[]).with('content-type').returns('text/html')
66
+
67
+ lambda { resource.parse_response(response) }.must_raise Api::BadResponse
68
+ end
69
+
70
+ it 'parses a json response' do
71
+ resource = Api::Resource.new
72
+ response = mock
73
+ response.stubs(:code).returns(200)
74
+ response.stubs(:[]).with('content-type').returns('application/json')
75
+ response.stubs(:body).returns('{ "foo": "bar" }')
76
+
77
+ json = resource.parse_response(response)
78
+ json.kind_of?(Hash).must_equal true
79
+ json[:foo].must_equal 'bar'
80
+ end
81
+
82
+ end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Api::Tunnel do
4
+
5
+ before :each do
6
+ FakeWeb.allow_net_connect = false
7
+ Forward::Api.token = 'abc123'
8
+ end
9
+
10
+ it 'creates a tunnel and returns the attributes' do
11
+ Forward.client = mock
12
+ fake_body = { :_id => '1', :subdomain => 'foo', :port => 56789 }
13
+
14
+ Forward.client.expects(:options).returns(:port => 3000)
15
+ stub_api_request(:post, '/api/tunnels', :body => fake_body.to_json)
16
+
17
+ response = Forward::Api::Tunnel.create
18
+
19
+ fake_body.each do |key, value|
20
+ response[key].must_equal fake_body[key]
21
+ end
22
+ end
23
+
24
+ it 'exits with message if create response has errors' do
25
+ Forward.client = mock
26
+ fake_body = { :errors => { :base => [ 'unable to create tunnel' ] } }
27
+
28
+ Forward.client.expects(:options).returns(:port => 3000)
29
+ stub_api_request(:post, '/api/tunnels', :body => fake_body.to_json)
30
+
31
+ lambda {
32
+ dev_null { Forward::Api::Tunnel.create }
33
+ }.must_raise SystemExit
34
+ end
35
+
36
+ it 'gives a choice and closes a tunnel if limit is reached' do
37
+ Forward.client = mock
38
+ post_options = [
39
+ { :body => { :errors => { :base => [ 'you have reached your limit' ] } }.to_json },
40
+ { :body => { :_id => '1', :subdomain => 'foo', :port => 56789 }.to_json }
41
+ ]
42
+ index_body = [ { :_id => 'abc123' }, { :_id => 'def456' } ]
43
+
44
+ Forward.client.expects(:options).returns(:port => 3000).twice
45
+ stub_api_request(:post, '/api/tunnels', post_options)
46
+ stub_api_request(:get, '/api/tunnels', :body => index_body.to_json)
47
+ STDIN.expects(:gets).returns('1')
48
+ Forward::Api::Tunnel.expects(:destroy).with(index_body.first[:_id])
49
+
50
+ dev_null { Forward::Api::Tunnel.create }
51
+ end
52
+
53
+ it 'destroys a tunnel and returns the attributes' do
54
+ Forward.client = mock
55
+ fake_body = { :_id => '1', :subdomain => 'foo', :port => 56789 }
56
+
57
+ stub_api_request(:delete, '/api/tunnels/1', :body => fake_body.to_json)
58
+
59
+ response = Forward::Api::Tunnel.destroy(1)
60
+
61
+ fake_body.each do |key, value|
62
+ response[key].must_equal fake_body[key]
63
+ end
64
+ end
65
+
66
+ it 'gracefully handles the error if destroy has errors' do
67
+ Forward.client = mock
68
+ fake_body = { :errors => { :base => 'unable to create tunnel' } }
69
+
70
+ stub_api_request(:delete, '/api/tunnels/1', :body => fake_body.to_json)
71
+
72
+ Forward::Api::Tunnel.destroy(1).must_be_nil
73
+ end
74
+
75
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Api::User do
4
+
5
+ before :each do
6
+ FakeWeb.allow_net_connect = false
7
+ end
8
+
9
+ it 'retrieves the users api token and returns it' do
10
+ fake_body = { :api_token => '123abc' }
11
+
12
+ stub_api_request(:post, '/api/users/api_token', :body => fake_body.to_json)
13
+
14
+ response = Api::User.api_token('guy@example.com', 'secret')
15
+ response[:api_token].must_equal '123abc'
16
+ end
17
+
18
+ it 'exits with message if response has errors' do
19
+ fake_body = { :errors => { :base => 'Unable to authenticate user' } }
20
+
21
+ stub_api_request(:post, '/api/users/api_token', :body => fake_body.to_json)
22
+
23
+ lambda {
24
+ dev_null { Api::User.api_token('guy@example.com', 'secret') }
25
+ }.must_raise SystemExit
26
+ end
27
+
28
+ end
File without changes
@@ -0,0 +1,84 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::CLI do
4
+
5
+ it 'parses a forwarded port' do
6
+ forwarded = Forward::CLI.parse_forwarded('600')
7
+ forwarded.has_key?(:port).must_equal true
8
+ forwarded[:port].must_equal 600
9
+ end
10
+
11
+ it 'parses a forwarded host' do
12
+ forwarded = Forward::CLI.parse_forwarded('mysite.dev')
13
+ forwarded.has_key?(:host).must_equal true
14
+ forwarded[:host].must_equal 'mysite.dev'
15
+ end
16
+
17
+ it 'parses a forwarded host and port' do
18
+ forwarded = Forward::CLI.parse_forwarded('mysite.dev:88')
19
+ forwarded.has_key?(:host).must_equal true
20
+ forwarded.has_key?(:port).must_equal true
21
+ forwarded[:host].must_equal 'mysite.dev'
22
+ forwarded[:port].must_equal 88
23
+ end
24
+
25
+ it 'parses valid basic auth and returns username and password' do
26
+ username = 'foo'
27
+ password = 'bar'
28
+
29
+ credentials = Forward::CLI.parse_basic_auth("#{username}:#{password}")
30
+ credentials.first.must_equal username
31
+ credentials.last.must_equal password
32
+ end
33
+
34
+ it 'validates basic auth and exits if invalid' do
35
+ [ 'afadsfsdf', 'adsf:', ':bar' ].each do |credentials|
36
+ lambda {
37
+ dev_null { Forward::CLI.validate_basic_auth(credentials) }
38
+ }.must_raise SystemExit
39
+ end
40
+ end
41
+
42
+ it 'doesnt exit on valid ports' do
43
+ Forward::CLI.validate_port(69).must_be_nil
44
+ Forward::CLI.validate_port(3000).must_be_nil
45
+ Forward::CLI.validate_port(65535).must_be_nil
46
+ end
47
+
48
+ it 'validates port and exits if invalid' do
49
+ [ 0, 65536 ].each do |port|
50
+ lambda {
51
+ dev_null { Forward::CLI.validate_port(port) }
52
+ }.must_raise SystemExit
53
+ end
54
+ end
55
+
56
+ it 'doesnt exit on valid cnames' do
57
+ [ 'foo.com', 'whatever-foo.com', 'www.foo.com', 'asdf.asdf.asdf.com' ].each do |cname|
58
+ Forward::CLI.validate_cname(cname).must_be_nil
59
+ end
60
+ end
61
+
62
+ it 'validates cname and exits if invalid' do
63
+ [ 'whatever', 'asdfasdf.', '-asdf', 'adsf#$).com' ].each do |cname|
64
+ lambda {
65
+ dev_null { Forward::CLI.validate_cname(cname) }
66
+ }.must_raise SystemExit
67
+ end
68
+ end
69
+
70
+ it 'doesnt exit on valid subdomains' do
71
+ [ 'foo', 'whatever-foo', 'asdf40' ].each do |subdomain|
72
+ Forward::CLI.validate_subdomain(subdomain).must_be_nil
73
+ end
74
+ end
75
+
76
+ it 'validates subdomain and exits if invalid' do
77
+ [ '-asdf', 'adsf#$)' ].each do |subdomain|
78
+ lambda {
79
+ dev_null { Forward::CLI.validate_subdomain(subdomain) }
80
+ }.must_raise SystemExit
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Client do
4
+
5
+ it 'should have access to api resources via the api method ' do
6
+
7
+ end
8
+ end
@@ -0,0 +1,102 @@
1
+ require 'test_helper'
2
+
3
+ describe Forward::Config do
4
+
5
+ before :each do
6
+ @user_attributes = {
7
+ :id => '12345',
8
+ :api_token => 'abcdefg',
9
+ :private_key => 'secret'
10
+ }
11
+ FileUtils.mkdir_p(ENV['HOME'])
12
+ end
13
+
14
+ after :each do
15
+ if Forward::Config.present?
16
+ FileUtils.rm(Forward::Config::CONFIG_PATH)
17
+ end
18
+ end
19
+
20
+ it "initializes a config with a hash" do
21
+ config = Forward::Config.new(@user_attributes)
22
+
23
+ @user_attributes.each do |key, value|
24
+ config.send(key).must_equal value
25
+ end
26
+ end
27
+
28
+ it "updates a config with a hash" do
29
+ config = Forward::Config.new
30
+ config.update(@user_attributes)
31
+
32
+ @user_attributes.each do |key, value|
33
+ config.send(key).must_equal value
34
+ end
35
+ end
36
+
37
+ it "writes and reads a config from disk" do
38
+ config = Forward::Config.new(@user_attributes)
39
+ config.write
40
+
41
+ saved_config = Forward::Config.load
42
+
43
+ @user_attributes.each do |key, value|
44
+ saved_config.send(key).must_equal value
45
+ end
46
+ end
47
+
48
+ it "converts a config to a hash" do
49
+ config = Forward::Config.new(@user_attributes)
50
+ config_hash = config.to_hash
51
+
52
+ @user_attributes.each do |key, value|
53
+ config_hash[key].must_equal value
54
+ end
55
+ end
56
+
57
+ it "deletes config file" do
58
+ config = Forward::Config.new(@user_attributes)
59
+ config.write
60
+ Forward::Config.clear
61
+
62
+ Forward::Config.wont_be :present?
63
+ end
64
+
65
+ it "raises exception with an empty config" do
66
+ config = Forward::Config.new
67
+
68
+ lambda { config.validate }.must_raise Forward::ConfigError
69
+ end
70
+
71
+ it "raises exception with an invalid config" do
72
+ @user_attributes.delete(:api_token)
73
+ config = Forward::Config.new(@user_attributes)
74
+
75
+ lambda { config.validate }.must_raise Forward::ConfigError
76
+ end
77
+
78
+ it "raises exception with bad config on write" do
79
+ @user_attributes.delete(:private_key)
80
+ config = Forward::Config.new(@user_attributes)
81
+
82
+ lambda { config.write }.must_raise Forward::ConfigError
83
+ end
84
+
85
+ it "raises exception when a config file is not found" do
86
+ lambda { Forward::Config.load }.must_raise Forward::ConfigError
87
+ end
88
+
89
+ it "raises exception when reading bad config file" do
90
+ config_path = Forward::Config::CONFIG_PATH
91
+ config_data = 'the trash alone'
92
+
93
+ File.open(config_path, 'w') { |f| f.write(config_data) }
94
+
95
+ lambda { Forward::Config.read }.must_raise Forward::ConfigError
96
+ end
97
+
98
+ it "raises exception when an invalid config file is loaded" do
99
+ skip
100
+ end
101
+
102
+ end