samorau 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ addon-manifest.json
data/NOTES ADDED
@@ -0,0 +1,28 @@
1
+ samorau
2
+ 1. to wait for a chance
3
+ 2. to serve by ones side
4
+
5
+ check man
6
+ - invalid
7
+ - valid
8
+ - invalid price (show error)
9
+ - multiple plans
10
+
11
+ check create
12
+ - server not running
13
+ - no config (no output)
14
+ - config
15
+
16
+ check all
17
+ - uses id from response
18
+
19
+ run
20
+ - ENV["FOO"]
21
+
22
+ arch
23
+
24
+ - chain of responsiblity
25
+ - commands pass by data
26
+
27
+ show contest
28
+ show turn
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "samorau"
5
+ gemspec.summary = ""
6
+ gemspec.description = ""
7
+ gemspec.email = "pedro@heroku.com"
8
+ gemspec.homepage = "http://heroku.com"
9
+ gemspec.authors = ["Blake Mizerany", "Pedro Belo", "Adam Wiggins"]
10
+
11
+ gemspec.add_development_dependency(%q<turn>, [">= 0"])
12
+ gemspec.add_development_dependency(%q<contest>, [">= 0"])
13
+ gemspec.add_dependency(%q<sinatra>, ["~> 0.9"])
14
+ gemspec.add_dependency(%q<rest-client>, ["~> 1.2.0"])
15
+ gemspec.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
16
+ gemspec.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
17
+
18
+ gemspec.version = '0.1.0'
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: gem install jeweler"
22
+ end
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+
2
+ [ ] heroku-addon test -n delete # run specific test [create | delete | sharing]
3
+ [ ] heroku-addon test # run full test suite
4
+ [ ] heroku-addon repl command # runs create, set ENVs, then delete on exit
5
+ [ ] heroku-addon push # push manifest to heroku
6
+
7
+ [X] heroku-addon check # check correctnes of JSON
8
+ [X] heroku-addon init # generate skeleton manifest
data/a-server.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'sinatra'
2
+ require 'yajl'
3
+ require 'restclient'
4
+
5
+ post "/heroku/resources" do
6
+ request.body.rewind
7
+ input = Yajl::Parser.parse(request.body.read)
8
+ resp = { :id => 123, :config => { "FOO" => "bar" } }
9
+ #resp = { :id => 456 }
10
+ json = Yajl::Encoder.encode(resp)
11
+ fork do
12
+ sleep 2
13
+ p input
14
+ RestClient.put(input["callback_url"], json)
15
+ end
16
+ "{}"
17
+ end
18
+
19
+ delete "/heroku/resources/:id" do
20
+ "ok"
21
+ end
data/bin/samorau ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'term/ansicolor'
4
+ require 'heroku/samorau'
5
+
6
+ fn="addon-manifest.json"
7
+ async=false
8
+ env="test"
9
+
10
+ ARGV.options do |o|
11
+ o.on("-f file", "--file") {|filename| fn = filename }
12
+ o.on("-h", "--help") { command = "help" }
13
+ o.on("--async") { async = true }
14
+ o.on("--production") { env = "production" }
15
+ o.parse!
16
+ end
17
+
18
+ command = ARGV.shift
19
+
20
+ $stdout.sync = true
21
+
22
+ class Screen
23
+ include Term::ANSIColor
24
+
25
+ def test(msg)
26
+ $stdout.puts
27
+ $stdout.puts
28
+ $stdout.print "Testing #{msg}"
29
+ end
30
+
31
+ def check(msg)
32
+ $stdout.puts
33
+ $stdout.print " Check #{msg}"
34
+ end
35
+
36
+ def error(msg)
37
+ $stdout.print "\n", magenta(" ! #{msg}")
38
+ end
39
+
40
+ def result(status)
41
+ msg = status ? bold("[PASS]") : red(bold("[FAIL]"))
42
+ $stdout.print " #{msg}"
43
+ end
44
+
45
+ def message(msg)
46
+ $stdout.puts msg
47
+ end
48
+
49
+ def finish
50
+ $stdout.puts
51
+ $stdout.puts
52
+ $stdout.puts "done."
53
+ end
54
+
55
+ end
56
+
57
+ def resolve_manifest(fn)
58
+ if File.exists?(fn)
59
+ File.read(fn)
60
+ else
61
+ abort("fatal: #{fn} not found")
62
+ end
63
+ end
64
+
65
+ def run(klass, fn, extras={})
66
+ screen = Screen.new
67
+ data = Yajl::Parser.parse(resolve_manifest(fn))
68
+ check = klass.new(data.merge(extras), screen)
69
+ check.call
70
+ screen.finish
71
+ end
72
+
73
+ include Heroku::Samorau
74
+
75
+ case command
76
+ when "init"
77
+ Manifest.init(fn)
78
+ Screen.new.message "Initialized new addon manifest in #{fn}"
79
+ when "test"
80
+ case check = ARGV.shift
81
+ when "manifest"
82
+ run ManifestCheck, fn
83
+ when "provision"
84
+ run ManifestCheck, fn
85
+ run CreateCheck, fn, :async => async, :env => env
86
+ when "deprovision"
87
+ id = ARGV.shift || abort("! no id specified; see usage")
88
+ run ManifestCheck, fn
89
+ run DeleteCheck, fn, :id => id, :async => async, :env => env
90
+ else
91
+ abort "! Unknown test '#{check}'; see usage"
92
+ end
93
+ when "run"
94
+ abort "! missing command to run; see usage" if ARGV.empty?
95
+ run ManifestCheck, fn
96
+ run AllCheck, fn, :args => ARGV, :async => async, :env => env
97
+ else
98
+ abort DATA.read
99
+ end
100
+
101
+ __END__
102
+ Usage: samorau [OPTIONS] command
103
+ samorau init
104
+ samorau test <type> [arg1 arg2 ...]
105
+ samorau run <command> [arg1 arg1 ...]
106
+
107
+ OPTIONS
108
+
109
+ -f, --filename path-to-file
110
+ Sets the manifest file to operate on, default is addon-manifest.json.
111
+
112
+ -h, --help
113
+ Show this message
114
+
115
+ --async
116
+ Check provision call with async response.
117
+
118
+ COMMANDS
119
+
120
+ init Creates a skeleton manifest
121
+
122
+ test <type> Simulate call from Heroku (provision or deprovision)
123
+
124
+ run <command> Provisions a resource and runs command in returned ENV
125
+
126
+ TEST TYPES
127
+
128
+ provision
129
+ Simulate a provision call from Heroku.
130
+
131
+ deprovision <id>
132
+ Simulate a deprovision call from Heroku.
133
+
134
+ manifest
135
+ Confirm that the manifest is valid. Automatically runs before all tests.
136
+
@@ -0,0 +1,465 @@
1
+ require 'yajl'
2
+ require 'restclient'
3
+ require 'socket'
4
+ require 'timeout'
5
+
6
+ module Heroku
7
+
8
+ module Samorau
9
+
10
+ module Manifest
11
+
12
+ def self.init(filename)
13
+ json = Yajl::Encoder.encode(skeleton, :pretty => true)
14
+ open(filename, 'w') {|f| f << json }
15
+ end
16
+
17
+ def self.skeleton
18
+ {
19
+ "name" => "myaddon",
20
+
21
+ "api" => {
22
+ "username" => "heroku",
23
+ "password" => generate_password,
24
+ "test" => "http://localhost:4567/",
25
+ "production" => "https://yourapp.com/",
26
+ "config_vars" => ["MYADDON_URL"]
27
+ },
28
+
29
+ "plans" => [
30
+ {
31
+ "name" => "Basic",
32
+ "price" => "0",
33
+ "price_unit" => "month"
34
+ }
35
+ ]
36
+ }
37
+ end
38
+
39
+ def self.generate_password
40
+ Array.new(8) { rand(256) }.pack('C*').unpack('H*').first
41
+ end
42
+
43
+ end
44
+
45
+
46
+ class NilScreen
47
+
48
+ def test(msg)
49
+ end
50
+
51
+ def check(msg)
52
+ end
53
+
54
+ def error(msg)
55
+ end
56
+
57
+ def result(status)
58
+ end
59
+
60
+ end
61
+
62
+
63
+ class Check
64
+ attr_accessor :screen, :data
65
+
66
+ class CheckError < StandardError ; end
67
+
68
+ def initialize(data, screen=NilScreen.new)
69
+ @data = data
70
+ @screen = screen
71
+ end
72
+
73
+ def test(msg)
74
+ screen.test msg
75
+ end
76
+
77
+ def check(msg)
78
+ screen.check(msg)
79
+ if yield
80
+ screen.result(true)
81
+ else
82
+ raise CheckError
83
+ end
84
+ end
85
+
86
+ def run(klass, data)
87
+ c = klass.new(data, screen)
88
+ instance_eval(&c)
89
+ end
90
+
91
+ def error(msg)
92
+ raise CheckError, msg
93
+ end
94
+
95
+ def call
96
+ call!
97
+ true
98
+ rescue CheckError => boom
99
+ screen.result(false)
100
+ screen.error boom.message if boom.message != boom.class.name
101
+
102
+ false
103
+ end
104
+
105
+ def to_proc
106
+ me = self
107
+ Proc.new { me.call! }
108
+ end
109
+
110
+ end
111
+
112
+
113
+ class ManifestCheck < Check
114
+
115
+ ValidPriceUnits = %w[month dyno_hour]
116
+
117
+ def call!
118
+ test "manifest name key"
119
+ check "if exists" do
120
+ data.has_key?("name")
121
+ end
122
+ check "is a string" do
123
+ data["name"].is_a?(String)
124
+ end
125
+ check "is not blank" do
126
+ !data["name"].empty?
127
+ end
128
+
129
+ test "manifest api key"
130
+ check "if exists" do
131
+ data.has_key?("api")
132
+ end
133
+ check "is a hash" do
134
+ data["api"].is_a?(Hash)
135
+ end
136
+ check "contains username" do
137
+ data["api"].has_key?("username") && data["api"]["username"] != ""
138
+ end
139
+ check "contains password" do
140
+ data["api"].has_key?("password") && data["api"]["password"] != ""
141
+ end
142
+ check "contains test url" do
143
+ data["api"].has_key?("test")
144
+ end
145
+ check "contains production url" do
146
+ data["api"].has_key?("production")
147
+ end
148
+ check "production url uses SSL" do
149
+ data["api"]["production"] =~ /^https:/
150
+ end
151
+ check "contains config_vars array" do
152
+ data["api"].has_key?("config_vars") && data["api"]["config_vars"].is_a?(Array)
153
+ end
154
+ check "containst at least one config var" do
155
+ !data["api"]["config_vars"].empty?
156
+ end
157
+ check "all config vars are uppercase strings" do
158
+ data["api"]["config_vars"].each do |k, v|
159
+ if k =~ /^[A-Z][0-9A-Z_]+$/
160
+ true
161
+ else
162
+ error "#{k.inspect} is not a valid ENV key"
163
+ end
164
+ end
165
+ end
166
+
167
+ test "plans"
168
+ check "key must exist" do
169
+ data.has_key?("plans")
170
+ end
171
+ check "is an array" do
172
+ data["plans"].is_a?(Array)
173
+ end
174
+ check "contains at least one plan" do
175
+ !data["plans"].empty?
176
+ end
177
+ check "all plans are a hash" do
178
+ data["plans"].all? {|plan| plan.is_a?(Hash) }
179
+ end
180
+ check "all plans have a name" do
181
+ data["plans"].all? {|plan| plan.has_key?("name") }
182
+ end
183
+ check "all plans have a unique name" do
184
+ names = data["plans"].map {|plan| plan["name"] }
185
+ names.size == names.uniq.size
186
+ end
187
+
188
+ data["plans"].each do |plan|
189
+ check "#{plan["name"]} has a valid price" do
190
+ if plan["price"] !~ /^\d+$/
191
+ error "expected an integer"
192
+ else
193
+ true
194
+ end
195
+ end
196
+
197
+ check "#{plan["name"]} has a valid price_unit" do
198
+ if ValidPriceUnits.include?(plan["price_unit"])
199
+ true
200
+ else
201
+ error "expected #{ValidPriceUnits.join(" or ")} but got #{plan["price_unit"].inspect}"
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ end
208
+
209
+
210
+ class CreateResponseCheck < Check
211
+
212
+ def call!
213
+ response = data[:create_response]
214
+ test "response"
215
+ check "contains an id" do
216
+ response.has_key?("id")
217
+ end
218
+
219
+ if response.has_key?("config")
220
+ test "config data"
221
+ check "is a hash" do
222
+ response["config"].is_a?(Hash)
223
+ end
224
+
225
+ check "all config keys were previously defined in the manifest" do
226
+ response["config"].keys.each do |key|
227
+ error "#{key} is not in the manifest" unless data["api"]["config_vars"].include?(key)
228
+ end
229
+ true
230
+ end
231
+
232
+ check "all config values are strings" do
233
+ response["config"].each do |k, v|
234
+ if v.is_a?(String)
235
+ true
236
+ else
237
+ error "#{v.inspect} is not a string"
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+
247
+ module HTTP
248
+
249
+ def post(credentials, path, payload=nil)
250
+ request(:post, credentials, path, payload)
251
+ end
252
+
253
+ def delete(credentials, path, payload=nil)
254
+ request(:delete, credentials, path, payload)
255
+ end
256
+
257
+ def request(meth, credentials, path, payload=nil)
258
+ code = nil
259
+ body = nil
260
+
261
+ begin
262
+ args = [
263
+ (Yajl::Encoder.encode(payload) if payload),
264
+ {
265
+ :accept => "application/json",
266
+ :content_type => "application/json"
267
+ }
268
+ ].compact
269
+
270
+ user, pass = credentials
271
+ body = RestClient::Resource.new(url, user, pass)[path].send(
272
+ meth,
273
+ *args
274
+ )
275
+
276
+ code = 200
277
+ rescue RestClient::ExceptionWithResponse => boom
278
+ code = boom.http_code
279
+ body = boom.http_body
280
+ rescue Errno::ECONNREFUSED
281
+ code = -1
282
+ body = nil
283
+ end
284
+
285
+ [code, body]
286
+ end
287
+
288
+ end
289
+
290
+ class ApiCheck < Check
291
+ def url
292
+ env = data[:env] || 'test'
293
+ data["api"][env].chomp("/")
294
+ end
295
+
296
+ def credentials
297
+ %w( username password ).map { |attr| data["api"][attr] }
298
+ end
299
+ end
300
+
301
+ class CreateCheck < ApiCheck
302
+ include HTTP
303
+
304
+ READLEN = 1024 * 10
305
+ APPID = "app123@heroku.com"
306
+
307
+ def call!
308
+ json = nil
309
+ response = nil
310
+
311
+ code = nil
312
+ json = nil
313
+ path = "/heroku/resources"
314
+ callback = "http://localhost:7779/callback/999"
315
+ reader, writer = nil
316
+
317
+ payload = {
318
+ :id => APPID,
319
+ :plan => "Basic",
320
+ :callback_url => callback
321
+ }
322
+
323
+ if data[:async]
324
+ reader, writer = IO.pipe
325
+ end
326
+
327
+ test "POST /heroku/resources"
328
+ check "response" do
329
+ if data[:async]
330
+ child = fork do
331
+ Timeout.timeout(10) do
332
+ reader.close
333
+ server = TCPServer.open(7779)
334
+ client = server.accept
335
+ writer.write(client.readpartial(READLEN))
336
+ client.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
337
+ client.close
338
+ writer.close
339
+ end
340
+ end
341
+ sleep(1)
342
+ end
343
+
344
+ code, json = post(credentials, path, payload)
345
+
346
+ if code == 200
347
+ # noop
348
+ elsif code == -1
349
+ error("unable to connect to #{url}")
350
+ else
351
+ error("expected 200, got #{code}")
352
+ end
353
+
354
+ true
355
+ end
356
+
357
+ if data[:async]
358
+ check "async response to PUT #{callback}" do
359
+ out = reader.readpartial(READLEN)
360
+ _, json = out.split("\r\n\r\n")
361
+ end
362
+ end
363
+
364
+ check "valid JSON" do
365
+ begin
366
+ response = Yajl::Parser.parse(json)
367
+ rescue Yajl::ParseError => boom
368
+ error boom.message
369
+ end
370
+ true
371
+ end
372
+
373
+ check "authentication" do
374
+ wrong_credentials = ['wrong', 'secret']
375
+ code, _ = post(wrong_credentials, path, payload)
376
+ error("expected 401, got #{code}") if code != 401
377
+ true
378
+ end
379
+
380
+ data[:create_response] = response
381
+
382
+ run CreateResponseCheck, data
383
+ end
384
+
385
+ ensure
386
+ reader.close rescue nil
387
+ writer.close rescue nil
388
+ end
389
+
390
+
391
+ class DeleteCheck < ApiCheck
392
+ include HTTP
393
+
394
+ def call!
395
+ id = data[:id]
396
+ raise ArgumentError, "No id specified" if id.nil?
397
+
398
+ path = "/heroku/resources/#{id}"
399
+
400
+ test "DELETE #{path}"
401
+ check "response" do
402
+ code, _ = delete(credentials, path, nil)
403
+ if code == 200
404
+ true
405
+ elsif code == -1
406
+ error("unable to connect to #{url}")
407
+ else
408
+ error("expected 200, got #{code}")
409
+ end
410
+ end
411
+
412
+ check "authentication" do
413
+ wrong_credentials = ['wrong', 'secret']
414
+ code, _ = delete(wrong_credentials, path, nil)
415
+ error("expected 401, got #{code}") if code != 401
416
+ true
417
+ end
418
+
419
+ end
420
+
421
+ end
422
+
423
+
424
+ ##
425
+ # On Testing:
426
+ # I've opted to not write tests for this
427
+ # due to the simple nature it's currently in.
428
+ # If this becomes more complex in even the
429
+ # least amount, find me (blake) and I'll
430
+ # help get tests in.
431
+ class AllCheck < Check
432
+
433
+ def call!
434
+ args = data[:args]
435
+ run CreateCheck, data
436
+
437
+ response = data[:create_response]
438
+ id = response["id"]
439
+ config = response["config"] || Hash.new
440
+
441
+ if args
442
+ screen.message "\n\n"
443
+ screen.message "Starting #{args.first}..."
444
+ screen.message ""
445
+
446
+ run_in_env(config) { system(*args) }
447
+
448
+ screen.message ""
449
+ screen.message "End of #{args.first}"
450
+ end
451
+
452
+ run DeleteCheck, data.merge(:id => id)
453
+ end
454
+
455
+ def run_in_env(env)
456
+ env.each {|key, value| ENV[key] = value }
457
+ yield
458
+ env.keys.each {|key| ENV.delete(key) }
459
+ end
460
+
461
+ end
462
+
463
+ end
464
+
465
+ end
data/samorau.gemspec ADDED
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{samorau}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Blake Mizerany", "Pedro Belo", "Adam Wiggins"]
12
+ s.date = %q{2010-02-11}
13
+ s.default_executable = %q{samorau}
14
+ s.description = %q{}
15
+ s.email = %q{pedro@heroku.com}
16
+ s.executables = ["samorau"]
17
+ s.extra_rdoc_files = [
18
+ "TODO"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "NOTES",
23
+ "Rakefile",
24
+ "TODO",
25
+ "a-server.rb",
26
+ "bin/samorau",
27
+ "lib/heroku/samorau.rb",
28
+ "samorau.gemspec",
29
+ "server.rb",
30
+ "set-env.sh",
31
+ "test/create_check_test.rb",
32
+ "test/create_response_check_test.rb",
33
+ "test/delete_check.rb",
34
+ "test/helper.rb",
35
+ "test/manifest_check_test.rb"
36
+ ]
37
+ s.homepage = %q{http://heroku.com}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.5}
41
+ s.summary = %q{}
42
+ s.test_files = [
43
+ "test/create_check_test.rb",
44
+ "test/create_response_check_test.rb",
45
+ "test/delete_check.rb",
46
+ "test/helper.rb",
47
+ "test/manifest_check_test.rb"
48
+ ]
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
+ s.add_development_dependency(%q<turn>, [">= 0"])
56
+ s.add_development_dependency(%q<contest>, [">= 0"])
57
+ s.add_runtime_dependency(%q<sinatra>, ["~> 0.9"])
58
+ s.add_runtime_dependency(%q<rest-client>, ["~> 1.2.0"])
59
+ s.add_runtime_dependency(%q<yajl-ruby>, ["~> 0.6"])
60
+ s.add_runtime_dependency(%q<term-ansicolor>, ["~> 1.0"])
61
+ else
62
+ s.add_dependency(%q<turn>, [">= 0"])
63
+ s.add_dependency(%q<contest>, [">= 0"])
64
+ s.add_dependency(%q<sinatra>, ["~> 0.9"])
65
+ s.add_dependency(%q<rest-client>, ["~> 1.2.0"])
66
+ s.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
67
+ s.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<turn>, [">= 0"])
71
+ s.add_dependency(%q<contest>, [">= 0"])
72
+ s.add_dependency(%q<sinatra>, ["~> 0.9"])
73
+ s.add_dependency(%q<rest-client>, ["~> 1.2.0"])
74
+ s.add_dependency(%q<yajl-ruby>, ["~> 0.6"])
75
+ s.add_dependency(%q<term-ansicolor>, ["~> 1.0"])
76
+ end
77
+ end
78
+
data/server.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'sinatra'
2
+ require 'yajl'
3
+ require 'restclient'
4
+
5
+ post "/heroku/resources" do
6
+ resp = { :id => 123, :config => { "FOO" => "bar" } }
7
+ #resp = { :id => 123 }
8
+ Yajl::Encoder.encode(resp)
9
+ end
10
+
11
+ delete "/heroku/resources/:id" do
12
+ "ok"
13
+ end
data/set-env.sh ADDED
@@ -0,0 +1,4 @@
1
+ PATH=$(pwd)/bin:$PATH
2
+ RUBYOPT=rubygems
3
+ RUBYLIB=$(pwd)/lib:$RUBYLIB
4
+ export PATH RUBYOPT RUBYLIB
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+ require "heroku/samorau"
3
+
4
+ class CreateCheckTest < Test::Unit::TestCase
5
+ include Heroku::Samorau
6
+
7
+ setup do
8
+ @data = Manifest.skeleton
9
+ @responses = [
10
+ [200, to_json({ :id => 456 })],
11
+ [401, "Unauthorized"]
12
+ ]
13
+ end
14
+
15
+ def check ; CreateCheck ; end
16
+
17
+ test "valid on 200 for the regular check, and 401 for the auth check" do
18
+ assert_valid do |check|
19
+ stub :post, check, @responses
20
+ end
21
+ end
22
+
23
+ test "invalid JSON" do
24
+ @responses[0] = [200, "---"]
25
+ assert_invalid do |check|
26
+ stub :post, check, @responses
27
+ end
28
+ end
29
+
30
+ test "status other than 200" do
31
+ @responses[0] = [500, to_json({ :id => 456 })]
32
+ assert_invalid do |check|
33
+ stub :post, check, @responses
34
+ end
35
+ end
36
+
37
+ test "runs create response check" do
38
+ @responses[0] = [200, to_json({ :noid => 456 })]
39
+ assert_invalid do |check|
40
+ stub :post, check, @responses
41
+ end
42
+ end
43
+
44
+ test "runs auth check" do
45
+ @responses[1] = [200, to_json({ :id => 456 })]
46
+ assert_invalid do |check|
47
+ stub :post, check, @responses
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+ require 'heroku/samorau'
3
+
4
+ class CreateResponseCheckTest < Test::Unit::TestCase
5
+ include Heroku::Samorau
6
+
7
+ def check ; CreateResponseCheck ; end
8
+
9
+ setup do
10
+ @response = { "id" => "123" }
11
+ @data = Manifest.skeleton.merge(:create_response => @response)
12
+ end
13
+
14
+ test "is valid if no errors" do
15
+ assert_valid
16
+ end
17
+
18
+ test "has an id" do
19
+ @response.delete("id")
20
+ assert_invalid
21
+ end
22
+
23
+ describe "when config is present" do
24
+
25
+ test "is a hash" do
26
+ @response["config"] = ""
27
+ assert_invalid
28
+ end
29
+
30
+ test "each key is previously set in the manifest" do
31
+ @response["config"] = { "MYSQL_URL" => "http://..." }
32
+ assert_invalid
33
+ end
34
+
35
+ test "each value is a string" do
36
+ @response["config"] = { "MYADDON_URL" => {} }
37
+ assert_invalid
38
+ end
39
+
40
+ test "is valid otherwise" do
41
+ @response["config"] = { "MYADDON_URL" => "http://..." }
42
+ assert_valid
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+ require "heroku/samorau"
3
+
4
+ class DeleteCheckTest < Test::Unit::TestCase
5
+ include Heroku::Samorau
6
+
7
+ setup do
8
+ @data = Manifest.skeleton.merge :id => 123
9
+ @responses = [
10
+ [200, ""],
11
+ [401, ""],
12
+ ]
13
+ end
14
+
15
+ def check ; DeleteCheck ; end
16
+
17
+ test "valid on 200" do
18
+ assert_valid do |check|
19
+ stub :delete, check, @responses
20
+ end
21
+ end
22
+
23
+ test "status other than 200" do
24
+ @responses[0] = [500, ""]
25
+ assert_invalid do |check|
26
+ stub :delete, check, @responses
27
+ end
28
+ end
29
+
30
+ test "runs auth check" do
31
+ @responses[1] = [200, ""]
32
+ assert_invalid do |check|
33
+ stub :delete, check, @responses
34
+ end
35
+ end
36
+
37
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'contest'
2
+
3
+ class Test::Unit::TestCase
4
+
5
+ def assert_valid(data=@data, &blk)
6
+ check = create_check(data, &blk)
7
+ assert check.call
8
+ end
9
+
10
+ def assert_invalid(data=@data, &blk)
11
+ check = create_check(data, &blk)
12
+ assert !check.call
13
+ end
14
+
15
+ def create_check(data, &blk)
16
+ check = self.check.new(data)
17
+ blk.call(check) if blk
18
+ check
19
+ end
20
+
21
+ module Headerize
22
+ attr_accessor :headers
23
+ end
24
+
25
+ def to_json(data, headers={})
26
+ body = Yajl::Encoder.encode(data)
27
+ add_headers(body, headers)
28
+ end
29
+
30
+ def add_headers(o, headers={})
31
+ o.extend Headerize
32
+ o.headers = {}
33
+ o.headers["Content-Type"] ||= "application/json"
34
+ o.headers.merge!(headers)
35
+ o
36
+ end
37
+
38
+ def stub(meth, o, returns)
39
+ o.instance_eval { @returns = Array(returns) }
40
+ eval <<-EVAL
41
+ def o.#{meth}(*args)
42
+ @returns.shift || fail("Nothing else to return from stub'ed method")
43
+ end
44
+ EVAL
45
+ end
46
+
47
+ end
@@ -0,0 +1,117 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+ require 'heroku/samorau'
3
+
4
+ class ManifestCheckTest < Test::Unit::TestCase
5
+ include Heroku::Samorau
6
+
7
+ def check ; ManifestCheck ; end
8
+
9
+ setup do
10
+ @data = Manifest.skeleton
11
+ end
12
+
13
+ test "is valid if no errors" do
14
+ assert_valid
15
+ end
16
+
17
+ test "has a name" do
18
+ @data.delete("name")
19
+ assert_invalid
20
+ end
21
+
22
+ test "api key exists" do
23
+ @data.delete("api")
24
+ assert_invalid
25
+ end
26
+
27
+ test "api is a Hash" do
28
+ @data["api"] = ""
29
+ assert_invalid
30
+ end
31
+
32
+ test "api has a username" do
33
+ @data["api"].delete("username")
34
+ assert_invalid
35
+ end
36
+
37
+ test "api has a password" do
38
+ @data["api"].delete("password")
39
+ assert_invalid
40
+ end
41
+
42
+ test "api contains test" do
43
+ @data["api"].delete("test")
44
+ assert_invalid
45
+ end
46
+
47
+ test "api contains production" do
48
+ @data["api"].delete("production")
49
+ assert_invalid
50
+ end
51
+
52
+ test "api contains production of https" do
53
+ @data["api"]["production"] = "http://foo.com"
54
+ assert_invalid
55
+ end
56
+
57
+ test "api contains config_vars array" do
58
+ @data["api"]["config_vars"] = "test"
59
+ assert_invalid
60
+ end
61
+
62
+ test "api contains at least one config var" do
63
+ @data["api"]["config_vars"].clear
64
+ assert_invalid
65
+ end
66
+
67
+ test "all config vars are in upper case" do
68
+ @data["api"]["config_vars"] << 'invalid_var'
69
+ assert_invalid
70
+ end
71
+
72
+ test "plans key must exist" do
73
+ @data.delete("plans")
74
+ assert_invalid
75
+ end
76
+
77
+ test "plans key must be an Array" do
78
+ @data["plans"] = ""
79
+ assert_invalid
80
+ end
81
+
82
+ test "has at least one plan" do
83
+ @data["plans"] = []
84
+ assert_invalid
85
+ end
86
+
87
+ test "all plans are a hash" do
88
+ @data["plans"][0] = ""
89
+ assert_invalid
90
+ end
91
+
92
+ test "all plans have a name" do
93
+ @data["plans"].first.delete("name")
94
+ assert_invalid
95
+ end
96
+
97
+ test "all plans have a unique name" do
98
+ @data["plans"] << @data["plans"].first.dup
99
+ assert_invalid
100
+ end
101
+
102
+ test "plans have a price" do
103
+ @data["plans"].first.delete("price")
104
+ assert_invalid
105
+ end
106
+
107
+ test "plans have an integer price" do
108
+ @data["plans"].first["price"] = "fiddy cent"
109
+ assert_invalid
110
+ end
111
+
112
+ test "plans have a valid price_unit" do
113
+ @data["plans"].first["price_unit"] = "first ov da munth"
114
+ assert_invalid
115
+ end
116
+
117
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: samorau
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Blake Mizerany
8
+ - Pedro Belo
9
+ - Adam Wiggins
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2010-02-11 00:00:00 -08:00
15
+ default_executable: samorau
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: turn
19
+ type: :development
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: "0"
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: contest
29
+ type: :development
30
+ version_requirement:
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ version:
37
+ - !ruby/object:Gem::Dependency
38
+ name: sinatra
39
+ type: :runtime
40
+ version_requirement:
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: "0.9"
46
+ version:
47
+ - !ruby/object:Gem::Dependency
48
+ name: rest-client
49
+ type: :runtime
50
+ version_requirement:
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.2.0
56
+ version:
57
+ - !ruby/object:Gem::Dependency
58
+ name: yajl-ruby
59
+ type: :runtime
60
+ version_requirement:
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: "0.6"
66
+ version:
67
+ - !ruby/object:Gem::Dependency
68
+ name: term-ansicolor
69
+ type: :runtime
70
+ version_requirement:
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: "1.0"
76
+ version:
77
+ description: ""
78
+ email: pedro@heroku.com
79
+ executables:
80
+ - samorau
81
+ extensions: []
82
+
83
+ extra_rdoc_files:
84
+ - TODO
85
+ files:
86
+ - .gitignore
87
+ - NOTES
88
+ - Rakefile
89
+ - TODO
90
+ - a-server.rb
91
+ - bin/samorau
92
+ - lib/heroku/samorau.rb
93
+ - samorau.gemspec
94
+ - server.rb
95
+ - set-env.sh
96
+ - test/create_check_test.rb
97
+ - test/create_response_check_test.rb
98
+ - test/delete_check.rb
99
+ - test/helper.rb
100
+ - test/manifest_check_test.rb
101
+ has_rdoc: true
102
+ homepage: http://heroku.com
103
+ licenses: []
104
+
105
+ post_install_message:
106
+ rdoc_options:
107
+ - --charset=UTF-8
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: "0"
115
+ version:
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: "0"
121
+ version:
122
+ requirements: []
123
+
124
+ rubyforge_project:
125
+ rubygems_version: 1.3.5
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: ""
129
+ test_files:
130
+ - test/create_check_test.rb
131
+ - test/create_response_check_test.rb
132
+ - test/delete_check.rb
133
+ - test/helper.rb
134
+ - test/manifest_check_test.rb