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