samorau 0.1.0

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