xplenty-kensa 1.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+ require 'restclient'
2
+ require 'uri'
3
+
4
+ module Xplenty
5
+ module Kensa
6
+ class Sso
7
+ attr_accessor :id, :url, :proxy_port, :timestamp, :token
8
+
9
+ def initialize(data)
10
+ @id = data[:id]
11
+ @salt = data['api']['sso_salt']
12
+
13
+ env = data.fetch :env, 'test'
14
+ if @url = data['api'][env]['sso_url']
15
+ @use_post = true
16
+ @proxy_port = find_available_port
17
+ else
18
+ @url = data["api"][env].chomp('/')
19
+ end
20
+ @timestamp = Time.now.to_i
21
+ @token = make_token(@timestamp)
22
+ end
23
+
24
+ def path
25
+ if self.POST?
26
+ URI.parse(url).path
27
+ else
28
+ "/xplenty/resources/#{id}"
29
+ end
30
+ end
31
+
32
+ def POST?
33
+ @use_post
34
+ end
35
+
36
+ def sso_url
37
+ if self.POST?
38
+ "http://localhost:#{@proxy_port}/"
39
+ else
40
+ full_url
41
+ end
42
+ end
43
+
44
+ def full_url
45
+ [ url, path, querystring ].join
46
+ end
47
+ alias get_url full_url
48
+
49
+ def post_url
50
+ url
51
+ end
52
+
53
+ def timestamp=(other)
54
+ @timestamp = other
55
+ @token = make_token(@timestamp)
56
+ end
57
+
58
+ def make_token(t)
59
+ Digest::SHA1.hexdigest([@id, @salt, t].join(':'))
60
+ end
61
+
62
+ def querystring
63
+ return '' unless @salt
64
+ '?' + query_data
65
+ end
66
+
67
+ def query_data
68
+ query_params.map{|p| p.join('=')}.join('&')
69
+ end
70
+
71
+ def query_params
72
+ { 'token' => @token,
73
+ 'timestamp' => @timestamp.to_s,
74
+ 'nav-data' => sample_nav_data,
75
+ 'email' => 'username@example.com',
76
+ 'app' => 'myapp'
77
+ }.tap do |params|
78
+ params.merge!('id' => @id) if self.POST?
79
+ end
80
+ end
81
+
82
+ def sample_nav_data
83
+ json = OkJson.encode({
84
+ :addon => 'Your Addon',
85
+ :appname => 'myapp',
86
+ :addons => [
87
+ { :slug => 'cron', :name => 'Cron' },
88
+ { :slug => 'custom_domains+wildcard', :name => 'Custom Domains + Wildcard' },
89
+ { :slug => 'youraddon', :name => 'Your Addon', :current => true },
90
+ ]
91
+ })
92
+ base64_url_variant(json)
93
+ end
94
+
95
+ def base64_url_variant(text)
96
+ base64 = [text].pack('m').gsub('=', '').gsub("\n", '')
97
+ base64.tr('+/','-_')
98
+ end
99
+
100
+ def message
101
+ if self.POST?
102
+ "POSTing #{query_data} to #{post_url} via proxy on port #{@proxy_port}"
103
+ else
104
+ "Opening #{full_url}"
105
+ end
106
+ end
107
+
108
+ def start
109
+ run_proxy
110
+ self
111
+ end
112
+
113
+ def find_available_port
114
+ server = TCPServer.new('127.0.0.1', 0)
115
+ server.addr[1]
116
+ ensure
117
+ server.close if server
118
+ end
119
+
120
+ def run_proxy
121
+ return unless self.POST?
122
+ server = PostProxy.new self
123
+ @proxy = server
124
+
125
+ trap("INT") { server.stop }
126
+ pid = fork do
127
+ server.start
128
+ end
129
+ at_exit { server.stop; Process.waitpid pid }
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,5 @@
1
+ module Xplenty
2
+ module Kensa
3
+ VERSION = '1.4.5'
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'xplenty/kensa/version'
2
+ require 'xplenty/kensa/http'
3
+ require 'xplenty/kensa/manifest'
4
+ require 'xplenty/kensa/check'
5
+ require 'xplenty/kensa/sso'
6
+ require 'xplenty/kensa/post_proxy'
7
+ require 'xplenty/kensa/screen'
8
+ require 'xplenty/kensa/git'
9
+ require 'xplenty/kensa/okjson'
10
+ require 'xplenty/kensa/okjson-support'
@@ -0,0 +1,33 @@
1
+ require 'test/helper'
2
+
3
+ class AllCheckTest < Test::Unit::TestCase
4
+ include Xplenty::Kensa
5
+ include ProviderMock
6
+ include FsMock
7
+
8
+ setup do
9
+ @data = Manifest.new(:method => :get).skeleton
10
+ @data['api']['password'] = 'secret'
11
+ @data['api']['test'] += "working"
12
+ @file = File.dirname(__FILE__) + "/resources/runner.rb"
13
+ end
14
+
15
+ def check; AllCheck; end
16
+
17
+ test "valid on script exit 0" do
18
+ @data[:args] = ["ruby #{@file}"]
19
+ assert_valid
20
+ end
21
+
22
+ test "invalid on script exit non 0" do
23
+ @data[:args] = ["ruby #{@file} fail"]
24
+ assert_invalid
25
+ end
26
+
27
+ test "all runs" do
28
+ assert_nothing_raised do
29
+ kensa "init"
30
+ kensa "test all"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ require 'test/helper'
2
+
3
+ class CreateTest < Test::Unit::TestCase
4
+ include Xplenty::Kensa
5
+
6
+ def setup
7
+ stub(Git).run
8
+ any_instance_of Client do |client|
9
+ stub(client).init
10
+ end
11
+ stub(Dir).chdir
12
+ end
13
+
14
+ def test_requires_app_name
15
+ assert_raise Client::CommandInvalid do
16
+ kensa "create my_addon"
17
+ end
18
+ end
19
+
20
+ def test_requires_template
21
+ assert_raise Client::CommandInvalid do
22
+ kensa "create --template foo"
23
+ end
24
+ end
25
+
26
+ def test_assumes_xplenty_template
27
+ kensa "create my_addon --template sinatra"
28
+ assert_received Git do |git|
29
+ git.run("git clone git://github.com/xplenty/kensa-create-sinatra my_addon")
30
+ end
31
+ end
32
+
33
+ def test_assumes_github
34
+ kensa "create my_addon --template xplenty/sinatra"
35
+ assert_received Git do |git|
36
+ git.run("git clone git://github.com/xplenty/sinatra my_addon")
37
+ end
38
+ end
39
+
40
+ def test_allows_full_url
41
+ kensa "create my_addon --template git://xplenty.com/sinatra.git"
42
+ assert_received Git do |git|
43
+ git.run("git clone git://xplenty.com/sinatra.git my_addon")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,39 @@
1
+ require 'test/helper'
2
+
3
+ class DeprovisionCheckTest < Test::Unit::TestCase
4
+ include Xplenty::Kensa
5
+
6
+ %w{get post}.each do |method|
7
+ context "with SSO #{method}" do
8
+ setup do
9
+ @data = Manifest.new(:method => method).skeleton.merge :id => 123
10
+ @responses = [
11
+ [200, ""],
12
+ [401, ""],
13
+ ]
14
+ end
15
+
16
+ def check ; DeprovisionCheck ; end
17
+
18
+ test "valid on 200" do
19
+ assert_valid do |check|
20
+ kensa_stub :delete, check, @responses
21
+ end
22
+ end
23
+
24
+ test "status other than 200" do
25
+ @responses[0] = [500, ""]
26
+ assert_invalid do |check|
27
+ kensa_stub :delete, check, @responses
28
+ end
29
+ end
30
+
31
+ test "runs auth check" do
32
+ @responses[1] = [200, ""]
33
+ assert_invalid do |check|
34
+ kensa_stub :delete, check, @responses
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'xplenty/kensa'
2
+ require 'xplenty/kensa/client'
3
+ require 'contest'
4
+ require 'coveralls'
5
+ require 'timecop'
6
+ require 'rr'
7
+ require 'artifice'
8
+ require 'test/resources/server'
9
+ require 'fakefs/safe'
10
+
11
+ Coveralls.wear!
12
+
13
+ class Test::Unit::TestCase
14
+ include RR::Adapters::TestUnit
15
+
16
+ module ProviderMock
17
+ def setup
18
+ Artifice.activate_with(ProviderServer)
19
+ super
20
+ end
21
+
22
+ def teardown
23
+ Artifice.deactivate
24
+ super
25
+ end
26
+ end
27
+
28
+ module FsMock
29
+ def setup
30
+ FakeFS.activate!
31
+ @filename = 'addon-manifest.json'
32
+ super
33
+ end
34
+
35
+ def teardown
36
+ File.unlink(@filename) if @filename && File.exist?(@filename)
37
+ FakeFS.deactivate!
38
+ super
39
+ end
40
+ end
41
+
42
+ def kensa(command)
43
+ Xplenty::Kensa::Client.new(command.split, :silent => true, :test => true).run!
44
+ end
45
+
46
+ def read_json(filename)
47
+ OkJson.decode(File.open(filename).read)
48
+ end
49
+
50
+ #this prepends a prefix for the provider server
51
+ #in test/resources/server.rb
52
+ def use_provider_endpoint(name, type = 'base')
53
+ if @data['api']['test'].is_a? Hash
54
+ url = @data['api']['test']["#{type}_url"]
55
+ path = URI.parse(url).path
56
+ @data['api']['test']["#{type}_url"] = url.sub(path, "/#{name}#{path}")
57
+ else
58
+ @data['api']['test'] += "#{name}"
59
+ end
60
+ end
61
+
62
+ def trace!
63
+ @screen = Xplenty::Kensa::IOScreen.new(STDOUT)
64
+ end
65
+
66
+ def screen
67
+ @screen ||= Xplenty::Kensa::IOScreen.new(StringIO.new("", 'w+'))
68
+ end
69
+
70
+ # call trace! in your test before the
71
+ # assert to see the output
72
+ def assert_valid(data=@data, &blk)
73
+ check = create_check(data, &blk)
74
+ result = check.call
75
+ assert result, screen.to_s
76
+ end
77
+
78
+ def assert_invalid(data=@data, &blk)
79
+ check = create_check(data, &blk)
80
+ result = check.call
81
+ assert !result, screen.to_s
82
+ end
83
+
84
+ def create_check(data, &blk)
85
+ check = self.check.new(data, screen)
86
+ blk.call(check) if blk
87
+ check
88
+ end
89
+
90
+ module Headerize
91
+ attr_accessor :headers
92
+ end
93
+
94
+ def to_json(data, headers={})
95
+ body = OkJson.encode(data)
96
+ add_headers(body, headers)
97
+ end
98
+
99
+ def add_headers(o, headers={})
100
+ o.extend Headerize
101
+ o.headers = {}
102
+ o.headers["Content-Type"] ||= "application/json"
103
+ o.headers.merge!(headers)
104
+ o
105
+ end
106
+
107
+ def kensa_stub(meth, o, returns)
108
+ o.instance_eval { @returns = Array(returns) }
109
+ eval <<-EVAL
110
+ def o.#{meth}(*args)
111
+ @returns.shift or fail("Nothing else to return from stub'ed method")
112
+ end
113
+ EVAL
114
+ end
115
+ end
data/test/init_test.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'test/helper'
2
+
3
+ class InitTest < Test::Unit::TestCase
4
+ include FsMock
5
+
6
+ def test_init_doesnt_overwite_addon_manifest
7
+ File.open(@filename, 'w') { |f| f << '{}' }
8
+ any_instance_of(Xplenty::Kensa::Client) do |client|
9
+ stub(client).gets { 'n' }
10
+ stub(client).print
11
+ stub(client).puts
12
+ end
13
+
14
+ assert_raises SystemExit do
15
+ kensa "init"
16
+ end
17
+ end
18
+
19
+ def test_init_defaults_to_sso_post
20
+ kensa "init"
21
+ manifest = read_json(@filename)
22
+ %w{test production}.each do |env|
23
+ %w{base_url sso_url}.each do |url|
24
+ assert manifest['api'][env][url] =~ /^http/
25
+ end
26
+ end
27
+ assert !File.exist?('.env')
28
+ end
29
+
30
+ def test_init_uses_file_flag
31
+ @filename = 'foo.json'
32
+
33
+ kensa "init -f #{@filename}"
34
+ assert !File.exist?('./addon-manifest.json')
35
+ assert !File.exist?('.env')
36
+ manifest = read_json(@filename)
37
+ end
38
+
39
+ def test_init_uses_sso_flag
40
+ kensa "init --sso get"
41
+ manifest = read_json(@filename)
42
+ %w{test production}.each do |env|
43
+ assert manifest['api'][env] =~ /^http/
44
+ end
45
+ assert !File.exist?('.env')
46
+ end
47
+
48
+ def assert_foreman_env(env, manifest)
49
+ assert env.include?("SSO_SALT=#{manifest['api']['sso_salt']}\n")
50
+ assert env.include?("HEROKU_USERNAME=#{manifest['id']}\n")
51
+ assert env.include?("HEROKU_PASSWORD=#{manifest['api']['password']}")
52
+ end
53
+
54
+ def test_init_with_foreman_flag_and_get
55
+ kensa "init --foreman --sso get"
56
+ env = File.open(".env").read
57
+ manifest = read_json(@filename)
58
+ assert manifest['api']['test'] =~ /:5000/
59
+ assert manifest['api']['test'] =~ /:5000/
60
+ assert_foreman_env env, manifest
61
+ end
62
+
63
+ def test_init_with_foreman_flag_and_post
64
+ kensa "init --foreman --sso post"
65
+ env = File.open(".env").read
66
+ manifest = read_json(@filename)
67
+ assert manifest['api']['test']['base_url'] =~ /:5000/
68
+ assert manifest['api']['test']['sso_url'] =~ /:5000/
69
+ assert_foreman_env env, manifest
70
+ end
71
+ end
@@ -0,0 +1,123 @@
1
+ require 'test/helper'
2
+
3
+ class ManifestCheckTest < Test::Unit::TestCase
4
+ include Xplenty::Kensa
5
+
6
+ def check ; ManifestCheck ; end
7
+
8
+ test "doesn't barf on OkJson errors" do
9
+ File.open("addon-manifest.json", 'w') { |f| f << "{,a" }
10
+ assert_raises Client::CommandInvalid, "addon-manifest.json includes invalid JSON" do
11
+ kensa "test provision"
12
+ end
13
+ end
14
+
15
+ %w{get post}.each do |method|
16
+ context "with sso #{method}" do
17
+ setup { @data = Manifest.new(:method => method).skeleton }
18
+
19
+ test "is valid if no errors" do
20
+ assert_valid
21
+ end
22
+
23
+ test "has an id" do
24
+ @data.delete("id")
25
+ assert_invalid
26
+ end
27
+
28
+ test "api key exists" do
29
+ @data.delete("api")
30
+ assert_invalid
31
+ end
32
+
33
+ test "api is a Hash" do
34
+ @data["api"] = ""
35
+ assert_invalid
36
+ end
37
+
38
+ test "api has a list of regions" do
39
+ @data["api"].delete("regions")
40
+ assert_invalid
41
+ end
42
+
43
+ test "api has a list of regions including US" do
44
+ @data["api"]["regions"] = ["eu"]
45
+ assert_invalid
46
+ end
47
+
48
+ test "api only allows valid region names" do
49
+ @data["api"]["regions"] = ["us", "ap"]
50
+ assert_invalid
51
+ end
52
+
53
+ test "api has a password" do
54
+ @data["api"].delete("password")
55
+ assert_invalid
56
+ end
57
+
58
+ test "api contains test" do
59
+ @data["api"].delete("test")
60
+ assert_invalid
61
+ end
62
+
63
+ test "api contains production" do
64
+ @data["api"].delete("production")
65
+ assert_invalid
66
+ end
67
+
68
+ test "api contains production of https" do
69
+ if method == 'get'
70
+ @data["api"]["production"] = "http://foo.com"
71
+ else
72
+ @data["api"]["production"]['base_url'] = "http://foo.com"
73
+ end
74
+ assert_invalid
75
+ end
76
+
77
+ if method == 'post'
78
+ test "sso contains production of https" do
79
+ @data["api"]["production"]['sso_url'] = "http://foo.com"
80
+ assert_invalid
81
+ end
82
+ end
83
+
84
+ test "api does not require config_vars" do
85
+ @data["api"].delete "config_vars"
86
+ assert_valid
87
+ end
88
+
89
+ context "with config vars" do
90
+ test "api contains config_vars array" do
91
+ @data["api"]["config_vars"] = "test"
92
+ assert_invalid
93
+ end
94
+
95
+ test "contains at least one config var" do
96
+ @data["api"]["config_vars"].clear
97
+ assert_invalid
98
+ end
99
+
100
+ test "all config vars are in upper case" do
101
+ @data["api"]["config_vars"] << 'MYADDON_invalid_var'
102
+ assert_invalid
103
+ end
104
+
105
+ test "assert config var prefixes match addon id" do
106
+ @data["api"]["config_vars"] << 'MONGO_URL'
107
+ assert_invalid
108
+ end
109
+
110
+ test "replaces dashes for underscores on the config var check" do
111
+ @data["id"] = "MY-ADDON"
112
+ @data["api"]["config_vars"] = ["MY_ADDON_URL"]
113
+ assert_valid
114
+ end
115
+ end
116
+
117
+ test "username is deprecated" do
118
+ @data["api"]["username"] = "xplenty"
119
+ assert_invalid
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,63 @@
1
+ require 'test/helper'
2
+
3
+ class ManifestTest < Test::Unit::TestCase
4
+ include Xplenty::Kensa
5
+
6
+ context 'GET manifest' do
7
+ setup { @manifest = Manifest.new(:method => :get) }
8
+
9
+ test 'have sso salt' do
10
+ assert_not_nil @manifest.skeleton['api']['sso_salt']
11
+ end
12
+
13
+ test 'generates a new sso salt every time' do
14
+ assert @manifest.skeleton['api']['sso_salt'] != Manifest.new.skeleton['api']['sso_salt']
15
+ end
16
+
17
+ test 'has an api password' do
18
+ assert_not_nil @manifest.skeleton['api']['password']
19
+ end
20
+
21
+ test 'generates a new password every time' do
22
+ assert @manifest.skeleton['api']['password'] != Manifest.new.skeleton['api']['password']
23
+ end
24
+
25
+ test 'uses get format' do
26
+ assert_equal @manifest.skeleton['api']['test'], 'http://localhost:4567/'
27
+ assert_equal @manifest.skeleton['api']['production'], 'https://yourapp.com/'
28
+ end
29
+
30
+ test 'specifies the US region by default' do
31
+ assert_equal @manifest.skeleton['api']['regions'], ['us']
32
+ end
33
+ end
34
+
35
+ context "POST manifest" do
36
+ setup { @manifest = Manifest.new(:method => :post) }
37
+
38
+ test 'uses post format for test url' do
39
+ assert_equal @manifest.skeleton['api']['test']['base_url'], 'http://localhost:4567/xplenty/resources'
40
+ assert_equal @manifest.skeleton['api']['test']['sso_url'], 'http://localhost:4566/sso/login'
41
+ end
42
+
43
+ test 'uses post format for test url' do
44
+ assert_equal @manifest.skeleton['api']['production']['base_url'], 'https://yourapp.com/xplenty/resources'
45
+ assert_equal @manifest.skeleton['api']['production']['sso_url'], 'https://yourapp.com/sso/login'
46
+ end
47
+
48
+ test 'specifies the US region by default' do
49
+ assert_equal @manifest.skeleton['api']['regions'], ['us']
50
+ end
51
+ end
52
+
53
+ context 'manifest without sso' do
54
+ setup do
55
+ options = { :sso => false, :filename => 'test.txt' }
56
+ @manifest = Manifest.new options
57
+ end
58
+
59
+ test 'exclude sso salt' do
60
+ assert_nil @manifest.skeleton['api']['sso_salt']
61
+ end
62
+ end
63
+ end