chassis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +362 -0
- data/Rakefile +33 -0
- data/chassis.gemspec +41 -0
- data/examples/repo.rb +40 -0
- data/lib/chassis.rb +81 -0
- data/lib/chassis/array_utils.rb +8 -0
- data/lib/chassis/circuit_panel.rb +22 -0
- data/lib/chassis/core_ext/array.rb +5 -0
- data/lib/chassis/core_ext/hash.rb +5 -0
- data/lib/chassis/core_ext/string.rb +13 -0
- data/lib/chassis/delegate.rb +29 -0
- data/lib/chassis/dirty_session.rb +105 -0
- data/lib/chassis/error.rb +7 -0
- data/lib/chassis/faraday.rb +226 -0
- data/lib/chassis/form.rb +56 -0
- data/lib/chassis/hash_utils.rb +16 -0
- data/lib/chassis/heroku.rb +5 -0
- data/lib/chassis/initializable.rb +11 -0
- data/lib/chassis/logger.rb +8 -0
- data/lib/chassis/observable.rb +19 -0
- data/lib/chassis/persistence.rb +49 -0
- data/lib/chassis/rack/bouncer.rb +33 -0
- data/lib/chassis/rack/builder_shim_patch.rb +7 -0
- data/lib/chassis/rack/health_check.rb +45 -0
- data/lib/chassis/rack/instrumentation.rb +20 -0
- data/lib/chassis/rack/json_body_parser.rb +20 -0
- data/lib/chassis/rack/no_robots.rb +24 -0
- data/lib/chassis/registry.rb +30 -0
- data/lib/chassis/repo.rb +73 -0
- data/lib/chassis/repo/base_repo.rb +99 -0
- data/lib/chassis/repo/delegation.rb +78 -0
- data/lib/chassis/repo/lazy_association.rb +57 -0
- data/lib/chassis/repo/memory_repo.rb +7 -0
- data/lib/chassis/repo/null_repo.rb +64 -0
- data/lib/chassis/repo/pstore_repo.rb +54 -0
- data/lib/chassis/repo/record_map.rb +44 -0
- data/lib/chassis/repo/redis_repo.rb +55 -0
- data/lib/chassis/serializable.rb +52 -0
- data/lib/chassis/string_utils.rb +50 -0
- data/lib/chassis/version.rb +3 -0
- data/lib/chassis/web_service.rb +61 -0
- data/test/array_utils_test.rb +23 -0
- data/test/chassis_test.rb +7 -0
- data/test/circuit_panel_test.rb +22 -0
- data/test/core_ext/array_test.rb +8 -0
- data/test/core_ext/hash_test.rb +8 -0
- data/test/core_ext/string_test.rb +16 -0
- data/test/delegate_test.rb +41 -0
- data/test/dirty_session_test.rb +138 -0
- data/test/error_test.rb +12 -0
- data/test/faraday_test.rb +749 -0
- data/test/form_test.rb +29 -0
- data/test/hash_utils_test.rb +17 -0
- data/test/initializable_test.rb +22 -0
- data/test/logger_test.rb +43 -0
- data/test/observable_test.rb +27 -0
- data/test/persistence_test.rb +112 -0
- data/test/prox_test.rb +7 -0
- data/test/rack/bouncer_test.rb +42 -0
- data/test/rack/builder_patch_test.rb +36 -0
- data/test/rack/health_check_test.rb +35 -0
- data/test/rack/instrumentation_test.rb +38 -0
- data/test/rack/json_body_parser_test.rb +38 -0
- data/test/rack/no_robots_test.rb +34 -0
- data/test/registry_test.rb +26 -0
- data/test/repo/delegation_test.rb +101 -0
- data/test/repo/lazy_association_test.rb +115 -0
- data/test/repo/memory_repo_test.rb +25 -0
- data/test/repo/null_repo_test.rb +48 -0
- data/test/repo/pstore_repo_test.rb +28 -0
- data/test/repo/redis_repo_test.rb +26 -0
- data/test/repo/repo_tests.rb +120 -0
- data/test/repo_test.rb +76 -0
- data/test/serializable_test.rb +77 -0
- data/test/string_utils_test.rb +21 -0
- data/test/test_helper.rb +10 -0
- data/test/web_service_test.rb +107 -0
- metadata +426 -0
data/test/form_test.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class FormTest < MiniTest::Unit::TestCase
|
4
|
+
class Form
|
5
|
+
include Chassis.form
|
6
|
+
|
7
|
+
attribute :name, String
|
8
|
+
attribute :email, String
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_initializer_takes_a_block
|
12
|
+
form = Form.new do |f|
|
13
|
+
f.name = 'Adam'
|
14
|
+
end
|
15
|
+
|
16
|
+
assert_equal 'Adam', form.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_attributes_only_includes_dirty_attributes
|
20
|
+
form = Form.new name: 'Adam'
|
21
|
+
assert_equal({ name: 'Adam' }, form.values)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_assigning_an_known_attributes_raises_an_error
|
25
|
+
assert_raises Chassis::UnknownFormFieldError do
|
26
|
+
Form.new foo: 'bar'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class HashUtilsTest < MiniTest::Unit::TestCase
|
4
|
+
def utils
|
5
|
+
Chassis::HashUtils
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_symbolize_keys_converts_keys_to_symbols
|
9
|
+
result = utils.symbolize({ 'foo' => 'bar' })
|
10
|
+
assert_equal({ foo: 'bar'}, result)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_symbolize_keys_recursises_into_objects
|
14
|
+
result = utils.symbolize({ 'foo' => { 'bar' => 'baz' }})
|
15
|
+
assert_equal({ foo: { bar: 'baz' }}, result)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class InitializableTest < MiniTest::Unit::TestCase
|
4
|
+
class Person
|
5
|
+
include Chassis::Initializable
|
6
|
+
|
7
|
+
attr_accessor :nick
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_sets_attributes
|
11
|
+
person = Person.new nick: 'ahawkins'
|
12
|
+
assert_equal 'ahawkins', person.nick
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_works_with_a_block
|
16
|
+
person = Person.new do |person|
|
17
|
+
person.nick = 'ahawkins'
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_equal 'ahawkins', person.nick
|
21
|
+
end
|
22
|
+
end
|
data/test/logger_test.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class LoggerTest < MiniTest::Unit::TestCase
|
4
|
+
def test_log_level_defaults_to_env_variable
|
5
|
+
with_env :warn do
|
6
|
+
logger = Chassis::Logger.new $stdout
|
7
|
+
assert_equal Logger::WARN, logger.level
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_log_deafults_debug_without_env_variable
|
12
|
+
refute ENV['LOG_LEVEL'], "Precondition: LOG_LEVEL must be blank"
|
13
|
+
|
14
|
+
logger = Chassis::Logger.new $stdout
|
15
|
+
assert_equal Logger::DEBUG, logger.level
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_log_dev_defaults_to_chassis_stream
|
19
|
+
Chassis.stream = StringIO.new
|
20
|
+
|
21
|
+
logger = Chassis::Logger.new
|
22
|
+
logger.debug 'test'
|
23
|
+
|
24
|
+
Chassis.stream.rewind
|
25
|
+
content = Chassis.stream.read
|
26
|
+
|
27
|
+
assert_includes content, 'test'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def with_env(name)
|
32
|
+
original_env = ENV['LOG_LEVEL']
|
33
|
+
ENV['LOG_LEVEL'] = name.to_s
|
34
|
+
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
if original_env
|
38
|
+
ENV['LOG_LEVEL'] = original_env
|
39
|
+
else
|
40
|
+
ENV.delete 'LOG_LEVEL'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class ObservableTest < MiniTest::Unit::TestCase
|
4
|
+
class Ship
|
5
|
+
include Chassis::Observable
|
6
|
+
|
7
|
+
def sink!(command)
|
8
|
+
notify_observers :sink, command
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Watchtower
|
13
|
+
def sink(ship, command)
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_includes_the_object_in_notifications
|
19
|
+
ship = Ship.new
|
20
|
+
observer = Watchtower.new
|
21
|
+
|
22
|
+
observer.expects(:sink).with(ship, 'abandon ship!')
|
23
|
+
|
24
|
+
ship.add_observer observer
|
25
|
+
ship.sink! 'abandon ship!'
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class PersistenceTest < MiniTest::Unit::TestCase
|
4
|
+
class FakeRepo
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
class Model
|
9
|
+
include Chassis::Persistence
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def repo
|
13
|
+
FakeRepo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_models_can_be_initialize_with_a_hash
|
19
|
+
assert_includes Model.ancestors, Chassis::Initializable
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_class_has_a_create_factory
|
23
|
+
FakeRepo.expects(:save)
|
24
|
+
Model.create id: 5
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_delegates_save_to_the_repo
|
28
|
+
model = Model.new id: 5
|
29
|
+
FakeRepo.expects(:save).with(model)
|
30
|
+
model.save
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_delegates_delete_to_the_repo
|
34
|
+
model = Model.new id: 5
|
35
|
+
FakeRepo.expects(:delete).with(model)
|
36
|
+
model.delete
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_is_new_record_when_id_is_nil
|
40
|
+
model = Model.new id: 5
|
41
|
+
refute model.new_record?
|
42
|
+
|
43
|
+
model.id = nil
|
44
|
+
assert model.new_record?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_implements_double_equals
|
48
|
+
m1 = Model.new(id: 1)
|
49
|
+
m2 = Model.new(id: 2)
|
50
|
+
m3 = Model.new(id: 1)
|
51
|
+
|
52
|
+
assert (m1 == m1)
|
53
|
+
assert (m1 == m3)
|
54
|
+
refute (m1 == m2)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_implements_triple_equals
|
58
|
+
m1 = Model.new(id: 1)
|
59
|
+
m2 = Model.new(id: 2)
|
60
|
+
m3 = Model.new(id: 1)
|
61
|
+
|
62
|
+
assert (m1 === m1)
|
63
|
+
assert (m1 === m3)
|
64
|
+
refute (m1 === m2)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_implements_eql?
|
68
|
+
m1 = Model.new(id: 1)
|
69
|
+
m2 = Model.new(id: 2)
|
70
|
+
m3 = Model.new(id: 1)
|
71
|
+
|
72
|
+
assert (m1 == m1)
|
73
|
+
assert (m1 == m3)
|
74
|
+
refute (m1 == m2)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_acts_has_a_hash_key
|
78
|
+
m1 = Model.new(id: 1)
|
79
|
+
m2 = Model.new(id: 2)
|
80
|
+
m3 = Model.new(id: 1)
|
81
|
+
|
82
|
+
hash = { }
|
83
|
+
hash[m1] = 'm1'
|
84
|
+
|
85
|
+
assert_includes hash, m1
|
86
|
+
assert_includes hash, m3
|
87
|
+
refute_includes hash, m2
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_acts_well_in_arrays
|
91
|
+
m1 = Model.new(id: 1)
|
92
|
+
m2 = Model.new(id: 2)
|
93
|
+
m3 = Model.new(id: 1)
|
94
|
+
|
95
|
+
array = [ m1 ]
|
96
|
+
|
97
|
+
assert_includes array, m1
|
98
|
+
assert_includes array, m3
|
99
|
+
refute_includes array, m2
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_save_takes_a_block_of_changes
|
103
|
+
FakeRepo.expects(:save)
|
104
|
+
|
105
|
+
model = Model.new id: 5
|
106
|
+
model.save do |m|
|
107
|
+
m.id = 6
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_equal 6, model.id
|
111
|
+
end
|
112
|
+
end
|
data/test/prox_test.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class RackBouncer < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class HelloWorld
|
7
|
+
def call(env)
|
8
|
+
[200, {'Content-Type' => 'text/plain' }, 'Hi']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :app
|
13
|
+
|
14
|
+
def setup
|
15
|
+
builder = Rack::Builder.new
|
16
|
+
builder.use Chassis::Rack::Bouncer
|
17
|
+
builder.run HelloWorld.new
|
18
|
+
@app = builder.to_app
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_bouncers_masscan
|
22
|
+
get '/', { } , 'HTTP_USER_AGENT' => 'masscan/1.0 (https://github.com/robertdavidgraham/masscan)'
|
23
|
+
assert_equal 403, last_response.status
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_can_supply_a_custom_bouncer
|
27
|
+
builder = Rack::Builder.new
|
28
|
+
builder.use Chassis::Rack::Bouncer do |req|
|
29
|
+
true
|
30
|
+
end
|
31
|
+
builder.run HelloWorld.new
|
32
|
+
@app = builder.to_app
|
33
|
+
|
34
|
+
get '/'
|
35
|
+
assert_equal 403, last_response.status
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_allows_other_requests
|
39
|
+
get '/foo'
|
40
|
+
assert_equal 'Hi', last_response.body
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class RackBuilderPatchTest < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class OrderPrinter
|
7
|
+
def call(env)
|
8
|
+
[200, {'Content-Type' => 'text/plain'}, [ env['order'].join(',') ]]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class OrderedMiddleware
|
13
|
+
def initialize(app, position)
|
14
|
+
@app, @position = app, position
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
env['order'] ||= []
|
19
|
+
env['order'] << @position
|
20
|
+
@app.call env
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :app
|
25
|
+
|
26
|
+
def test_should_be_able_to_insert_middlewares_at_the_top
|
27
|
+
@app = Rack::Builder.app do
|
28
|
+
shim OrderedMiddleware, 2
|
29
|
+
shim OrderedMiddleware, 1
|
30
|
+
run OrderPrinter.new
|
31
|
+
end
|
32
|
+
|
33
|
+
get '/'
|
34
|
+
assert_equal '1,2', last_response.body
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class HealthCheckTest < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class HelloWorld
|
7
|
+
def call(env)
|
8
|
+
[200, {'Content-Type' => 'text/plain' }, 'Hi']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :log, :app
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@log = StringIO.new
|
16
|
+
builder = Rack::Builder.new
|
17
|
+
builder.use Chassis::Rack::Ping
|
18
|
+
builder.run HelloWorld.new
|
19
|
+
@app = builder.to_app
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_returns_status_when_accessing_ping
|
23
|
+
get '/ping'
|
24
|
+
|
25
|
+
assert_equal 200, last_response.status
|
26
|
+
assert_equal 'text/plain', last_response.content_type
|
27
|
+
assert_equal 'pong', last_response.body
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_allows_other_requests
|
31
|
+
get '/foo'
|
32
|
+
|
33
|
+
assert_equal 'Hi', last_response.body
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class RackInstrumentationTest < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class HelloWorld
|
7
|
+
def call(env)
|
8
|
+
[200, {'Content-Type' => 'text/plain' }, 'Hi']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :app
|
13
|
+
|
14
|
+
def setup
|
15
|
+
Harness.config.collector = Harness::FakeCollector.new
|
16
|
+
Harness.config.queue = Harness::SyncQueue.new
|
17
|
+
|
18
|
+
builder = Rack::Builder.new
|
19
|
+
builder.use Chassis::Rack::Instrumentation
|
20
|
+
builder.run HelloWorld.new
|
21
|
+
@app = builder.to_app
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_adds_the_x_runtime_header
|
25
|
+
get '/foo'
|
26
|
+
|
27
|
+
assert_equal 'Hi', last_response.body
|
28
|
+
|
29
|
+
assert last_response['X-Runtime']
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_instruments_with_harness
|
33
|
+
get '/foo'
|
34
|
+
|
35
|
+
refute_empty Harness.collector.timers
|
36
|
+
assert_equal 'rack.request.all', Harness.collector.timers.first.name
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class JsonBodyParserTest < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
class EchoServer
|
7
|
+
def call(env)
|
8
|
+
request = Rack::Request.new env
|
9
|
+
[200, {'Content-Type' => 'text/plain'}, [JSON.dump(request.params)]]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :app
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@app = Rack::Builder.app do
|
17
|
+
use Chassis::Rack::JsonBodyParser
|
18
|
+
run EchoServer.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_parses_json_requests
|
23
|
+
post '/', JSON.dump({foo: 'bar'}), 'CONTENT_TYPE' => 'application/json'
|
24
|
+
|
25
|
+
params = JSON.load last_response.body
|
26
|
+
assert_equal 'bar', params.fetch('foo')
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_handles_empty_get_requests
|
30
|
+
get '/', { }, { 'CONTENT_TYPE' => 'application/json' }
|
31
|
+
assert_equal 200, last_response.status
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_json_parse_errors_return_400
|
35
|
+
post '/', 'foo=bar', 'CONTENT_TYPE' => 'application/json'
|
36
|
+
assert_equal 400, last_response.status
|
37
|
+
end
|
38
|
+
end
|