forward 0.0.1 → 0.0.11

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.
@@ -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